
org.jpedal.display.MultiPageDecoder Maven / Gradle / Ivy
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2016 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---------------
* MultiPageDecoder.java
* ---------------
*/
package org.jpedal.display;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Semaphore;
import org.jpedal.FileAccess;
import org.jpedal.PdfDecoderInt;
import org.jpedal.constants.SpecialOptions;
import static org.jpedal.display.Display.CONTINUOUS;
import static org.jpedal.display.Display.CONTINUOUS_FACING;
import static org.jpedal.display.Display.FACING;
import static org.jpedal.display.Display.SINGLE_PAGE;
import static org.jpedal.display.Display.debugLayout;
import org.jpedal.external.Options;
import org.jpedal.external.RenderChangeListener;
import org.jpedal.gui.GUIFactory;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.acroforms.AcroRenderer;
import org.jpedal.objects.raw.PageObject;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.parser.*;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.text.TextLines;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.repositories.Vector_Int;
import org.jpedal.utils.repositories.generic.Vector_Rectangle_Int;
/**
*
* @author markee
*/
public abstract class MultiPageDecoder {
private final Semaphore semaphore=new Semaphore(1);
final GUIFactory gui;
//used to redraw multiple pages
private Thread worker;
public final Map currentPageViews=new HashMap();
private final FileAccess fileAccess;
private final PdfObjectReader currentPdfFile;
private final AcroRenderer formRenderer;
private int displayView;
private Map cachedPageViews=new WeakHashMap();
private final MultiPagesDisplay display;
private RenderChangeListener customRenderChangeListener;
//facing mode drag pages
private final BufferedImage[] facingDragCachedImages = new BufferedImage[4];
private BufferedImage facingDragTempLeftImg, facingDragTempRightImg;
private int facingDragTempLeftNo, facingDragTempRightNo;
private final MultiDisplayOptions multiDisplayOptions;
public final PdfDecoderInt pdf;
private final PdfPageData pageData;
final DecoderOptions options;
final DisplayOffsets offsets;
public MultiPageDecoder(final GUIFactory gui,final PdfDecoderInt pdf,final PdfPageData pageData,final MultiPagesDisplay display, final MultiDisplayOptions multiDisplayOptions,
final DynamicVectorRenderer currentDisplay, final int pageNumber,final FileAccess fileAccess,
final PdfObjectReader io, final AcroRenderer formRenderer,final DecoderOptions options) {
this.gui=gui;
this.pdf=pdf;
this.pageData=pageData;
this.display=display;
this.multiDisplayOptions=multiDisplayOptions;
this.fileAccess=fileAccess;
this.currentPdfFile=io;
this.formRenderer=formRenderer;
this.options=options;
offsets=(DisplayOffsets) pdf.getExternalHandler(Options.DisplayOffsets);
/**cache current page*/
if(currentDisplay!=null) {
currentPageViews.put(pageNumber, currentDisplay);
}
}
/**used to decode multiple pages on views*/
public void decodeOtherPages(int pageNumber, final int pageCount, int displayView) {
try {
semaphore.acquire();
} catch (InterruptedException ex) {
LogWriter.writeLog("Exception: " + ex.getMessage());
}
this.displayView=displayView;
if(debugLayout) {
System.out.println("start decodeOtherPages " + pageNumber + ' ' + pageCount);
}
//Ensure page range does not drop below one
if(pageNumber<1) {
pageNumber = 1;
}
final int oldPN = multiDisplayOptions.getPageNumber();
multiDisplayOptions.setPageNumber(pageNumber);
//Store the image to be used instead of filling the borders with white
if (displayView==FACING && multiDisplayOptions.isTurnoverOn()) {
final int lp;
if (multiDisplayOptions.isSeparateCover()) {
lp = (oldPN / 2) * 2;
} else {
lp = oldPN - (1 - (oldPN & 1));
}
if (offsets.getDragLeft()) {
facingDragTempLeftImg = facingDragCachedImages[0];
facingDragTempLeftNo = lp-2;
facingDragTempRightImg = facingDragCachedImages[1];
facingDragTempRightNo = lp-1;
} else {
facingDragTempLeftImg = facingDragCachedImages[2];
facingDragTempLeftNo = lp+2;
facingDragTempRightImg = facingDragCachedImages[3];
facingDragTempRightNo = lp+3;
}
}
facingDragCachedImages[0]=null;
facingDragCachedImages[1]=null;
facingDragCachedImages[2]=null;
facingDragCachedImages[3]=null;
calcDisplayedRange();
while(multiDisplayOptions.isRunning()){
try {
Thread.sleep(100);
} catch (final InterruptedException e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
//need in JavaFX to avoid illegalThreadState
if(worker!=null && worker.getState()==Thread.State.TERMINATED){
worker=null;
}
// restart if not display.running - uses pages to control loop so I hope will
// pick up change
if(worker!=null && worker.getState()!=Thread.State.NEW){
//still running
//System.out.println(worker.getState()+" "+worker);
}else if ((worker == null || !multiDisplayOptions.isRunning())) {
multiDisplayOptions.setRunning(true);
worker = new Thread() {
@Override
public void run(){
try {
if(debugLayout) {
System.out.println("START=========================Started decoding pages "
+ multiDisplayOptions.getStartViewPage() + ' ' + multiDisplayOptions.getEndViewPage());
}
decodeOtherPages();
if(debugLayout) {
System.out.println("END=========================Pages done");
}
multiDisplayOptions.setRunning(false);
//tell user object if exists that pages painted
if(customRenderChangeListener!=null) {
customRenderChangeListener.renderingWorkerFinished();
}
} catch (final Exception e) {
multiDisplayOptions.setRunning(false);
LogWriter.writeLog("Exception: " + e.getMessage());
}catch(final Error err){
multiDisplayOptions.setRunning(false);
LogWriter.writeLog("Error: " + err.getMessage());
}finally{
semaphore.release();
}
}
};
worker.setDaemon(true);
worker.start();
}
}
/**
* scans all pages in visible range and decodes
*/
void decodeOtherPages() {
final int pageCount=pageData.getPageCount();
if(debugLayout) {
System.out.println("decodeOtherPages called");
}
multiDisplayOptions.setIsGeneratingOtherPages(true);
int page = multiDisplayOptions.getStartViewPage(), originalStart = multiDisplayOptions.getStartViewPage(), originalEnd = multiDisplayOptions.getEndViewPage()+1;
//increase decoded range for facing with turnover
int firstFacing=1, lastFacing=1;
if (multiDisplayOptions.isTurnoverOn() && displayView == FACING) {
firstFacing = originalStart - 2;
lastFacing = firstFacing + 6;
if (firstFacing < 1) {
firstFacing = 1;
}
if (lastFacing > pageCount+1) {
lastFacing = pageCount + 1;
}
}
int facingCount = lastFacing-firstFacing;
resetPageCaches(multiDisplayOptions.getStartViewPage(), multiDisplayOptions.getEndViewPage()+1);
if(debugLayout){
System.out.println("decoding ------START " + originalStart + " END="+ originalEnd+" display.isGeneratingOtherPages="+multiDisplayOptions.isIsGeneratingOtherPages());
System.out.println(multiDisplayOptions.getStartViewPage() + " "+ multiDisplayOptions.getEndViewPage());
}
while (multiDisplayOptions.isIsGeneratingOtherPages()) {
// detect if restarted
if ((originalStart != multiDisplayOptions.getStartViewPage())&& (originalEnd != multiDisplayOptions.getEndViewPage())) {
if(debugLayout) {
System.out.println("Worker detected change to page range to " + multiDisplayOptions.getStartViewPage() + ' ' + multiDisplayOptions.getEndViewPage());
}
page = multiDisplayOptions.getStartViewPage();
originalEnd = multiDisplayOptions.getEndViewPage()+1;
// can be zero in facing mode
if (page == 0) {
page++;
}
originalStart = page;
if (multiDisplayOptions.isTurnoverOn() && displayView == FACING) {
firstFacing = originalStart - 2;
lastFacing = firstFacing + 6;
if (firstFacing < 1) {
firstFacing = 1;
}
if (lastFacing > pageCount+1) {
lastFacing = pageCount + 1;
}
facingCount = lastFacing-firstFacing;
}
resetPageCaches(originalStart, originalEnd);
}
// exit if finished
if (multiDisplayOptions.isTurnoverOn() && displayView == FACING && facingCount==0) {
break;
}
if ((!multiDisplayOptions.isTurnoverOn() || displayView != FACING) && page == originalEnd) {
break;
}
if(originalStart>originalEnd) {
break;
}
if (page > 0 && page < pdf.getPageCount()+1) {
decodeMorePages(page, originalStart, originalEnd);
}
//store thumbnail for turnover if in facing mode
if (displayView==FACING && multiDisplayOptions.isTurnoverOn()) {
int leftPage = multiDisplayOptions.getPageNumber();
if (multiDisplayOptions.isSeparateCover() && (leftPage & 1) == 1) {
leftPage--;
}
if (!multiDisplayOptions.isSeparateCover() && (leftPage & 1) == 0) {
leftPage--;
}
int ref = page-leftPage + 2;
if (!(ref > 1 && ref < 4 )){
if (ref > 1) {
ref -= 2;
}
int[] pageW=multiDisplayOptions.getPageW();
int[] pageH=multiDisplayOptions.getPageH();
// System.out.println("page + \" \" + ref = " + page + " " + ref);
if(ref < 4 && ref > -1 && facingDragCachedImages[ref]==null) {
final BufferedImage image = new BufferedImage(pageW[page], pageH[page], BufferedImage.TYPE_INT_ARGB);
final Graphics2D pg = (Graphics2D) image.getGraphics();
final int displayRotation=display.getDisplayRotation();
pg.rotate(displayRotation*Math.PI/180);
try {
switch (displayRotation) {
case 90:
pg.translate(0,-pageW[page]);
pg.drawImage(pdf.getPageAsImage(page),0,0,pageH[page]+1,pageW[page]+1,null);
break;
case 180:
pg.translate(-pageW[page],-pageH[page]);
pg.drawImage(pdf.getPageAsImage(page),0,0,pageW[page]+1, pageH[page]+1, null);
break;
case 270:
pg.translate(-pageH[page], 0);
pg.drawImage(pdf.getPageAsImage(page),0,0,pageH[page]+1, pageW[page]+1,null);
break;
default:
pg.drawImage(pdf.getPageAsImage(page),0,0,pageW[page]+1, pageH[page]+1,null);
break;
}
} catch(final Exception e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
facingDragCachedImages[ref] = image;
}
}
}
facingCount--;
//change decode order if facing
page++;
if (multiDisplayOptions.isTurnoverOn() && displayView == FACING && page == lastFacing) {
page = firstFacing;
}
}
if(debugLayout) {
System.out.println("decodeOtherPageinins------ENDED");
}
}
public void decodeMorePages(int page, int originalStart, int originalEnd) {
/**
* decode or get cached page data. If it is still in cache we just need a repaint
*/
if(currentPageViews.get(page)==null){
decodePage(page, originalStart, originalEnd);
}
repaint();
}
public void decodePage(int page,int originalStart, int originalEnd) {
final AcroRenderer formRenderer =pdf.getFormRenderer();
//Create form objects
if (displayView == CONTINUOUS || displayView == CONTINUOUS_FACING){
final PdfStreamDecoder current= (PdfStreamDecoder) currentPageViews.get(page);
//if(currentOffset!=null)
// formRenderer.getCompData().setPageValues(pdf.getScaling(), displayRotation,(int)getIndent(),0,0,pdf.getDisplayView(),currentOffset.widestPageNR,currentOffset.widestPageR);
formRenderer.createDisplayComponentsForPage(page,current);
if(pdf.getSpecialMode()!= SpecialOptions.NONE &&
pdf.getSpecialMode()!= SpecialOptions.SINGLE_PAGE &&
page!=pdf.getPageCount()) {
formRenderer.createDisplayComponentsForPage(page + 1, current);
}
}
/** get pdf object id for page to decode */
final String currentPageOffset = pdf.getIO().getReferenceforPage(page);
if (debugLayout) {
System.out.println("Decoding page " + page + " currentPageOffset=" + currentPageOffset + " range=" + originalStart + ' ' + originalEnd);
}
/**
* decode the file if not already decoded and stored
*/
if (currentPageOffset != null || (formRenderer.isXFA() && formRenderer.useXFA())) {
final Integer key= page;
final Object currentView = currentPageViews.get(key);
if (currentView == null && multiDisplayOptions.isIsGeneratingOtherPages()) {
if (debugLayout) {
System.out.println("recreate page");
}
// force redraw
display.forceRedraw();
getPageView(currentPageOffset, page);
}
}
}
private void getPageView(final String currentPageOffset, final int pageNumber) {
final PdfObject pdfObject=new PageObject(currentPageOffset);
//ensure set (needed for XFA)
pdfObject.setPageNumber(pageNumber);
/** read page or next pages */
currentPdfFile.readObject(pdfObject);
final PdfObject Resources=pdfObject.getDictionary(PdfDictionary.Resources);
final DynamicVectorRenderer currentDisplay = getNewDisplay(pageNumber);
/** set hires mode or not for display */
currentDisplay.setHiResImageForDisplayMode(pdf.isHiResScreenDisplay());
int val=0;
if(pdf.getDisplayView()==Display.CONTINUOUS && pdf.getDisplayView()==Display.CONTINUOUS_FACING) {
val = 1;
}
final PdfStreamDecoder current=formRenderer.getStreamDecoder(currentPdfFile, pdf.isHiResScreenDisplay(), fileAccess.getRes().getPdfLayerList(),true);
/**
* draw acroform data onto Panel
*/
if (formRenderer != null && pdf.isForm()) {
// formRenderer.getCompData().setPageValues(scaling, displayRotation,0,0,0,pdf.getDisplayView(),currentOffset.widestPageNR,currentOffset.widestPageR);
formRenderer.createDisplayComponentsForPage(pageNumber,current);
}
current.setParameters(true, true, 7, val, false,pdf.getExternalHandler().getMode().equals(GUIModes.JAVAFX));
current.setXMLExtraction(pdf.isXMLExtraction());
pdf.getExternalHandler().addHandlers(current);
current.setObjectValue(ValueTypes.Name, fileAccess.getFilename());
current.setObjectValue(ValueTypes.ObjectStore, pdf.getObjectStore());
current.setObjectValue(ValueTypes.PDFPageData,pageData);
current.setIntValue(ValueTypes.PageNum, pageNumber);
current.setRenderer(currentDisplay);
try {
currentDisplay.init(pageData.getMediaBoxWidth(pageNumber), pageData.getMediaBoxHeight(pageNumber),options.getPageColor());
currentDisplay.setValue(DynamicVectorRenderer.ALT_BACKGROUND_COLOR, options.getPageColor().getRGB());
if(options.getTextColor()!=null){
currentDisplay.setValue(DynamicVectorRenderer.ALT_FOREGROUND_COLOR, options.getTextColor().getRGB());
if(options.getChangeTextAndLine()) {
currentDisplay.setValue(DynamicVectorRenderer.FOREGROUND_INCLUDE_LINEART, 1);
} else {
currentDisplay.setValue(DynamicVectorRenderer.FOREGROUND_INCLUDE_LINEART, 0);
}
}
fileAccess.getRes().setupResources(current, false, Resources,pageNumber,currentPdfFile);
current.decodePageContent(pdfObject);
final TextLines textLines = pdf.getTextLines();
//All data loaded so now get all line areas for page
if(textLines!=null){
final Vector_Rectangle_Int vr = (Vector_Rectangle_Int) current.getObjectValue(ValueTypes.TextAreas);
vr.trim();
final int[][] pageTextAreas = vr.get();
final Vector_Int vi = (Vector_Int) current.getObjectValue(ValueTypes.TextDirections);
vi.trim();
final int[] pageTextDirections = vi.get();
for(int k=0; k!=pageTextAreas.length; k++){
textLines.addToLineAreas(pageTextAreas[k], pageTextDirections[k], pageNumber);
}
}
//tell viewer we have finished so it will generate highlights
currentDisplay.flagDecodingFinished();
} catch (final Exception ex) {
LogWriter.writeLog("Exception: " + ex.getMessage());
} catch (final Error err) {
LogWriter.writeLog("Exception: " + err.getMessage());
}
currentPageViews.put(pageNumber,currentDisplay);
display.setCurrentDisplay(currentDisplay);
}
/**
* copy pages between WeakMap and vice-versa
*/
void resetPageCaches(final int startPage, final int endPage) {
// copy any pages in existence into hashMap so not garbage collected
//synchronized (cachedPageViews) {
for (final Object o : this.cachedPageViews.keySet()) {
final Integer currentKey = (Integer) o;
final int keyValue = currentKey;
if ((keyValue >= startPage) && (keyValue <= endPage)) {
final Object obj = cachedPageViews.get(currentKey);
if (obj != null) {
this.currentPageViews.put(currentKey, obj);
}
}
}
//}
// move any pages not visible into cache
{
//synchronized (currentPageViews) {
Iterator keys = this.currentPageViews.keySet().iterator();
final Map keysToTrash = new HashMap();
while (keys.hasNext()) {
final Integer currentKey = (Integer) keys.next();
final int keyValue = currentKey;
if ((keyValue < startPage) || (keyValue > endPage)) {
final Object obj = currentPageViews.get(currentKey);
if (obj != null) {
this.cachedPageViews.put(currentKey, obj);
}
// track so we can delete outside loop
keysToTrash.put(currentKey, "x");
}
}
// now remove
keys = keysToTrash.keySet().iterator();
while (keys.hasNext()) {
currentPageViews.remove(keys.next());
}
//}
}
//System.out.println("cache contains " + currentPageViews.keySet());
}
/**
* workout which pages are displayed
*/
private synchronized void calcDisplayedRange() {
final int pageCount=pageData.getPageCount();
if(debugLayout) {
System.out.println("calcDisplayedRange pageNumber=" + multiDisplayOptions.getPageNumber() + " mode=" + displayView);
}
if(displayView==SINGLE_PAGE) {
return;
}
display.getDisplayedRectangle();
if(displayView==FACING){
multiDisplayOptions.calcDisplayRangeForFacing();
}else{
//// START SI'S PAGE COUNTER ////////////
final int newPage = updatePageDisplayed();
//System.out.println("newPage="+newPage);
fileAccess.setPageNumber(newPage);
//// END SI'S PAGE COUNTER ////////////
//update page number
if(newPage!=-1)// && customSwingHandle!=null)
{
gui.setPage(newPage);//( (org.jpedal.gui.GUIFactory) customSwingHandle).setPage(newPage);
}
}
//Ensure end page is not set beyond the total count of pages
if(multiDisplayOptions.getEndViewPage()>pageCount) {
multiDisplayOptions.setEndViewPage(pageCount);
}
if(displayView!=FACING) {
display.refreshDisplay(); //refresh display to fix backbuffer background color issue
}
}
public void flushPageCaches() {
currentPageViews.clear();
cachedPageViews.clear();
}
public DynamicVectorRenderer getCurrentPageView(int i) {
return (DynamicVectorRenderer) currentPageViews.get(i);
}
public void dispose() {
this.cachedPageViews=null;
}
private int updatePageDisplayed() {
int newPage=-1;
final int pageCount=pageData.getPageCount();
final int[] yReached=multiDisplayOptions.getyReached();
final int[] pageH = multiDisplayOptions.getPageH();
final int ry=display.getRy();
final int rh=display.getRh();
//final int insetH=display.getInsetH();
final boolean debug=false;
int largestH = 0;
int firstVisiblePage = 0;
int lastVisiblePage = 0;
for (int i = 1; i <= pageCount; i += 1) {
int pageTop=yReached[i];
int pageBottom=yReached[i]+pageH[i];
int viewBottom=ry+rh;
if(debug){
System.out.println(display.getInsetH()+" "+i+ ' ' +" pageTop="+pageTop+" pageBottom="+pageBottom+" viewTop="+ ry +" viewBottom="+ viewBottom);
}
if( pageTop<=viewBottom && pageBottom>= ry){
//in view
if(debug){
System.out.println("in view ");
}
}else{
continue;
}
//If not set yet, set to first page by default
if (newPage == -1) {
newPage = i;
firstVisiblePage=i;
}
lastVisiblePage = i;
int midPt=ry+(rh/2);
int gap=midPt-yReached[i];
if(debug){
System.out.println("gap="+gap);
}
if (gap<0 || gap>pageH[i]){
gap=0;
}
int gap2=yReached[i]+pageH[i]-midPt;
if(debug){
System.out.println("gap2="+gap2);
}
if (gap2<0 || gap2>pageH[i]){
gap2=0;
}
if(gap2>gap){
gap=gap2;
}
if (gap>0 && gap > largestH) {
largestH =gap;
newPage = i;
if(debug){
System.out.println(i+" gap now="+largestH);
}
//break; //Stop if found first whole page
}
if(debug){
System.out.println(i+" reached "+yReached[i]+ ' ' +ry);
}
}
//If still not set set page 1 as a default
if (newPage == -1) {
newPage = 1;
firstVisiblePage = 1;
lastVisiblePage = 1;
}
multiDisplayOptions.setStartViewPage(firstVisiblePage);
//allow for 2 page doc with both pages onscreen
multiDisplayOptions.setEndViewPage(lastVisiblePage);
if (debugLayout) {
System.out.println("page range start=" + multiDisplayOptions.getStartViewPage() + " end=" + multiDisplayOptions.getEndViewPage());
}
//Ensure end page is not set beyond the total count of pages
if (multiDisplayOptions.getEndViewPage() > pageCount) {
multiDisplayOptions.setEndViewPage(pageCount);
}
return newPage;
}
public void resetCachedFacingImages() {
for (int i=0; i<4; i++) {
facingDragCachedImages[i] = null;
}
}
public BufferedImage[] getFacingDragImages() {
return facingDragCachedImages;
}
public BufferedImage getfacingDragTempLeftImg() {
return facingDragTempLeftImg;
}
public BufferedImage getfacingDragTempRightImg() {
return facingDragTempRightImg;
}
public int getFacingDragTempLeftNo() {
return facingDragTempLeftNo;
}
public int getFacingDragTempRightNo() {
return facingDragTempRightNo;
}
public void setCustomRenderChangeListener(RenderChangeListener customRenderChangeListener) {
this.customRenderChangeListener=customRenderChangeListener;
}
public void repaint() {
throw new UnsupportedOperationException("repaint Not supported yet.");
}
public DynamicVectorRenderer getNewDisplay(int pageNumber) {
throw new UnsupportedOperationException(this+ "Not supported yet.");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy