package com.tom_roush.pdfbox.pdmodel;
import android.util.Log;
import com.tom_roush.pdfbox.contentstream.PDContentStream;
import com.tom_roush.pdfbox.cos.COSArray;
import com.tom_roush.pdfbox.cos.COSBase;
import com.tom_roush.pdfbox.cos.COSDictionary;
import com.tom_roush.pdfbox.cos.COSFloat;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.cos.COSNumber;
import com.tom_roush.pdfbox.cos.COSStream;
import com.tom_roush.pdfbox.pdmodel.common.COSArrayList;
import com.tom_roush.pdfbox.pdmodel.common.COSObjectable;
import com.tom_roush.pdfbox.pdmodel.common.PDMetadata;
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle;
import com.tom_roush.pdfbox.pdmodel.common.PDStream;
import com.tom_roush.pdfbox.pdmodel.interactive.action.PDPageAdditionalActions;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import com.tom_roush.pdfbox.pdmodel.interactive.pagenavigation.PDThreadBead;
import com.tom_roush.pdfbox.pdmodel.interactive.pagenavigation.PDTransition;
import com.tom_roush.pdfbox.util.Matrix;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A page in a PDF document.
*
* @author Ben Litchfield
*/
public class PDPage implements COSObjectable, PDContentStream
{
private final COSDictionary page;
private PDResources pageResources;
private ResourceCache resourceCache;
private PDRectangle mediaBox;
/**
* Creates a new PDPage instance for embedding, with a size of U.S. Letter (8.5 x 11 inches).
*/
public PDPage()
{
this(PDRectangle.LETTER);
}
/**
* Creates a new instance of PDPage for embedding.
*
* @param mediaBox The MediaBox of the page.
*/
public PDPage(PDRectangle mediaBox)
{
page = new COSDictionary();
page.setItem(COSName.TYPE, COSName.PAGE);
page.setItem(COSName.MEDIA_BOX, mediaBox);
}
/**
* Creates a new instance of PDPage for reading.
*
* @param pageDictionary A page dictionary in a PDF document.
*/
public PDPage(COSDictionary pageDictionary)
{
page = pageDictionary;
}
/**
* Creates a new instance of PDPage for reading.
*
* @param pageDictionary A page dictionary in a PDF document.
*/
PDPage(COSDictionary pageDictionary, ResourceCache resourceCache)
{
page = pageDictionary;
this.resourceCache = resourceCache;
}
/**
* Convert this standard java object to a COS object.
*
* @return The cos object that matches this Java object.
*/
@Override
public COSDictionary getCOSObject()
{
return page;
}
/**
* Returns the content streams which make up this page.
*
* @return content stream iterator
*/
public Iterator<PDStream> getContentStreams()
{
List<PDStream> streams = new ArrayList<PDStream>();
COSBase base = page.getDictionaryObject(COSName.CONTENTS);
if (base instanceof COSStream)
{
streams.add(new PDStream((COSStream) base));
}
else if (base instanceof COSArray && ((COSArray) base).size() > 0)
{
COSArray array = (COSArray) base;
for (int i = 0; i < streams.size(); i++)
{
COSStream stream = (COSStream) array.getObject(i);
streams.add(new PDStream(stream));
}
}
return streams.iterator();
}
@Override
public InputStream getContents() throws IOException
{
COSBase base = page.getDictionaryObject(COSName.CONTENTS);
if (base instanceof COSStream)
{
return ((COSStream) base).getUnfilteredStream();
}
else if (base instanceof COSArray && ((COSArray)base).size() > 0)
{
COSArray streams = (COSArray) base;
byte[] delimiter = new byte[]{'\n'};
List<InputStream> inputStreams = new ArrayList<InputStream>();
for (int i = 0; i < streams.size(); i++)
{
COSStream stream = (COSStream) streams.getObject(i);
inputStreams.add(stream.getUnfilteredStream());
inputStreams.add(new ByteArrayInputStream(delimiter));
}
return new SequenceInputStream(Collections.enumeration(inputStreams));
}
return null;
}
/**
* Returns true if this page has contents.
*/
public boolean hasContents()
{
COSBase contents = page.getDictionaryObject(COSName.CONTENTS);
if (contents instanceof COSStream)
{
return ((COSStream) contents).size() > 0;
}
else if (contents instanceof COSArray)
{
return ((COSArray) contents).size() > 0;
}
return false;
}
/**
* A dictionary containing any resources required by the page.
*/
@Override
public PDResources getResources()
{
if (pageResources == null)
{
COSDictionary resources = (COSDictionary)
PDPageTree.getInheritableAttribute(page, COSName.RESOURCES);
// note: it's an error for resources to not be present
if (resources != null)
{
pageResources = new PDResources(resources, resourceCache);
}
}
return pageResources;
}
/**
* This will set the resources for this page.
*
* @param resources The new resources for this page.
*/
public void setResources(PDResources resources)
{
pageResources = resources;
if (resources != null)
{
page.setItem(COSName.RESOURCES, resources);
}
else
{
page.removeItem(COSName.RESOURCES);
}
}
/**
* This will get the key of this Page in the structural parent tree.
*
* @return the integer key of the page's entry in the structural parent tree
*/
public int getStructParents()
{
return page.getInt(COSName.STRUCT_PARENTS, 0);
}
@Override
public PDRectangle getBBox()
{
return getCropBox();
}
/**
* This will set the key for this page in the structural parent tree.
*
* @param structParents The new key for this page.
*/
public void setStructParents(int structParents)
{
page.setInt(COSName.STRUCT_PARENTS, structParents);
}
/**
* A rectangle, expressed in default user space units, defining the boundaries of the physical
* medium on which the page is intended to be displayed or printed.
*/
public PDRectangle getMediaBox()
{
if (mediaBox == null)
{
COSArray array = (COSArray) PDPageTree.getInheritableAttribute(page, COSName.MEDIA_BOX);
if (array != null)
{
mediaBox = new PDRectangle(array);
}
}
if (mediaBox == null)
{
Log.d("PdfBox-Android", "Can't find MediaBox, will use U.S. Letter");
mediaBox = PDRectangle.LETTER;
}
return mediaBox;
}
/**
* This will set the mediaBox for this page.
*
* @param mediaBox The new mediaBox for this page.
*/
public void setMediaBox(PDRectangle mediaBox)
{
this.mediaBox = mediaBox;
if (mediaBox == null)
{
page.removeItem(COSName.MEDIA_BOX);
}
else
{
page.setItem(COSName.MEDIA_BOX, mediaBox.getCOSArray());
}
}
/**
* A rectangle, expressed in default user space units, defining the visible region of default
* user space. When the page is displayed or printed, its contents are to be clipped (cropped)
* to this rectangle.
*/
public PDRectangle getCropBox()
{
COSArray array = (COSArray) PDPageTree.getInheritableAttribute(page, COSName.CROP_BOX);
if (array != null)
{
return clipToMediaBox(new PDRectangle(array));
}
else
{
return getMediaBox();
}
}
@Override
public Matrix getMatrix() {
// todo: take into account user-space unit redefinition as scale?
return new Matrix();
}
/**
* This will set the CropBox for this page.
*
* @param cropBox The new CropBox for this page.
*/
public void setCropBox(PDRectangle cropBox)
{
if (cropBox == null)
{
page.removeItem(COSName.CROP_BOX);
}
else
{
page.setItem(COSName.CROP_BOX, cropBox.getCOSArray());
}
}
/**
* A rectangle, expressed in default user space units, defining the region to which the contents
* of the page should be clipped when output in a production environment. The default is the
* CropBox.
*
* @return The BleedBox attribute.
*/
public PDRectangle getBleedBox()
{
PDRectangle retval;
COSArray array = (COSArray) page.getDictionaryObject(COSName.BLEED_BOX);
if (array != null)
{
retval = new PDRectangle(array);
}
else
{
retval = clipToMediaBox(new PDRectangle(array));
}
return retval;
}
/**
* This will set the BleedBox for this page.
*
* @param bleedBox The new BleedBox for this page.
*/
public void setBleedBox(PDRectangle bleedBox)
{
if (bleedBox == null)
{
page.removeItem(COSName.BLEED_BOX);
}
else
{
page.setItem(COSName.BLEED_BOX, bleedBox);
}
}
/**
* A rectangle, expressed in default user space units, defining the intended dimensions of the
* finished page after trimming. The default is the CropBox.
*
* @return The TrimBox attribute.
*/
public PDRectangle getTrimBox()
{
PDRectangle retval;
COSArray array = (COSArray) page.getDictionaryObject(COSName.TRIM_BOX);
if (array != null)
{
retval = clipToMediaBox(new PDRectangle(array));
}
else
{
retval = getCropBox();
}
return retval;
}
/**
* This will set the TrimBox for this page.
*
* @param trimBox The new TrimBox for this page.
*/
public void setTrimBox(PDRectangle trimBox)
{
if (trimBox == null)
{
page.removeItem(COSName.TRIM_BOX);
}
else
{
page.setItem(COSName.TRIM_BOX, trimBox);
}
}
/**
* A rectangle, expressed in default user space units, defining the extent of the page's
* meaningful content (including potential white space) as intended by the page's creator The
* default is the CropBox.
*
* @return The ArtBox attribute.
*/
public PDRectangle getArtBox()
{
PDRectangle retval;
COSArray array = (COSArray) page.getDictionaryObject(COSName.ART_BOX);
if (array != null)
{
retval = clipToMediaBox(new PDRectangle(array));
}
else
{
retval = getCropBox();
}
return retval;
}
/**
* This will set the ArtBox for this page.
*
* @param artBox The new ArtBox for this page.
*/
public void setArtBox(PDRectangle artBox)
{
if (artBox == null)
{
page.removeItem(COSName.ART_BOX);
}
else
{
page.setItem(COSName.ART_BOX, artBox);
}
}
/**
* Clips the given box to the bounds of the media box.
*/
private PDRectangle clipToMediaBox(PDRectangle box)
{
PDRectangle mediaBox = getMediaBox();
PDRectangle result = new PDRectangle();
result.setLowerLeftX(Math.max(mediaBox.getLowerLeftX(), box.getLowerLeftX()));
result.setLowerLeftY(Math.max(mediaBox.getLowerLeftY(), box.getLowerLeftY()));
result.setUpperRightX(Math.min(mediaBox.getUpperRightX(), box.getUpperRightX()));
result.setUpperRightY(Math.min(mediaBox.getUpperRightY(), box.getUpperRightY()));
return result;
}
/**
* Returns the rotation angle in degrees by which the page should be rotated
* clockwise when displayed or printed. Valid values in a PDF must be a
* multiple of 90.
*
* @return The rotation angle in degrees in normalized form (0, 90, 180 or
* 270) or 0 if invalid or not set at this level.
*/
public int getRotation()
{
COSBase obj = PDPageTree.getInheritableAttribute(page, COSName.ROTATE);
if (obj instanceof COSNumber)
{
int rotationAngle = ((COSNumber) obj).intValue();
if (rotationAngle % 90 == 0)
{
return (rotationAngle % 360 + 360) % 360;
}
}
return 0;
}
/**
* This will set the rotation for this page.
*
* @param rotation The new rotation for this page in degrees.
*/
public void setRotation(int rotation)
{
page.setInt(COSName.ROTATE, rotation);
}
/**
* This will set the contents of this page.
*
* @param contents The new contents of the page.
*/
public void setContents(PDStream contents)
{
page.setItem(COSName.CONTENTS, contents);
}
/**
* This will set the contents of this page.
*
* @param contents Array of new contents of the page.
*/
public void setContents(List<PDStream> contents)
{
COSArray array = new COSArray();
for (PDStream stream : contents)
{
array.add(stream);
}
page.setItem(COSName.CONTENTS, array);
}
/**
* This will get a list of PDThreadBead objects, which are article threads in the document.
* This will return an empty list of there are no thread beads.
*
* @return A list of article threads on this page.
*/
public List<PDThreadBead> getThreadBeads()
{
COSArray beads = (COSArray) page.getDictionaryObject(COSName.B);
if (beads == null)
{
beads = new COSArray();
}
List<PDThreadBead> pdObjects = new ArrayList<PDThreadBead>();
for (int i = 0; i < beads.size(); i++)
{
COSDictionary beadDic = (COSDictionary) beads.getObject(i);
PDThreadBead bead = null;
// in some cases the bead is null
if (beadDic != null)
{
bead = new PDThreadBead(beadDic);
}
pdObjects.add(bead);
}
return new COSArrayList<PDThreadBead>(pdObjects, beads);
}
/**
* This will set the list of thread beads.
*
* @param beads A list of PDThreadBead objects or null.
*/
public void setThreadBeads(List<PDThreadBead> beads)
{
page.setItem(COSName.B, COSArrayList.converterToCOSArray(beads));
}
/**
* Get the metadata that is part of the document catalog. This will return null if there is no meta data for this
* object.
*
* @return The metadata for this object.
*/
public PDMetadata getMetadata()
{
PDMetadata retval = null;
COSStream stream = (COSStream) page.getDictionaryObject(COSName.METADATA);
if (stream != null)
{
retval = new PDMetadata(stream);
}
return retval;
}
/**
* Set the metadata for this object. This can be null.
*
* @param meta The meta data for this object.
*/
public void setMetadata(PDMetadata meta)
{
page.setItem(COSName.METADATA, meta);
}
/**
* Get the page actions.
*
* @return The Actions for this Page
*/
public PDPageAdditionalActions getActions()
{
COSDictionary addAct = (COSDictionary) page.getDictionaryObject(COSName.AA);
if (addAct == null)
{
addAct = new COSDictionary();
page.setItem(COSName.AA, addAct);
}
return new PDPageAdditionalActions(addAct);
}
/**
* Set the page actions.
*
* @param actions The actions for the page.
*/
public void setActions(PDPageAdditionalActions actions)
{
page.setItem(COSName.AA, actions);
}
/**
* @return The page transition associated with this page or null if no transition is defined
*/
public PDTransition getTransition()
{
COSDictionary transitionDictionary = (COSDictionary) page.getDictionaryObject(COSName.TRANS);
return transitionDictionary == null ? null : new PDTransition(transitionDictionary);
}
/**
* @param transition The new transition to set on this page.
*/
public void setTransition(PDTransition transition)
{
page.setItem(COSName.TRANS, transition);
}
/**
* Convenient method to set a transition and the display duration
*
* @param transition The new transition to set on this page.
* @param duration The maximum length of time, in seconds, that the page shall be displayed during presentations
* before the viewer application shall automatically advance to the next page.
*/
public void setTransition(PDTransition transition, float duration)
{
page.setItem(COSName.TRANS, transition);
page.setItem(COSName.DUR, new COSFloat(duration));
}
/**
* This will return a list of the Annotations for this page.
*
* @return List of the PDAnnotation objects, never null.
* @throws IOException If there is an error while creating the annotation list.
*/
public List<PDAnnotation> getAnnotations() throws IOException
{
COSArrayList<PDAnnotation> retval;
COSArray annots = (COSArray) page.getDictionaryObject(COSName.ANNOTS);
if (annots == null)
{
annots = new COSArray();
page.setItem(COSName.ANNOTS, annots);
retval = new COSArrayList<PDAnnotation>(new ArrayList<PDAnnotation>(), annots);
}
else
{
List<PDAnnotation> actuals = new ArrayList<PDAnnotation>();
for (int i = 0; i < annots.size(); i++)
{
COSBase item = annots.getObject(i);
if (item == null)
{
continue;
}
actuals.add(PDAnnotation.createAnnotation(item));
}
retval = new COSArrayList<PDAnnotation>(actuals, annots);
}
return retval;
}
/**
* This will set the list of annotations.
*
* @param annotations The new list of annotations.
*/
public void setAnnotations(List<PDAnnotation> annotations)
{
page.setItem(COSName.ANNOTS, COSArrayList.converterToCOSArray(annotations));
}
@Override
public boolean equals(Object other)
{
return other instanceof PDPage && ((PDPage) other).getCOSObject() == this.getCOSObject();
}
@Override
public int hashCode()
{
return page.hashCode();
}
/**
* Returns the resource cache associated with this page, or null if there is none.
*/
public ResourceCache getResourceCache()
{
return resourceCache;
}
}