001: /**
002: * Copyright (c) 2003-2006, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdmodel;
031:
032: import org.pdfbox.cos.COSArray;
033: import org.pdfbox.cos.COSBase;
034: import org.pdfbox.cos.COSDictionary;
035: import org.pdfbox.cos.COSInteger;
036: import org.pdfbox.cos.COSName;
037: import org.pdfbox.cos.COSNumber;
038: import org.pdfbox.cos.COSStream;
039:
040: import org.pdfbox.pdfviewer.PageDrawer;
041: import org.pdfbox.pdmodel.common.COSArrayList;
042: import org.pdfbox.pdmodel.common.COSObjectable;
043: import org.pdfbox.pdmodel.common.PDMetadata;
044: import org.pdfbox.pdmodel.common.PDRectangle;
045: import org.pdfbox.pdmodel.common.PDStream;
046: import org.pdfbox.pdmodel.interactive.action.PDPageAdditionalActions;
047: import org.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
048: import org.pdfbox.pdmodel.interactive.pagenavigation.PDThreadBead;
049:
050: import java.awt.Color;
051: import java.awt.Dimension;
052: import java.awt.Graphics;
053: import java.awt.Graphics2D;
054: import java.awt.image.BufferedImage;
055: import java.awt.print.PageFormat;
056: import java.awt.print.Printable;
057: import java.awt.print.PrinterException;
058: import java.awt.print.PrinterIOException;
059: import java.io.IOException;
060:
061: import java.util.ArrayList;
062: import java.util.Calendar;
063: import java.util.GregorianCalendar;
064: import java.util.List;
065:
066: /**
067: * This represents a single page in a PDF document.
068: *
069: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
070: * @version $Revision: 1.28 $
071: */
072: public class PDPage implements COSObjectable, Printable {
073: private COSDictionary page;
074:
075: /**
076: * A page size of LETTER or 8.5x11.
077: */
078: public static final PDRectangle PAGE_SIZE_LETTER = new PDRectangle(
079: 612, 792);
080:
081: /**
082: * Creates a new instance of PDPage with a size of 8.5x11.
083: */
084: public PDPage() {
085: page = new COSDictionary();
086: page.setItem(COSName.TYPE, COSName.PAGE);
087: setMediaBox(PAGE_SIZE_LETTER);
088: }
089:
090: /**
091: * Creates a new instance of PDPage.
092: *
093: * @param pageDic The existing page dictionary.
094: */
095: public PDPage(COSDictionary pageDic) {
096: page = pageDic;
097: }
098:
099: /**
100: * Convert this standard java object to a COS object.
101: *
102: * @return The cos object that matches this Java object.
103: */
104: public COSBase getCOSObject() {
105: return page;
106: }
107:
108: /**
109: * This will get the underlying dictionary that this class acts on.
110: *
111: * @return The underlying dictionary for this class.
112: */
113: public COSDictionary getCOSDictionary() {
114: return page;
115: }
116:
117: /**
118: * This is the parent page node. The parent is a required element of the
119: * page. This will be null until this page is added to the document.
120: *
121: * @return The parent to this page.
122: */
123: public PDPageNode getParent() {
124: PDPageNode parent = null;
125: COSDictionary parentDic = (COSDictionary) page
126: .getDictionaryObject("Parent", "P");
127: if (parentDic != null) {
128: parent = new PDPageNode(parentDic);
129: }
130: return parent;
131: }
132:
133: /**
134: * This will set the parent of this page.
135: *
136: * @param parent The parent to this page node.
137: */
138: public void setParent(PDPageNode parent) {
139: page.setItem(COSName.PARENT, parent.getDictionary());
140: }
141:
142: /**
143: * This will update the last modified time for the page object.
144: */
145: public void updateLastModified() {
146: page.setDate("LastModified", new GregorianCalendar());
147: }
148:
149: /**
150: * This will get the date that the content stream was last modified. This
151: * may return null.
152: *
153: * @return The date the content stream was last modified.
154: *
155: * @throws IOException If there is an error accessing the date information.
156: */
157: public Calendar getLastModified() throws IOException {
158: return page.getDate("LastModified");
159: }
160:
161: /**
162: * This will get the resources at this page and not look up the hierarchy.
163: * This attribute is inheritable, and findResources() should probably used.
164: * This will return null if no resources are available at this level.
165: *
166: * @return The resources at this level in the hierarchy.
167: */
168: public PDResources getResources() {
169: PDResources retval = null;
170: COSDictionary resources = (COSDictionary) page
171: .getDictionaryObject(COSName.RESOURCES);
172: if (resources != null) {
173: retval = new PDResources(resources);
174: }
175: return retval;
176: }
177:
178: /**
179: * This will find the resources for this page by looking up the hierarchy until
180: * it finds them.
181: *
182: * @return The resources at this level in the hierarchy.
183: */
184: public PDResources findResources() {
185: PDResources retval = getResources();
186: PDPageNode parent = getParent();
187: if (retval == null && parent != null) {
188: retval = parent.findResources();
189: }
190: return retval;
191: }
192:
193: /**
194: * This will set the resources for this page.
195: *
196: * @param resources The new resources for this page.
197: */
198: public void setResources(PDResources resources) {
199: page.setItem(COSName.RESOURCES, resources);
200: }
201:
202: /**
203: * A rectangle, expressed
204: * in default user space units, defining the boundaries of the physical
205: * medium on which the page is intended to be displayed or printed
206: *
207: * This will get the MediaBox at this page and not look up the hierarchy.
208: * This attribute is inheritable, and findMediaBox() should probably used.
209: * This will return null if no MediaBox are available at this level.
210: *
211: * @return The MediaBox at this level in the hierarchy.
212: */
213: public PDRectangle getMediaBox() {
214: PDRectangle retval = null;
215: COSArray array = (COSArray) page
216: .getDictionaryObject(COSName.MEDIA_BOX);
217: if (array != null) {
218: retval = new PDRectangle(array);
219: }
220: return retval;
221: }
222:
223: /**
224: * This will find the MediaBox for this page by looking up the hierarchy until
225: * it finds them.
226: *
227: * @return The MediaBox at this level in the hierarchy.
228: */
229: public PDRectangle findMediaBox() {
230: PDRectangle retval = getMediaBox();
231: PDPageNode parent = getParent();
232: if (retval == null && parent != null) {
233: retval = parent.findMediaBox();
234: }
235: return retval;
236: }
237:
238: /**
239: * This will set the mediaBox for this page.
240: *
241: * @param mediaBox The new mediaBox for this page.
242: */
243: public void setMediaBox(PDRectangle mediaBox) {
244: if (mediaBox == null) {
245: page.removeItem(COSName.MEDIA_BOX);
246: } else {
247: page.setItem(COSName.MEDIA_BOX, mediaBox.getCOSArray());
248: }
249: }
250:
251: /**
252: * A rectangle, expressed in default user space units,
253: * defining the visible region of default user space. When the page is displayed
254: * or printed, its contents are to be clipped (cropped) to this rectangle
255: * and then imposed on the output medium in some implementationdefined
256: * manner
257: *
258: * This will get the CropBox at this page and not look up the hierarchy.
259: * This attribute is inheritable, and findCropBox() should probably used.
260: * This will return null if no CropBox is available at this level.
261: *
262: * @return The CropBox at this level in the hierarchy.
263: */
264: public PDRectangle getCropBox() {
265: PDRectangle retval = null;
266: COSArray array = (COSArray) page
267: .getDictionaryObject(COSName.CROP_BOX);
268: if (array != null) {
269: retval = new PDRectangle(array);
270: }
271: return retval;
272: }
273:
274: /**
275: * This will find the CropBox for this page by looking up the hierarchy until
276: * it finds them.
277: *
278: * @return The CropBox at this level in the hierarchy.
279: */
280: public PDRectangle findCropBox() {
281: PDRectangle retval = getCropBox();
282: PDPageNode parent = getParent();
283: if (retval == null && parent != null) {
284: retval = findParentCropBox(parent);
285: }
286:
287: //default value for cropbox is the media box
288: if (retval == null) {
289: retval = findMediaBox();
290: }
291: return retval;
292: }
293:
294: /**
295: * This will search for a crop box in the parent and return null if it is not
296: * found. It will NOT default to the media box if it cannot be found.
297: *
298: * @param node The node
299: */
300: private PDRectangle findParentCropBox(PDPageNode node) {
301: PDRectangle rect = node.getCropBox();
302: PDPageNode parent = node.getParent();
303: if (rect == null && parent != null) {
304: rect = findParentCropBox(parent);
305: }
306: return rect;
307: }
308:
309: /**
310: * This will set the CropBox for this page.
311: *
312: * @param cropBox The new CropBox for this page.
313: */
314: public void setCropBox(PDRectangle cropBox) {
315: if (cropBox == null) {
316: page.removeItem(COSName.CROP_BOX);
317: } else {
318: page.setItem(COSName.CROP_BOX, cropBox.getCOSArray());
319: }
320: }
321:
322: /**
323: * A rectangle, expressed in default user space units, defining
324: * the region to which the contents of the page should be clipped
325: * when output in a production environment. The default is the CropBox.
326: *
327: * @return The BleedBox attribute.
328: */
329: public PDRectangle getBleedBox() {
330: PDRectangle retval = null;
331: COSArray array = (COSArray) page
332: .getDictionaryObject(COSName.BLEED_BOX);
333: if (array != null) {
334: retval = new PDRectangle(array);
335: } else {
336: retval = findCropBox();
337: }
338: return retval;
339: }
340:
341: /**
342: * This will set the BleedBox for this page.
343: *
344: * @param bleedBox The new BleedBox for this page.
345: */
346: public void setBleedBox(PDRectangle bleedBox) {
347: if (bleedBox == null) {
348: page.removeItem(COSName.BLEED_BOX);
349: } else {
350: page.setItem(COSName.BLEED_BOX, bleedBox.getCOSArray());
351: }
352: }
353:
354: /**
355: * A rectangle, expressed in default user space units, defining
356: * the intended dimensions of the finished page after trimming.
357: * The default is the CropBox.
358: *
359: * @return The TrimBox attribute.
360: */
361: public PDRectangle getTrimBox() {
362: PDRectangle retval = null;
363: COSArray array = (COSArray) page
364: .getDictionaryObject(COSName.TRIM_BOX);
365: if (array != null) {
366: retval = new PDRectangle(array);
367: } else {
368: retval = findCropBox();
369: }
370: return retval;
371: }
372:
373: /**
374: * This will set the TrimBox for this page.
375: *
376: * @param trimBox The new TrimBox for this page.
377: */
378: public void setTrimBox(PDRectangle trimBox) {
379: if (trimBox == null) {
380: page.removeItem(COSName.TRIM_BOX);
381: } else {
382: page.setItem(COSName.TRIM_BOX, trimBox.getCOSArray());
383: }
384: }
385:
386: /**
387: * A rectangle, expressed in default user space units, defining
388: * the extent of the page's meaningful content (including potential
389: * white space) as intended by the page's creator The default isthe CropBox.
390: *
391: * @return The ArtBox attribute.
392: */
393: public PDRectangle getArtBox() {
394: PDRectangle retval = null;
395: COSArray array = (COSArray) page
396: .getDictionaryObject(COSName.ART_BOX);
397: if (array != null) {
398: retval = new PDRectangle(array);
399: } else {
400: retval = findCropBox();
401: }
402: return retval;
403: }
404:
405: /**
406: * This will set the ArtBox for this page.
407: *
408: * @param artBox The new ArtBox for this page.
409: */
410: public void setArtBox(PDRectangle artBox) {
411: if (artBox == null) {
412: page.removeItem(COSName.ART_BOX);
413: } else {
414: page.setItem(COSName.ART_BOX, artBox.getCOSArray());
415: }
416: }
417:
418: //todo BoxColorInfo
419: //todo Contents
420:
421: /**
422: * A value representing the rotation. This will be null if not set at this level
423: * The number of degrees by which the page should
424: * be rotated clockwise when displayed or printed. The value must be a multiple
425: * of 90.
426: *
427: * This will get the rotation at this page and not look up the hierarchy.
428: * This attribute is inheritable, and findRotation() should probably used.
429: * This will return null if no rotation is available at this level.
430: *
431: * @return The rotation at this level in the hierarchy.
432: */
433: public Integer getRotation() {
434: Integer retval = null;
435: COSNumber value = (COSNumber) page
436: .getDictionaryObject(COSName.ROTATE);
437: if (value != null) {
438: retval = new Integer(value.intValue());
439: }
440: return retval;
441: }
442:
443: /**
444: * This will find the rotation for this page by looking up the hierarchy until
445: * it finds them.
446: *
447: * @return The rotation at this level in the hierarchy.
448: */
449: public int findRotation() {
450: int retval = 0;
451: Integer rotation = getRotation();
452: if (rotation != null) {
453: retval = rotation.intValue();
454: } else {
455: PDPageNode parent = getParent();
456: if (parent != null) {
457: retval = parent.findRotation();
458: }
459: }
460:
461: return retval;
462: }
463:
464: /**
465: * This will set the rotation for this page.
466: *
467: * @param rotation The new rotation for this page.
468: */
469: public void setRotation(int rotation) {
470: page.setItem(COSName.ROTATE, new COSInteger(rotation));
471: }
472:
473: /**
474: * This will get the contents of the PDF Page, in the case that the contents
475: * of the page is an array then then the entire array of streams will be
476: * be wrapped and appear as a single stream.
477: *
478: * @return The page content stream.
479: *
480: * @throws IOException If there is an error obtaining the stream.
481: */
482: public PDStream getContents() throws IOException {
483: return PDStream.createFromCOS(page
484: .getDictionaryObject(COSName.CONTENTS));
485: }
486:
487: /**
488: * This will set the contents of this page.
489: *
490: * @param contents The new contents of the page.
491: */
492: public void setContents(PDStream contents) {
493: page.setItem(COSName.CONTENTS, contents);
494: }
495:
496: /**
497: * This will get a list of PDThreadBead objects, which are article threads in the
498: * document. This will return an empty list of there are no thread beads.
499: *
500: * @return A list of article threads on this page.
501: */
502: public List getThreadBeads() {
503: COSArray beads = (COSArray) page.getDictionaryObject(COSName.B);
504: if (beads == null) {
505: beads = new COSArray();
506: }
507: List pdObjects = new ArrayList();
508: for (int i = 0; i < beads.size(); i++) {
509: COSDictionary beadDic = (COSDictionary) beads.getObject(i);
510: PDThreadBead bead = null;
511: //in some cases the bead is null
512: if (beadDic != null) {
513: bead = new PDThreadBead(beadDic);
514: }
515: pdObjects.add(bead);
516: }
517: return new COSArrayList(pdObjects, beads);
518:
519: }
520:
521: /**
522: * This will set the list of thread beads.
523: *
524: * @param beads A list of PDThreadBead objects or null.
525: */
526: public void setThreadBeads(List beads) {
527: page
528: .setItem(COSName.B, COSArrayList
529: .converterToCOSArray(beads));
530: }
531:
532: /**
533: * Get the metadata that is part of the document catalog. This will
534: * return null if there is no meta data for this object.
535: *
536: * @return The metadata for this object.
537: */
538: public PDMetadata getMetadata() {
539: PDMetadata retval = null;
540: COSStream stream = (COSStream) page
541: .getDictionaryObject(COSName.METADATA);
542: if (stream != null) {
543: retval = new PDMetadata(stream);
544: }
545: return retval;
546: }
547:
548: /**
549: * Set the metadata for this object. This can be null.
550: *
551: * @param meta The meta data for this object.
552: */
553: public void setMetadata(PDMetadata meta) {
554: page.setItem(COSName.METADATA, meta);
555: }
556:
557: /**
558: * Convert this page to an output image.
559: *
560: * @return A graphical representation of this page.
561: *
562: * @throws IOException If there is an error drawing to the image.
563: */
564: public BufferedImage convertToImage() throws IOException {
565: int scaling = 2;
566: int rotation = findRotation();
567: PDRectangle mBox = findMediaBox();
568: int width = (int) (mBox.getWidth());//*2);
569: int height = (int) (mBox.getHeight());//*2);
570: if (rotation == 90 || rotation == 270) {
571: int tmp = width;
572: width = height;
573: height = tmp;
574: }
575: Dimension pageDimension = new Dimension(width, height);
576:
577: //note we are doing twice as many pixels because
578: //the default size is not really good resolution,
579: //so create an image that is twice the size
580: //and let the client scale it down.
581: BufferedImage retval = new BufferedImage(width * scaling,
582: height * scaling, BufferedImage.TYPE_BYTE_INDEXED);
583: Graphics2D graphics = (Graphics2D) retval.getGraphics();
584: graphics.setColor(Color.WHITE);
585: graphics.fillRect(0, 0, width * scaling, height * scaling);
586: graphics.scale(scaling, scaling);
587: PageDrawer drawer = new PageDrawer();
588: drawer.drawPage(graphics, this , pageDimension);
589:
590: return retval;
591: }
592:
593: /**
594: * Get the page actions.
595: *
596: * @return The Actions for this Page
597: */
598: public PDPageAdditionalActions getActions() {
599: COSDictionary addAct = (COSDictionary) page
600: .getDictionaryObject(COSName.AA);
601: if (addAct == null) {
602: addAct = new COSDictionary();
603: page.setItem(COSName.AA, addAct);
604: }
605: return new PDPageAdditionalActions(addAct);
606: }
607:
608: /**
609: * Set the page actions.
610: *
611: * @param actions The actions for the page.
612: */
613: public void setActions(PDPageAdditionalActions actions) {
614: page.setItem(COSName.AA, actions);
615: }
616:
617: /**
618: * This will return a list of the Annotations for this page.
619: *
620: * @return List of the PDAnnotation objects.
621: *
622: * @throws IOException If there is an error while creating the annotations.
623: */
624: public List getAnnotations() throws IOException {
625: COSArrayList retval = null;
626: COSArray annots = (COSArray) page
627: .getDictionaryObject(COSName.ANNOTS);
628: if (annots == null) {
629: annots = new COSArray();
630: page.setItem(COSName.ANNOTS, annots);
631: retval = new COSArrayList(new ArrayList(), annots);
632: } else {
633: List actuals = new ArrayList();
634:
635: for (int i = 0; i < annots.size(); i++) {
636: COSBase item = annots.getObject(i);
637: actuals.add(PDAnnotation.createAnnotation(item));
638: }
639: retval = new COSArrayList(actuals, annots);
640: }
641: return retval;
642: }
643:
644: /**
645: * This will set the list of annotations.
646: *
647: * @param annots The new list of annotations.
648: */
649: public void setAnnotations(List annots) {
650: page.setItem(COSName.ANNOTS, COSArrayList
651: .converterToCOSArray(annots));
652: }
653:
654: /**
655: * {@inheritDoc}
656: */
657: public int print(Graphics graphics, PageFormat pageFormat,
658: int pageIndex) throws PrinterException {
659: int retval = Printable.PAGE_EXISTS;
660: try {
661: PageDrawer drawer = new PageDrawer();
662: PDRectangle pageSize = findMediaBox();
663: drawer.drawPage(graphics, this , pageSize.createDimension());
664: } catch (IOException io) {
665: throw new PrinterIOException(io);
666: }
667: return retval;
668: }
669:
670: /**
671: * {@inheritDoc}
672: */
673: public boolean equals(Object other) {
674: return other instanceof PDPage
675: && ((PDPage) other).getCOSObject() == this
676: .getCOSObject();
677: }
678:
679: /**
680: * {@inheritDoc}
681: */
682: public int hashCode() {
683: return this.getCOSDictionary().hashCode();
684: }
685: }
|