/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.icepdf.core.pobjects;
import org.icepdf.core.events.*;
import org.icepdf.core.io.SeekableInput;
import org.icepdf.core.pobjects.annotations.Annotation;
import org.icepdf.core.pobjects.annotations.FreeTextAnnotation;
import org.icepdf.core.pobjects.graphics.Shapes;
import org.icepdf.core.pobjects.graphics.WatermarkCallback;
import org.icepdf.core.pobjects.graphics.text.GlyphText;
import org.icepdf.core.pobjects.graphics.text.LineText;
import org.icepdf.core.pobjects.graphics.text.PageText;
import org.icepdf.core.pobjects.graphics.text.WordText;
import org.icepdf.core.util.*;
import org.icepdf.core.util.content.ContentParser;
import org.icepdf.core.util.content.ContentParserFactory;
import java.awt.*;
import java.awt.geom.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>This class represents the leaves of a <code>PageTree</code> object known
* as <code>Page</code> class. The page dictionary specifies attributes
* of the single page of the document. Many of the page's attributes are
* inherited from the page tree dictionary if not specified in the page
* dictionary.</p>
* <br>
* <p>The page object also provides a method which will extract a page's content,
* such as text and images. The <code>paint</code> method is the core of
* the ICEpdf renderer, allowing page content to be painted to any Java graphics
* context. </p>
* <br>
* <p>Page objects in a PDF document have different boundaries defined which
* govern various aspects of the pre-press process, such as cropping, bleed,
* and trimming. Facilities for including printer's marks, such a registration
* targets, gray ramps color bars, and cut marks which assist in the production
* process. When getting a page's size, the default boundary used is the crop
* box which is what most viewer applications should use. However, if your application
* requires the use of different page boundaries, they can be specified when
* using the getSize or paint methods. If in doubt, always use the crop box
* constant.</p>
*
* @see org.icepdf.core.pobjects.PageTree
* @since 1.0
*/
public class Page extends Dictionary {
private static final Logger logger =
Logger.getLogger(Page.class.toString());
/**
* Transparency value used to simulate text highlighting.
*/
public static final float SELECTION_ALPHA = 0.3f;
// text selection colour
public static Color selectionColor;
static {
// sets the shadow colour of the decorator.
try {
String color = Defs.sysProperty(
"org.icepdf.core.views.page.text.selectionColor", "#0077FF"); //#99c1da
int colorValue = ColorUtil.convertColor(color);
selectionColor =
new Color(colorValue >= 0 ? colorValue :
Integer.parseInt("99c1da", 16));
} catch (NumberFormatException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Error reading text selection colour");
}
}
}
// text highlight colour
public static Color highlightColor;
static {
// sets the shadow colour of the decorator.
try {
String color = Defs.sysProperty(
"org.icepdf.core.views.page.text.highlightColor", "#CC00FF");//ff99ff
int colorValue = ColorUtil.convertColor(color);
highlightColor =
new Color(colorValue >= 0 ? colorValue :
Integer.parseInt("ff99ff", 16));
} catch (NumberFormatException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Error reading text highlight colour");
}
}
}
public static final Name TYPE = new Name("Page");
public static final Name ANNOTS_KEY = new Name("Annots");
public static final Name CONTENTS_KEY = new Name("Contents");
public static final Name RESOURCES_KEY = new Name("Resources");
public static final Name THUMB_KEY = new Name("Thumb");
public static final Name PARENT_KEY = new Name("Parent");
public static final Name ROTATE_KEY = new Name("Rotate");
public static final Name MEDIABOX_KEY = new Name("MediaBox");
public static final Name CROPBOX_KEY = new Name("CropBox");
public static final Name ARTBOX_KEY = new Name("ArtBox");
public static final Name BLEEDBOX_KEY = new Name("BleedBox");
public static final Name TRIMBOX_KEY = new Name("TrimBox");
/**
* Defines the boundaries of the physical medium on which the page is
* intended to be displayed or printed.
*/
public static final int BOUNDARY_MEDIABOX = 1;
/**
* Defines the visible region of the default user space. When the page
* is displayed or printed, its contents are to be clipped to this
* rectangle and then imposed on the output medium in some implementation
* defined manner.
*/
public static final int BOUNDARY_CROPBOX = 2;
/**
* Defines the region to which the contents of the page should be clipped
* when output in a production environment (Mainly commercial printing).
*/
public static final int BOUNDARY_BLEEDBOX = 3;
/**
* Defines the intended dimensions of the finished page after trimming.
*/
public static final int BOUNDARY_TRIMBOX = 4;
/**
* Defines the extent of the page's meaningful content as intended by the
* page's creator.
*/
public static final int BOUNDARY_ARTBOX = 5;
// resources for page's parent pages, default fonts, etc.
private Resources resources;
// Vector of annotations
private List<Annotation> annotations;
// Contents
private List<Stream> contents;
// Container for all shapes stored on page
private Shapes shapes = null;
// the collection of objects listening for page paint events
private final List<PaintPageListener> paintPageListeners = new ArrayList<PaintPageListener>(8);
// the collection of objects listening for page loading events
private final List<PageLoadingListener> pageLoadingListeners = new ArrayList<PageLoadingListener>();
// Defines the boundaries of the physical medium on which the page is
// intended to be displayed on.
private PRectangle mediaBox;
// Defining the visible region of default user space.
private PRectangle cropBox;
// Defines the region to which the contents of the page should be clipped
// when output in a production environment.
private PRectangle bleedBox;
// Defines the intended dimension of the finished page after trimming.
private PRectangle trimBox;
// Defines the extent of the pages meaningful content as intended by the
// pages creator.
private PRectangle artBox;
// page has default rotation value
private float pageRotation = 0;
private int pageIndex;
private int imageCount;
private boolean pageInitialized;
private boolean pagePainted;
private WatermarkCallback watermarkCallback;
/**
* Create a new Page object. A page object represents a PDF object that
* has the name page associated with it. It also conceptually represents
* a page entity and all of it child elements that are associated with it.
*
* @param l pointer to default library containing all document objects
* @param h HashMap containing all of the dictionary entries
*/
public Page(Library l, HashMap h) {
super(l, h);
}
public boolean isInitiated() {
return inited;
}
private void initPageContents() throws InterruptedException {
Object pageContent = library.getObject(entries, CONTENTS_KEY);
// if a stream process it as needed
if (pageContent instanceof Stream) {
contents = new ArrayList<Stream>(1);
Stream tmpStream = (Stream) pageContent;
tmpStream.setPObjectReference(
library.getObjectReference(entries, CONTENTS_KEY));
contents.add(tmpStream);
}
// if a vector, process it as needed
else if (pageContent instanceof List) {
List conts = (List) pageContent;
int sz = conts.size();
contents = new ArrayList<Stream>(Math.max(sz, 1));
// pull all of the page content references from the library
for (int i = 0; i < sz; i++) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("Page Content initialization thread interrupted");
}
Object tmp = library.getObject(conts.get(i));
if (tmp instanceof Stream) {
Stream tmpStream = (Stream) tmp;
// prune any zero length streams,
if (tmpStream != null && tmpStream.getRawBytes().length > 0) {
tmpStream.setPObjectReference((Reference) conts.get(i));
contents.add(tmpStream);
}
}
}
}
}
public void initPageResources() throws InterruptedException {
Resources res = library.getResources(entries, RESOURCES_KEY);
PageTree pageTree;
if (res == null) {
pageTree = getParent();
while (pageTree != null) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("Page Resource initialization thread interrupted");
}
Resources parentResources = pageTree.getResources();
if (parentResources != null) {
res = parentResources;
break;
}
pageTree = pageTree.getParent();
}
}
resources = res;
}
/**
* Gets a raw list of annotation references. The annotations are not initialized.
*
* @return list of a pages annotation reference list.
*/
public ArrayList<Reference> getAnnotationReferences() {
Object annots = library.getObject(entries, ANNOTS_KEY);
if (annots != null && annots instanceof ArrayList) {
return (ArrayList<Reference>) annots;
}
return null;
}
private void initPageAnnotations() throws InterruptedException {
// find annotations in main library for our pages dictionary
Object annots = library.getObject(entries, ANNOTS_KEY);
if (annots != null && annots instanceof List) {
List v = (List) annots;
annotations = new ArrayList<Annotation>(v.size() + 1);
// add annotations
Object annotObj;
org.icepdf.core.pobjects.annotations.Annotation a = null;
for (int i = 0; i < v.size(); i++) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException(
"Page Annotation initialization thread interrupted");
}
annotObj = v.get(i);
Reference ref = null;
// we might have a reference
if (annotObj instanceof Reference) {
ref = (Reference) v.get(i);
annotObj = library.getObject(ref);
}
// but most likely its an annotations base class
if (annotObj instanceof Annotation) {
a = (Annotation) annotObj;
}
// or build annotations from dictionary.
else if (annotObj instanceof HashMap) { // HashMap lacks "Type"->"Annot" entry
a = Annotation.buildAnnotation(library, (HashMap) annotObj);
}
// set the object reference, so we can save the state correct
// and update any references accordingly.
try {
// set the object reference, so we can save the state correct
// and update any references accordingly.
if (ref != null && a != null) {
a.setPObjectReference(ref);
a.init();
}
// add any found annotations to the vector.
annotations.add(a);
} catch (IllegalStateException e) {
logger.warning("Malformed annotation could not be initialized. " +
a != null ? " " + a.getPObjectReference() + a.getEntries() : "");
}
}
}
}
@Override
public void setPObjectReference(Reference reference) {
super.setPObjectReference(reference); //To change body of overridden methods use File | Settings | File Templates.
}
/**
* Reset the pages initialized flag and as a result subsequent calls to
* this page may trigger a call to init().
*/
public void resetInitializedState() {
inited = false;
}
/**
* Initialize the Page object. This method triggers the parsing of a page's
* child elements. Once a page has been initialized, it can be painted.
*/
public synchronized void init() throws InterruptedException {
try {
// make sure we are not revisiting this method
if (inited) {
return;
}
pageInitialized = false;
// get pages resources
initPageResources();
// annotations
initPageAnnotations();
// Get the value of the page's content entry
initPageContents();
// send out loading event.
if (resources != null) {
imageCount = resources.getImageCount();
int contentCount = 0;
if (contents != null) {
contentCount = contents.size();
}
notifyPageLoadingStarted(contentCount, resources.getImageCount());
}
/**
* Finally iterate through the contents vector and concat all of the
* the resource streams together so that the content parser can
* go to town and build all of the page's shapes.
*/
notifyPageInitializationStarted();
if (contents != null) {
try {
ContentParser cp = ContentParserFactory.getInstance()
.getContentParser(library, resources);
byte[][] streams = new byte[contents.size()][];
byte[] stream;
for (int i = 0, max = contents.size(); i < max; i++) {
stream = contents.get(i).getDecodedStreamBytes();
if (stream != null) {
streams[i] = stream;
}
}
// get any optional groups from the catalog, which control
// visibility
OptionalContent optionalContent =
library.getCatalog().getOptionalContent();
if (optionalContent != null) {
optionalContent.init();
}
// pass in option group references into parse.
if (streams.length > 0) {
shapes = cp.parse(streams, this).getShapes();
}
// set the initiated flag, first as there are couple corner
// cases where the content parsing can call page.init() again
// from the same thread.
inited = true;
}catch(InterruptedException e){
throw new InterruptedException(e.getMessage());
} catch (Exception e) {
shapes = new Shapes();
logger.log(Level.WARNING, "Error initializing Page.", e);
}
}
// empty page, nothing to do.
else {
shapes = new Shapes();
logger.log(Level.WARNING, "Error initializing Page, no page content.");
}
} catch (InterruptedException e) {
// keeps shapes vector so we can paint what we have but make init state as false
// so we can try to re parse it later.
inited = false;
throw new InterruptedException(e.getMessage());
}
notifyPageInitializationEnded(inited);
}
/**
* Gets a Thumbnail object associated with this page. If no Thumbnail
* entry exists then null is returned.
*
* @return thumbnail object of this page, null if no thumbnail value is
* encoded.
*/
public Thumbnail getThumbnail() {
Object thumb = library.getObject(entries, THUMB_KEY);
if (thumb != null && thumb instanceof Stream) {
return new Thumbnail(library, entries);
} else {
return null;
}
}
public void requestInterrupt() {
if (shapes != null) {
shapes.interruptPaint();
}
}
/**
* Sets a page watermark implementation to be painted on top of the page
* content. Watermark can be specified for each page or once by calling
* document.setWatermark().
*
* @param watermarkCallback watermark implementation.
*/
public void setWatermarkCallback(WatermarkCallback watermarkCallback) {
this.watermarkCallback = watermarkCallback;
}
/**
* Paints the contents of this page to the graphics context using
* the specified rotation, zoom, rendering hints and page boundary.
*
* @param g graphics context to which the page content will be painted.
* @param renderHintType Constant specified by the GraphicsRenderingHints class.
* There are two possible entries, SCREEN and PRINT, each with configurable
* rendering hints settings.
* @param boundary Constant specifying the page boundary to use when
* painting the page content.
* @param userRotation Rotation factor, in degrees, to be applied to the rendered page
* @param userZoom Zoom factor to be applied to the rendered page
*/
public void paint(Graphics g, int renderHintType, final int boundary,
float userRotation, float userZoom) throws InterruptedException {
paint(g, renderHintType, boundary, userRotation, userZoom, true, true);
}
/**
* Paints the contents of this page to the graphics context using
* the specified rotation, zoom, rendering hints and page boundary.
*
* @param g graphics context to which the page content will be painted.
* @param renderHintType Constant specified by the GraphicsRenderingHints class.
* There are two possible entries, SCREEN and PRINT, each with configurable
* rendering hints settings.
* @param boundary Constant specifying the page boundary to use when
* painting the page content.
* @param userRotation Rotation factor, in degrees, to be applied to the rendered page
* @param userZoom Zoom factor to be applied to the rendered page
* @param paintAnnotations true enables the painting of page annotations. False
* paints no annotations for a given page.
* @param paintSearchHighlight true enables the painting of search highlight
* state of text object. The search controller can
* be used to easily search and add highlighted state
* for search terms.
*/
public void paint(Graphics g, int renderHintType, final int boundary,
float userRotation, float userZoom,
boolean paintAnnotations, boolean paintSearchHighlight) throws InterruptedException {
if (!inited) {
// make sure we don't do a page init on the awt thread in the viewer
// ri, let the
return;
}
Graphics2D g2 = (Graphics2D) g;
GraphicsRenderingHints grh = GraphicsRenderingHints.getDefault();
g2.setRenderingHints(grh.getRenderingHints(renderHintType));
AffineTransform at = getPageTransform(boundary, userRotation, userZoom);
g2.transform(at);
// Store graphic context state before page content is painted
AffineTransform prePagePaintState = g2.getTransform();
PRectangle pageBoundary = getPageBoundary(boundary);
float x = 0 - pageBoundary.x;
float y = 0 - (pageBoundary.y - pageBoundary.height);
// Draw the (typically white) background
Color backgroundColor = grh.getPageBackgroundColor(renderHintType);
if (backgroundColor != null) {
g2.setColor(backgroundColor);
g2.fillRect((int) (0 - x),
(int) (0 - y),
(int) pageBoundary.width,
(int) pageBoundary.height);
}
// We have to impose a page clip because some documents don't separate
// pages into separate Page objects, but instead reuse the Page object,
// but with a different clip
// And we can't stomp over the clip, because the PageView might be
// trying to only draw a portion of the page for performance, or
// other reasons
Rectangle2D rect = new Rectangle2D.Double(-x, -y, pageBoundary.width, pageBoundary.height);
Shape oldClip = g2.getClip();
if (oldClip == null) {
g2.setClip(rect);
} else {
Area area = new Area(oldClip);
area.intersect(new Area(rect));
g2.setClip(area);
}
paintPageContent(g2, renderHintType, userRotation, userZoom, paintAnnotations, paintSearchHighlight);
// one last repaint, just to be sure
notifyPaintPageListeners();
// apply old graphics context state, to more accurately paint water mark
g2.setTransform(prePagePaintState);
if (watermarkCallback != null) {
watermarkCallback.paintWatermark(g, this, renderHintType,
boundary, userRotation, userZoom);
}
}
/**
* Paints the contents of this page to the graphics context using
* the specified rotation, zoom, rendering hints.
* <br>
* The drawing commands that are issued on the given graphics context will use coordinates
* in PDF user coordinate space. It is the responsibility of the caller of this method
* to setup the graphics context to correctly interpret these coordinates.
*
* @param g graphics context to which the page content will be painted.
* @param renderHintType Constant specified by the GraphicsRenderingHints class.
* There are two possible entries, SCREEN and PRINT, each with configurable
* rendering hints settings.
* @param userRotation Rotation factor, in degrees, to be applied to the rendered page
* @param userZoom Zoom factor to be applied to the rendered page
* @param paintAnnotations true enables the painting of page annotations. False
* paints no annotations for a given page.
* @param paintSearchHighlight true enables the painting of search highlight
* state of text object. The search controller can
* be used to easily search and add highlighted state
* for search terms.
*/
public void paintPageContent(Graphics g, int renderHintType, float userRotation, float userZoom,
boolean paintAnnotations, boolean paintSearchHighlight) throws InterruptedException {
if (!inited) {
init();
}
paintPageContent(((Graphics2D) g), renderHintType, userRotation, userZoom, paintAnnotations, paintSearchHighlight);
}
private void paintPageContent(Graphics2D g2, int renderHintType, float userRotation, float userZoom,
boolean paintAnnotations, boolean paintSearchHighlight) throws InterruptedException {
// draw page content
if (shapes != null) {
pagePainted = false;
notifyPagePaintingStarted(shapes.getShapesCount());
AffineTransform pageTransform = g2.getTransform();
Shape pageClip = g2.getClip();
shapes.setPageParent(this);
shapes.paint(g2);
shapes.setPageParent(null);
g2.setTransform(pageTransform);
g2.setClip(pageClip);
} else {
notifyPagePaintingStarted(0);
}
// paint annotations if available and desired.
if (annotations != null && paintAnnotations) {
float totalRotation = getTotalRotation(userRotation);
int num = annotations.size();
Annotation annotation;
for (int i = 0; i < num; i++) {
annotation = annotations.get(i);
annotation.render(g2, renderHintType, totalRotation, userZoom, false);
}
}
// paint search highlight values
if (paintSearchHighlight) {
PageText pageText = getViewText();
if (pageText != null) {
//g2.setComposite(BlendComposite.getInstance(BlendComposite.BlendingMode.MULTIPLY, 1.0f));
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER,
SELECTION_ALPHA));
// paint the sprites
GeneralPath textPath;
// iterate over the data structure.
if (pageText.getPageLines() != null) {
for (LineText lineText : pageText.getPageLines()) {
if (lineText != null) {
for (WordText wordText : lineText.getWords()) {
// paint whole word
if (wordText.isHighlighted()) {
textPath = new GeneralPath(wordText.getBounds());
g2.setColor(highlightColor);
g2.fill(textPath);
} else {
for (GlyphText glyph : wordText.getGlyphs()) {
if (glyph.isHighlighted()) {
textPath = new GeneralPath(glyph.getBounds());
g2.setColor(highlightColor);
g2.fill(textPath);
}
}
}
}
}
}
}
//g2.setComposite(BlendComposite.getInstance(BlendComposite.BlendingMode.NORMAL, 1.0f));
}
}
pagePainted = true;
// painting is complete interrupted or not.
if (shapes != null) {
notifyPagePaintingEnded(shapes.isInterrupted());
} else {
notifyPagePaintingEnded(false);
}
// one last repaint, just to be sure
notifyPaintPageListeners();
// check image count if no images we are done.
if (imageCount == 0 || (pageInitialized && pagePainted)) {
notifyPageLoadingEnded();
}
}
/**
* The Java Graphics coordinate system has the origin at the top-left
* of the screen, with Y values increasing as one moves down the screen.
* The PDF coordinate system has the origin at the bottom-left of the
* document, with Y values increasing as one moved up the document.
* As well, PDFs can be displayed both rotated and zoomed.
* This method gives an AffineTransform which can be passed to
* java.awt.Graphics2D.transform(AffineTransform) so that one can then
* use that Graphics2D in the user-perspective PDF coordinate space.
*
* @param boundary Constant specifying the page boundary to use when
* painting the page content.
* @param userRotation Rotation factor, in degrees, to be applied to the rendered page
* @param userZoom Zoom factor to be applied to the rendered page
* @return AffineTransform for translating from the rotated and zoomed PDF
* coordinate system to the Java Graphics coordinate system
*/
public AffineTransform getPageTransform(final int boundary,
float userRotation,
float userZoom) {
AffineTransform at = new AffineTransform();
Rectangle2D.Double boundingBox = getBoundingBox(boundary, userRotation, userZoom);
at.translate(0, boundingBox.getHeight());
// setup canvas for PDF document orientation
at.scale(1, -1);
at.scale(userZoom, userZoom);
float totalRotation = getTotalRotation(userRotation);
PRectangle pageBoundary = getPageBoundary(boundary);
at = Page.getPageRotation(at, totalRotation, pageBoundary.width, pageBoundary.height);
// translate crop lower left corner back to where media box corner was
float x = 0 - pageBoundary.x;
float y = 0 - (pageBoundary.y - pageBoundary.height);
at.translate(x, y);
return at;
}
public static AffineTransform getPageRotation(AffineTransform at, double totalRotation, float width, float height){
if (totalRotation == 0) {
// do nothing
} else if (totalRotation == 90) {
at.translate(height, 0);
} else if (totalRotation == 180) {
at.translate(width, height);
} else if (totalRotation == 270) {
at.translate(0, width);
} else {
if (totalRotation > 0 && totalRotation < 90) {
double xShift = width * Math.cos(Math.toRadians(90 - totalRotation));
at.translate(xShift, 0);
} else if (totalRotation > 90 && totalRotation < 180) {
double rad = Math.toRadians(180 - totalRotation);
double cosRad = Math.cos(rad);
double sinRad = Math.sin(rad);
double xShift = height * sinRad + width * cosRad;
double yShift = height * cosRad;
at.translate(xShift, yShift);
} else if (totalRotation > 180 && totalRotation < 270) {
double rad = Math.toRadians(totalRotation - 180);
double cosRad = Math.cos(rad);
double sinRad = Math.sin(rad);
double xShift = width * cosRad;
double yShift = width * sinRad + height * cosRad;
at.translate(xShift, yShift);
} else if (totalRotation > 270 && totalRotation < 360) {
double yShift = width * Math.cos(Math.toRadians(totalRotation - 270));
at.translate(0, yShift);
}
}
// apply rotation on canvas, convert to Radians
at.rotate(totalRotation * Math.PI / 180.0);
return at;
}
/**
* This method returns a Shape that represents the outline of this Page,
* after being rotated and zoomed. It is used for clipping, and drawing
* borders around the page rendering onscreen.
*
* @param boundary Constant specifying the page boundary to use
* @param userRotation Rotation factor, in degrees, to be applied
* @param userZoom Zoom factor to be applied
* @return Shape outline of the rotated and zoomed portion of this Page
* corresponding to the specified boundary
*/
public Shape getPageShape(int boundary, float userRotation, float userZoom) {
AffineTransform at = getPageTransform(boundary, userRotation, userZoom);
PRectangle pageBoundary = getPageBoundary(boundary);
float x = 0 - pageBoundary.x;
float y = 0 - (pageBoundary.y - pageBoundary.height);
Rectangle2D rect = new Rectangle2D.Double(-x, -y, pageBoundary.width, pageBoundary.height);
GeneralPath path = new GeneralPath(rect);
return path.createTransformedShape(at);
}
/**
* Adds an annotation that was previously added to the document. It is
* assumed that the annotation has a valid object reference. This
* is commonly used with the undo/redo state manager in the RI. Use
* the method @link{#createAnnotation} for creating new annotations.
*
* @param newAnnotation annotation object to add
* @return reference to annotation that was added.
*/
@SuppressWarnings("unchecked")
public Annotation addAnnotation(Annotation newAnnotation) {
// make sure the page annotations have been initialized.
if (annotations == null) {
try {
initPageAnnotations();
} catch (InterruptedException e) {
logger.warning("Annotation Initialization interrupted");
}
}
StateManager stateManager = library.getStateManager();
List<Reference> annotations = library.getArray(entries, ANNOTS_KEY);
boolean isAnnotAReference = library.isReference(entries, ANNOTS_KEY);
// does the page not already have an annotations or if the annots
// dictionary is indirect. If so we have to add the page to the state
// manager
if (!isAnnotAReference && annotations != null) {
// get annots array from page
// update annots dictionary with new annotations reference,
annotations.add(newAnnotation.getPObjectReference());
// add the page as state change
stateManager.addChange(
new PObject(this, this.getPObjectReference()));
} else if (isAnnotAReference && annotations != null) {
// get annots array from page
// update annots dictionary with new annotations reference,
annotations.add(newAnnotation.getPObjectReference());
// add the annotations reference dictionary as state has changed
stateManager.addChange(
new PObject(annotations, library.getObjectReference(
entries, ANNOTS_KEY)));
}
// we need to add the a new annots reference
else {
List<Reference> annotsVector = new ArrayList(4);
annotsVector.add(newAnnotation.getPObjectReference());
// create a new Dictionary of annotations using an external reference
PObject annotsPObject = new PObject(annotsVector,
stateManager.getNewReferencNumber());
// add the new dictionary to the page
entries.put(ANNOTS_KEY, annotsPObject.getReference());
// add it to the library so we can resolve the reference
library.addObject(annotsVector, annotsPObject.getReference());
// add the page and the new dictionary to the state change
stateManager.addChange(
new PObject(this, this.getPObjectReference()));
stateManager.addChange(annotsPObject);
this.annotations = new ArrayList<Annotation>();
}
// update parent page reference.
newAnnotation.getEntries().put(Annotation.PARENT_PAGE_KEY,
this.getPObjectReference());
// add the annotations to the parsed annotations list
this.annotations.add(newAnnotation);
// add the new annotations to the library
library.addObject(newAnnotation, newAnnotation.getPObjectReference());
// finally add the new annotations to the state manager
stateManager.addChange(new PObject(newAnnotation, newAnnotation.getPObjectReference()));
// return to caller for further manipulations.
return newAnnotation;
}
/**
* Deletes the specified annotation instance from his page. If the
* annotation was original then either the page or the annot ref object
* is also added to the state manager. If the annotation was new then
* we just have to update the page and or annot reference as the objects
* will already be in the state manager.
*
* @param annot annotation to delete.
*/
public void deleteAnnotation(Annotation annot) {
// make sure the page annotations have been initialized.
if (annotations == null) {
try {
initPageAnnotations();
} catch (InterruptedException e) {
logger.warning("Annotation Initialization interupted");
}
}
StateManager stateManager = library.getStateManager();
Object annots = getObject(ANNOTS_KEY);
boolean isAnnotAReference =
library.isReference(entries, ANNOTS_KEY);
// mark the item as deleted so the state manager can clean up the reference.
annot.setDeleted(true);
Stream nAp = annot.getAppearanceStream();
if (nAp != null) {
nAp.setDeleted(true);
// find the xobjects font resources.
Object tmp = library.getObject(nAp.entries, RESOURCES_KEY);
if (tmp instanceof Resources) {
Resources resources = (Resources) tmp;
// only remove our font instance, if we remove another font we would have
// to check the document to see if it was used anywhere else.
Dictionary font = resources.getFont(FreeTextAnnotation.EMBEDDED_FONT_NAME);
if (font != null) {
font.setDeleted(true);
}
}
}
// check to see if this is an existing annotations, if the annotations
// is existing then we have to mark either the page or annot ref as changed.
if (!annot.isNew() && !isAnnotAReference) {
// add the page as state change
stateManager.addChange(
new PObject(this, this.getPObjectReference()));
}
// if not new and annot is a ref, we have to add annot ref as changed.
else if (!annot.isNew() && isAnnotAReference) {
stateManager.addChange(
new PObject(annots, library.getObjectReference(
entries, ANNOTS_KEY)));
}
// if new annotation, then we can remove it from the state manager.
else if (annot.isNew()) {
stateManager.removeChange(
new PObject(annot, annot.getPObjectReference()));
// check for an appearance stream which also needs to be removed.
if (nAp != null) {
stateManager.removeChange(new PObject(
nAp, nAp.getPObjectReference()));
library.removeObject(nAp.getPObjectReference());
}
}
// removed the annotations from the annots vector
if (annots instanceof List) {
// update annots dictionary with new annotations reference,
((List) annots).remove(annot.getPObjectReference());
}
// remove the annotations form the annotation cache in the page object
if (annotations != null) {
annotations.remove(annot);
}
// finally remove it from the library to free up the memory
library.removeObject(annot.getPObjectReference());
}
/**
* Updates the annotation associated with this page. If the annotation
* is not in this page then the annotation is no added.
*
* @param annotation annotation object that should be updated for this page.
* @return true if the update was successful, false otherwise.
*/
@SuppressWarnings("unchecked")
public boolean updateAnnotation(Annotation annotation) {
// bail on null annotations
if (annotation == null) {
return false;
}
// make sure the page annotations have been initialized.
if (annotations == null) {
try {
initPageAnnotations();
} catch (InterruptedException e) {
logger.warning("Annotation Initialization interrupted");
}
}
StateManager stateManager = library.getStateManager();
// if we are doing an update we have at least on annot
List<Object> annotations = (List)
library.getObject(entries, ANNOTS_KEY);
// make sure annotations is in part of page.
boolean found = false;
for (Object ref : annotations) {
if (ref.equals(annotation.getPObjectReference())) {
found = true;
break;
}
}
if (!found) {
return false;
}
// check the state manager for an instance of this object
if (stateManager.contains(annotation.getPObjectReference())) {
// if found we just have to re add the object, foot work around
// page and annotations creation has already been done.
stateManager.addChange(
new PObject(annotation, annotation.getPObjectReference()));
return true;
}
// we have to do the checks for page and annot dictionary entry.
else {
// update parent page reference.
annotation.getEntries().put(Annotation.PARENT_PAGE_KEY,
this.getPObjectReference());
// add the annotations to the parsed annotations list
this.annotations.add(annotation);
// add the new annotations to the library
library.addObject(annotation, annotation.getPObjectReference());
// finally add the new annotations to the state manager
stateManager.addChange(new PObject(annotation, annotation.getPObjectReference()));
return true;
}
}
/**
* Gets a reference to the page's parent page tree. A reference can be resolved
* by the Library class.
*
* @return reference to parent page tree.
* @see org.icepdf.core.util.Library
*/
protected Reference getParentReference() {
return (Reference) entries.get(PARENT_KEY);
}
/**
* Gets the page's parent page tree.
*
* @return parent page tree.
*/
public PageTree getParent() {
// retrieve a pointer to the pageTreeParent
Object tmp = library.getObject(entries, PARENT_KEY);
if (tmp instanceof PageTree) {
return (PageTree) tmp;
} else if (tmp instanceof HashMap) {
return new PageTree(library, (HashMap) tmp);
} else {
return null;
}
}
/**
* Get the width and height that the page can occupy, given the userRotation,
* page's own pageRotation and cropBox boundary. The page's default zoom of
* 1.0f is used.
*
* @param userRotation Rotation factor specified by the user under which the
* page will be rotated.
* @return Dimension of width and height of the page represented in point
* units.
* @see #getSize(float, float)
*/
public PDimension getSize(float userRotation) {
return getSize(BOUNDARY_CROPBOX, userRotation, 1.0f);
}
/**
* Get the width and height that the page can occupy, given the userRotation,
* userZoom, page's own pageRotation and cropBox boundary.
*
* @param userRotation rotation factor specified by the user under which the
* page will be rotated.
* @param userZoom zoom factor specified by the user under which the page will
* be rotated.
* @return Dimension of width and height of the page represented in point units.
*/
public PDimension getSize(float userRotation, float userZoom) {
return getSize(BOUNDARY_CROPBOX, userRotation, userZoom);
}
/**
* Get the width and height that the page can occupy, given the userRotation,
* userZoom, page's own pageRotation and cropBox boundary.
*
* @param boundary boundary constant to specify which boundary to respect when
* calculating the page's size.
* @param userRotation rotation factor specified by the user under which the
* page will be rotated.
* @param userZoom zoom factor specified by the user under which the page will
* be rotated.
* @return Dimension of width and height of the page represented in point units.
* or null if the boundary box is not defined.
*/
public PDimension getSize(final int boundary, float userRotation, float userZoom) {
float totalRotation = getTotalRotation(userRotation);
PRectangle pageBoundary = getPageBoundary(boundary);
float width = pageBoundary.width * userZoom;
float height = pageBoundary.height * userZoom;
// No rotation, or flipped upside down
if (totalRotation == 0 || totalRotation == 180) {
// Do nothing
}
// Rotated sideways
else if (totalRotation == 90 || totalRotation == 270) {
float temp = width;
// flip with and height.
width = height;
height = temp;
}
// Arbitrary rotation
else {
AffineTransform at = new AffineTransform();
double radians = Math.toRadians(totalRotation);
at.rotate(radians);
Rectangle2D.Double boundingBox = new Rectangle2D.Double(0.0, 0.0, 0.0, 0.0);
Point2D.Double src = new Point2D.Double();
Point2D.Double dst = new Point2D.Double();
src.setLocation(0.0, height); // Top left
at.transform(src, dst);
boundingBox.add(dst);
src.setLocation(width, height); // Top right
at.transform(src, dst);
boundingBox.add(dst);
src.setLocation(0.0, 0.0); // Bottom left
at.transform(src, dst);
boundingBox.add(dst);
src.setLocation(width, 0.0); // Bottom right
at.transform(src, dst);
boundingBox.add(dst);
width = (float) boundingBox.getWidth();
height = (float) boundingBox.getHeight();
}
return new PDimension(width, height);
}
/**
* Get the bounding box that the page can occupy, given the userRotation and
* page's own pageRotation. The boundary of BOUNDARY_CROPBOX, and the default
* zoom of 1.0f are assumed.
*
* @param userRotation Rotation factor specified by the user under which the
* page will be rotated.
* @return Dimension of width and height of the page represented in point
* units.
* @see #getSize(float, float)
*/
public Rectangle2D.Double getBoundingBox(float userRotation) {
return getBoundingBox(BOUNDARY_CROPBOX, userRotation, 1.0f);
}
/**
* Get the bounding box that the page can occupy, given the userRotation,
* userZoom, page's own pageRotation.
*
* @param userRotation rotation factor specified by the user under which the
* page will be rotated.
* @param userZoom zoom factor specified by the user under which the page will
* be rotated.
* @return Rectangle encompassing the page represented in point units.
*/
public Rectangle2D.Double getBoundingBox(float userRotation, float userZoom) {
return getBoundingBox(BOUNDARY_CROPBOX, userRotation, userZoom);
}
/**
* Get the bounding box that the page can occupy, given the userRotation,
* userZoom, page's own pageRotation and cropBox boundary.
*
* @param boundary boundary constant to specify which boundary to respect when
* calculating the page's size.
* @param userRotation rotation factor specified by the user under which the
* page will be rotated.
* @param userZoom zoom factor specified by the user under which the page will
* be rotated.
* @return Rectangle encompassing the page represented in point units.
*/
public Rectangle2D.Double getBoundingBox(final int boundary, float userRotation, float userZoom) {
float totalRotation = getTotalRotation(userRotation);
PRectangle pageBoundary = getPageBoundary(boundary);
float width = pageBoundary.width * userZoom;
float height = pageBoundary.height * userZoom;
AffineTransform at = new AffineTransform();
double radians = Math.toRadians(totalRotation);
at.rotate(radians);
Rectangle2D.Double boundingBox = new Rectangle2D.Double(0.0, 0.0, 0.0, 0.0);
Point2D.Double src = new Point2D.Double();
Point2D.Double dst = new Point2D.Double();
src.setLocation(0.0, height); // Top left
at.transform(src, dst);
boundingBox.add(dst);
src.setLocation(width, height); // Top right
at.transform(src, dst);
boundingBox.add(dst);
src.setLocation(0.0, 0.0); // Bottom left
at.transform(src, dst);
boundingBox.add(dst);
src.setLocation(width, 0.0); // Bottom right
at.transform(src, dst);
boundingBox.add(dst);
return boundingBox;
}
/**
* Utility method for applying the page boundary rules. If no matching specifiedBox type is found then
* the BOUNDARY_CROPBOX bound will be returned.
*
* @param specifiedBox page boundary constant
* @return bounds of page after the chain of rules have been applied.
*/
public PRectangle getPageBoundary(final int specifiedBox) {
PRectangle userSpecifiedBox;
// required property
if (specifiedBox == BOUNDARY_MEDIABOX) {
userSpecifiedBox = (PRectangle) getMediaBox();
}
// required property
else if (specifiedBox == BOUNDARY_CROPBOX) {
userSpecifiedBox = (PRectangle) getCropBox();
}
// optional, default value is crop box
else if (specifiedBox == BOUNDARY_BLEEDBOX) {
userSpecifiedBox = (PRectangle) getBleedBox();
}
// optional, default value is crop box
else if (specifiedBox == BOUNDARY_TRIMBOX) {
userSpecifiedBox = (PRectangle) getTrimBox();
}
// optional, default value is crop box
else if (specifiedBox == BOUNDARY_ARTBOX) {
userSpecifiedBox = (PRectangle) getArtBox();
}
// encase of bad usage, default to crop box
else {
userSpecifiedBox = (PRectangle) getCropBox();
}
// just in case, make sure we return a non null boundary, and the
// media box is marked as required and should be in either this dictionary
// or a parent's
if (userSpecifiedBox == null) {
userSpecifiedBox = (PRectangle) getMediaBox();
}
return userSpecifiedBox;
}
/**
* Returns a summary of the page dictionary entries.
*
* @return dictionary entries.
*/
public String toString() {
return "PAGE= " + entries.toString();
}
/**
* Gets the total rotation factor of the page after applying a user rotation
* factor. This method will normalize rotation factors to be in the range
* of 0 to 360 degrees.
*
* @param userRotation rotation factor to be applied to page
* @return Total Rotation, representing pageRotation + user rotation
* factor applied to the whole document.
*/
public float getTotalRotation(float userRotation) {
float totalRotation = getPageRotation() + userRotation;
// correct to keep in rotation in 360 range.
totalRotation %= 360;
if (totalRotation < 0)
totalRotation += 360;
// If they calculated the degrees from radians or whatever,
// then we need to make our even rotation comparisons work
if (totalRotation >= -0.001f && totalRotation <= 0.001f)
return 0.0f;
else if (totalRotation >= 89.99f && totalRotation <= 90.001f)
return 90.0f;
else if (totalRotation >= 179.99f && totalRotation <= 180.001f)
return 180.0f;
else if (totalRotation >= 269.99f && totalRotation <= 270.001f)
return 270.0f;
return totalRotation;
}
private float getPageRotation() {
// Get the pages default orientation if available, if not defined
// then it is zero.
Object tmpRotation = library.getObject(entries, ROTATE_KEY);
if (tmpRotation != null) {
pageRotation = ((Number) tmpRotation).floatValue();
// System.out.println("Page Rotation " + pageRotation);
}
// check parent to see if value has been set
else {
PageTree pageTree = getParent();
while (pageTree != null) {
if (pageTree.isRotationFactor) {
pageRotation = pageTree.rotationFactor;
break;
}
pageTree = pageTree.getParent();
}
}
// PDF specifies rotation as clockwise, but Java2D does it
// counter-clockwise, so normalise it to Java2D
pageRotation = 360 - pageRotation;
pageRotation %= 360;
// System.out.println("New Page Rotation " + pageRotation);
return pageRotation;
}
/**
* Gets all annotation information associated with this page. Each entry
* in the vector represents one annotation. The size of the vector represents
* the total number of annotations associated with the page.
*
* @return annotation associated with page; null, if there are no annotations.
*/
public List<Annotation> getAnnotations() {
if (!inited) {
try {
initPageAnnotations();
} catch (InterruptedException e) {
logger.finer("Interrupt exception getting annotations. ");
}
}
return annotations;
}
/**
* Returns the decoded content stream for this page instance. A page instance
* can have more then one content stream associated with it.
*
* @return An array of decoded content stream. Each index in the array
* represents one content stream. Null return and null String array
* values are possible.
*/
public String[] getDecodedContentSteam() {
// Some PDF's won't have any content for a given page.
try {
initPageContents();
if (contents == null) {
return null;
}
String[] decodedContentStream = new String[contents.size()];
int i = 0;
for (Stream stream : contents) {
InputStream input = stream.getDecodedByteArrayInputStream();
String content;
if (input instanceof SeekableInput) {
content = Utils.getContentFromSeekableInput((SeekableInput) input, false);
} else {
InputStream[] inArray = new InputStream[]{input};
content = Utils.getContentAndReplaceInputStream(inArray, false);
}
decodedContentStream[i] = content;
input.close();
i++;
}
return decodedContentStream;
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Error initializing page Contents.", e);
} catch (IOException e) {
logger.log(Level.SEVERE, "Error closing content stream");
}
return null;
}
/**
* Gets the media box boundary defined by this page. The media box is a
* required page entry and can be inherited from its parent page tree.
*
* @return media box boundary in user space units.
*/
public Rectangle2D.Float getMediaBox() {
if (mediaBox != null) {
return mediaBox;
}
// add all of the pages media box dimensions to a vector and process
List boxDimensions = (List) (library.getObject(entries, MEDIABOX_KEY));
if (boxDimensions != null) {
mediaBox = new PRectangle(boxDimensions);
}
// If mediaBox is null check with the parent pages, as media box is inheritable
if (mediaBox == null) {
PageTree pageTree = getParent();
while (pageTree != null && mediaBox == null) {
mediaBox = pageTree.getMediaBox();
if (mediaBox == null) {
pageTree = pageTree.getParent();
}
}
}
// last resort
if (mediaBox == null) {
mediaBox = new PRectangle(new Point.Float(0, 0), new Point.Float(612, 792));
}
return mediaBox;
}
/**
* Gets the crop box boundary defined by this page. The media box is a
* required page entry and can be inherited from its parent page tree.
*
* @return crop box boundary in user space units.
*/
public Rectangle2D.Float getCropBox() {
if (cropBox != null) {
return cropBox;
}
// add all of the pages crop box dimensions to a vector and process
List boxDimensions = (List) (library.getObject(entries, CROPBOX_KEY));
if (boxDimensions != null) {
cropBox = new PRectangle(boxDimensions);
}
// If cropbox is null check with the parent pages, as media box is inheritable
boolean isParentCropBox = false;
if (cropBox == null) {
PageTree pageTree = getParent();
while (pageTree != null && cropBox == null) {
if (pageTree.getCropBox() == null) {
break;
}
cropBox = pageTree.getCropBox();
if (cropBox != null) {
isParentCropBox = true;
}
pageTree = pageTree.getParent();
}
}
// Default value of the cropBox is the MediaBox if not set implicitly
PRectangle mediaBox = (PRectangle) getMediaBox();
if ((cropBox == null || isParentCropBox) && mediaBox != null) {
cropBox = (PRectangle) mediaBox.clone();
} else if (cropBox != null && mediaBox != null) {
// PDF 1.5 spec states that the media box should be intersected with the
// crop box to get the new box. But we only want to do this if the
// cropBox is not the same as the mediaBox
cropBox = mediaBox.createCartesianIntersection(cropBox);
}
return cropBox;
}
/**
* Gets the art box boundary defined by this page. The art box is a
* required page entry and can be inherited from its parent page tree.
*
* @return art box boundary in user space units.
*/
public Rectangle2D.Float getArtBox() {
if (artBox != null) {
return artBox;
}
// get the art box vector value
List boxDimensions = (List) (library.getObject(entries, ARTBOX_KEY));
if (boxDimensions != null) {
artBox = new PRectangle(boxDimensions);
}
// Default value of the artBox is the bleed if not set implicitly
if (artBox == null) {
artBox = (PRectangle) getCropBox();
}
return artBox;
}
/**
* Gets the bleed box boundary defined by this page. The bleed box is a
* required page entry and can be inherited from its parent page tree.
*
* @return bleed box boundary in user space units.
*/
public Rectangle2D.Float getBleedBox() {
if (bleedBox != null) {
return bleedBox;
}
// get the art box vector value
List boxDimensions = (List) (library.getObject(entries, BLEEDBOX_KEY));
if (boxDimensions != null) {
bleedBox = new PRectangle(boxDimensions);
// System.out.println("Page - BleedBox " + bleedBox);
}
// Default value of the bleedBox is the bleed if not set implicitly
if (bleedBox == null) {
bleedBox = (PRectangle) getCropBox();
}
return bleedBox;
}
/**
* Gets the trim box boundary defined by this page. The trim box is a
* required page entry and can be inherited from its parent page tree.
*
* @return trim box boundary in user space units.
*/
public Rectangle2D.Float getTrimBox() {
if (trimBox != null) {
return trimBox;
}
// get the art box vector value
List boxDimensions = (List) (library.getObject(entries, TRIMBOX_KEY));
if (boxDimensions != null) {
trimBox = new PRectangle(boxDimensions);
// System.out.println("Page - TrimBox " + trimBox);
}
// Default value of the trimBox is the bleed if not set implicitly
if (trimBox == null) {
trimBox = (PRectangle) getCropBox();
}
return trimBox;
}
/**
* Gest the PageText data structure for this page. PageText is made up
* of lines, words and glyphs which can be used for searches, text extraction
* and text highlighting. The coordinates system has been normalized
* to page space.
*
* @return list of text sprites for the given page.
*/
public PageText getViewText() throws InterruptedException {
if (!inited) {
init();
}
if (shapes != null) {
return shapes.getPageText();
} else {
return null;
}
}
/**
* Gets the Shapes object associated with this Page. The return value can be
* null depending on the PDF encoding. The init() method should be called to
* insure the the page parsing and resource loading has completed. This method
* will not call init() if the page has not yet be initialized.
*
* @return shapes object associated with this Page, can be null.
*/
public Shapes getShapes() {
return shapes;
}
/**
* Gets the PageText data structure for this page using an accelerated
* parsing technique that ignores some text elements. This method should
* be used for straight text extraction.
*
* @return vector of Strings of all text objects inside the specified page.
*/
public synchronized PageText getText() throws InterruptedException {
// we only do this once per page
if (inited) {
if (shapes != null && shapes.getPageText() != null) {
return shapes.getPageText();
}
}
Shapes textBlockShapes = new Shapes();
/**
* Finally iterate through the contents vector and concat all of the
* the resouse streams together so that the contant parser can
* go to town and build all of the pages shapes.
*/
if (contents == null) {
// Get the value of the page's content entry
initPageContents();
}
if (resources == null) {
// get pages resources
initPageResources();
}
if (contents != null) {
try {
ContentParser cp = ContentParserFactory.getInstance()
.getContentParser(library, resources);
byte[][] streams = new byte[contents.size()][];
for (int i = 0, max = contents.size(); i < max; i++) {
streams[i] = contents.get(i).getDecodedStreamBytes();
}
textBlockShapes = cp.parseTextBlocks(streams);
// print off any fuzz left on the stack
if (logger.isLoggable(Level.FINER)) {
Stack<Object> stack = cp.getStack();
while (!stack.isEmpty()) {
String tmp = stack.pop().toString();
if (logger.isLoggable(Level.FINE)) {
logger.fine("STACK=" + tmp);
}
}
}
} catch (Exception e) {
logger.log(Level.FINE, "Error getting page text.", e);
}
}
if (textBlockShapes.getPageText() != null) {
return textBlockShapes.getPageText();
} else {
return null;
}
}
/**
* Gets the zero based page index of this page as define by the order
* in the page tree. This does not correspond to a page's label name.
*
* @return zero base page index.
*/
public int getPageIndex() {
return pageIndex;
}
/**
* Gets the xObject image found for this page which does not include
* any inline images.
*
* @return xObject image count.
*/
public int getImageCount() {
return imageCount;
}
/**
* Returns true if the page is initialized, this is different then init(),
* as it tracks if the page has started initialization and we don't want to
* do that again, in this case the init() method has completely finished,
* minus any image loading threads.
*
* @return true if page has completed initialization otherwise false.
*/
public boolean isPageInitialized() {
return pageInitialized;
}
/**
* Returns true if the page painting is complete regardless if it was
* interrupted.
*
* @return true if the page painting is complete.
*/
public boolean isPagePainted() {
return pagePainted;
}
protected void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}
/**
* Gets a vector of Images where each index represents an image inside
* this page.
*
* @return vector of Images inside the current page
*/
public synchronized List<Image> getImages() throws InterruptedException {
if (!inited) {
init();
}
return shapes.getImages();
}
public Resources getResources() {
return resources;
}
public void addPaintPageListener(PaintPageListener listener) {
// add a listener if it is not already registered
synchronized (paintPageListeners) {
if (!paintPageListeners.contains(listener)) {
paintPageListeners.add(listener);
}
}
}
public void removePaintPageListener(PaintPageListener listener) {
// remove a listener if it is already registered
synchronized (paintPageListeners) {
if (paintPageListeners.contains(listener)) {
paintPageListeners.remove(listener);
}
}
}
public List<PageLoadingListener> getPageLoadingListeners() {
return pageLoadingListeners;
}
public void addPageProcessingListener(PageLoadingListener listener) {
// add a listener if it is not already registered
synchronized (pageLoadingListeners) {
if (!pageLoadingListeners.contains(listener)) {
pageLoadingListeners.add(listener);
}
}
}
public void removePageProcessingListener(PageLoadingListener listener) {
// remove a listener if it is already registered
synchronized (pageLoadingListeners) {
if (pageLoadingListeners.contains(listener)) {
pageLoadingListeners.remove(listener);
}
}
}
private void notifyPageLoadingStarted(int contentCount, int imageCount) {
PageLoadingEvent pageLoadingEvent =
new PageLoadingEvent(this, contentCount, imageCount);
PageLoadingListener client;
for (int i = pageLoadingListeners.size() - 1; i >= 0; i--) {
client = pageLoadingListeners.get(i);
client.pageLoadingStarted(pageLoadingEvent);
}
}
private void notifyPageInitializationStarted() {
PageInitializingEvent pageLoadingEvent =
new PageInitializingEvent(this, false);
PageLoadingListener client;
for (int i = pageLoadingListeners.size() - 1; i >= 0; i--) {
client = pageLoadingListeners.get(i);
client.pageInitializationStarted(pageLoadingEvent);
}
}
private void notifyPagePaintingStarted(int shapesCount) {
PagePaintingEvent pageLoadingEvent =
new PagePaintingEvent(this, shapesCount);
PageLoadingListener client;
for (int i = pageLoadingListeners.size() - 1; i >= 0; i--) {
client = pageLoadingListeners.get(i);
client.pagePaintingStarted(pageLoadingEvent);
}
}
private void notifyPagePaintingEnded(boolean interrupted) {
pagePainted = true;
PagePaintingEvent pageLoadingEvent =
new PagePaintingEvent(this, interrupted);
PageLoadingListener client;
for (int i = pageLoadingListeners.size() - 1; i >= 0; i--) {
client = pageLoadingListeners.get(i);
client.pagePaintingEnded(pageLoadingEvent);
}
}
private void notifyPageInitializationEnded(boolean interrupted) {
pageInitialized = true;
PageInitializingEvent pageLoadingEvent =
new PageInitializingEvent(this, interrupted);
PageLoadingListener client;
for (int i = pageLoadingListeners.size() - 1; i >= 0; i--) {
client = pageLoadingListeners.get(i);
client.pageInitializationEnded(pageLoadingEvent);
}
}
protected void notifyPageLoadingEnded() {
PageLoadingEvent pageLoadingEvent =
new PageLoadingEvent(this, inited);
PageLoadingListener client;
for (int i = pageLoadingListeners.size() - 1; i >= 0; i--) {
client = pageLoadingListeners.get(i);
client.pageLoadingEnded(pageLoadingEvent);
}
}
public void notifyPaintPageListeners() {
// create the event object
PaintPageEvent evt = new PaintPageEvent(this);
// fire the event to all listeners
PaintPageListener client;
for (int i = paintPageListeners.size() - 1; i >= 0; i--) {
client = paintPageListeners.get(i);
client.paintPage(evt);
}
}
}