001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - 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: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.pdf.pd;
031:
032: import java.lang.ref.SoftReference;
033: import java.util.ArrayList;
034: import java.util.HashSet;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Set;
038: import de.intarsys.pdf.cds.CDSRectangle;
039: import de.intarsys.pdf.content.CSContent;
040: import de.intarsys.pdf.content.IContentStreamProvider;
041: import de.intarsys.pdf.cos.COSArray;
042: import de.intarsys.pdf.cos.COSBasedObject;
043: import de.intarsys.pdf.cos.COSDictionary;
044: import de.intarsys.pdf.cos.COSName;
045: import de.intarsys.pdf.cos.COSObject;
046: import de.intarsys.pdf.cos.COSStream;
047:
048: /**
049: * A single concrete page in a PDF document.
050: */
051: public class PDPage extends PDPageNode implements
052: IAdditionalActionSupport, IContentStreamProvider {
053: /**
054: * The meta class implementation
055: */
056: public static class MetaClass extends PDPageNode.MetaClass {
057: protected MetaClass(Class instanceClass) {
058: super (instanceClass);
059: }
060:
061: protected COSBasedObject doCreateCOSBasedObject(COSObject object) {
062: return new PDPage(object);
063: }
064: }
065:
066: /** The meta class instance */
067: public static final MetaClass META = new MetaClass(MetaClass.class
068: .getDeclaringClass());
069:
070: public static String COPY_SUFFIX = "_copy"; //$NON-NLS-1$
071:
072: public static final COSName DK_Annots = COSName.constant("Annots"); //$NON-NLS-1$
073:
074: public static final COSName DK_PieceInfo = COSName
075: .constant("PieceInfo"); //$NON-NLS-1$
076:
077: public static final COSName DK_Resources = COSName
078: .constant("Resources"); //$NON-NLS-1$
079:
080: public static final COSName DK_MediaBox = COSName
081: .constant("MediaBox"); //$NON-NLS-1$
082:
083: public static final COSName DK_CropBox = COSName
084: .constant("CropBox"); //$NON-NLS-1$
085:
086: public static final COSName DK_Contents = COSName
087: .constant("Contents"); //$NON-NLS-1$
088:
089: public static final COSName DK_Metadata = COSName
090: .constant("Metadata"); //$NON-NLS-1$
091:
092: /** supported additional action triggers */
093: public static final Set PAGE_ACTION_TRIGGERS;
094:
095: private static final List NULL = new ArrayList();
096:
097: static {
098: PAGE_ACTION_TRIGGERS = new HashSet(3);
099: PAGE_ACTION_TRIGGERS.add("O"); //$NON-NLS-1$
100: PAGE_ACTION_TRIGGERS.add("C"); //$NON-NLS-1$
101: }
102:
103: private List cachedAnnotations = null;
104:
105: private SoftReference cachedContentStream = null;
106:
107: /**
108: * Create the receiver class from an already defined {@link COSDictionary}.
109: * NEVER use the constructor directly.
110: *
111: * @param object
112: * the PDDocument containing the new object
113: */
114: protected PDPage(COSObject object) {
115: super (object);
116: }
117:
118: /**
119: * Add a {@link PDAnnotation} to the collection of annotations on the
120: * receiver page.
121: *
122: * @param annot
123: * The PDAnnotation to add to the page.
124: */
125: public void addAnnotation(PDAnnotation annot) {
126: COSArray cosAnnots = cosGetField(DK_Annots).asArray();
127: if (cosAnnots == null) {
128: cosAnnots = COSArray.create();
129: cosAnnots.beIndirect();
130: cosSetField(DK_Annots, cosAnnots);
131: cachedAnnotations = null;
132: }
133: cosAnnots.add(annot.cosGetDict());
134: annot.setPage(this );
135: }
136:
137: /**
138: * Add a {@link CSContent} stream to this.
139: *
140: * @param contentStream
141: * The new {@link CSContent}
142: */
143: public void addContentStream(CSContent contentStream) {
144: cosAddContents(contentStream.createStream());
145: }
146:
147: protected void basicSetResourceDict(COSDictionary resourceDict) {
148: cosSetField(DK_Resources, resourceDict);
149: }
150:
151: /*
152: * (non-Javadoc)
153: *
154: * @see de.intarsys.pdf.pd.PDPageNode#collectAnnotations(java.util.List)
155: */
156: protected void collectAnnotations(List annotations) {
157: if (getAnnotations() != null) {
158: annotations.addAll(getAnnotations());
159: }
160: }
161:
162: /**
163: * Append {@link COSStream} to the pages content
164: *
165: * @param content
166: * The {@link COSStream} to add to the page
167: */
168: public void cosAddContents(COSStream content) {
169: COSObject contents = cosGetField(DK_Contents);
170: if (contents.isNull()) {
171: cosSetField(DK_Contents, content);
172: }
173: if (contents instanceof COSStream) {
174: COSArray array = COSArray.create(2);
175: array.add(contents);
176: array.add(content);
177: cosSetField(DK_Contents, array);
178: }
179: if (contents instanceof COSArray) {
180: COSArray array = (COSArray) contents;
181: array.add(content);
182: }
183: }
184:
185: /**
186: * Prepend contents to the pages content.
187: *
188: * @param content
189: * The {@link COSStream} to add to the page
190: */
191: public void cosPrependContents(COSStream content) {
192: COSObject contents = cosGetField(DK_Contents);
193: if (contents.isNull()) {
194: cosSetField(DK_Contents, content);
195: }
196: if (contents instanceof COSStream) {
197: COSArray array = COSArray.create(2);
198: array.add(content);
199: array.add(contents);
200: cosSetField(DK_Contents, array);
201: }
202: if (contents instanceof COSArray) {
203: COSArray array = (COSArray) contents;
204: array.add(0, content);
205: }
206: }
207:
208: /**
209: * The /Contents entry
210: *
211: * @return The /Contents entry
212: */
213: public COSObject cosGetContents() {
214: return cosGetField(DK_Contents);
215: }
216:
217: /*
218: * (non-Javadoc)
219: *
220: * @see de.intarsys.pdf.pd.PDObject#cosGetExpectedType()
221: */
222: protected COSName cosGetExpectedType() {
223: return CN_Type_Page;
224: }
225:
226: /**
227: * The piece info dictionary of the document.
228: *
229: * @return The piece info dictionary of the document.
230: */
231: public COSDictionary cosGetPieceInfo() {
232: return cosGetField(DK_PieceInfo).asDictionary();
233: }
234:
235: /**
236: * Set the /Contents for the page
237: *
238: * @param content
239: * the stream defining the page content
240: *
241: * @return The /Contents entry previously associated with this.
242: */
243: public COSObject cosSetContents(COSObject content) {
244: return cosSetField(DK_Contents, content);
245: }
246:
247: /**
248: * Set the piece info dictionary of the document.
249: *
250: * @param dict
251: * The piece info dictionary of the document.
252: *
253: * @return The /PieceInfo entry previously associated with this.
254: */
255: public COSDictionary cosSetPieceInfo(COSDictionary dict) {
256: if (dict != null) {
257: dict.beIndirect();
258: }
259: return cosSetField(DK_PieceInfo, dict).asDictionary();
260: }
261:
262: /*
263: * (non-Javadoc)
264: *
265: * @see de.intarsys.pdf.pd.PDPageNode#dispose()
266: */
267: public void dispose() {
268: // todo 1 to much logic here...
269: if (getAnnotations() != null) {
270: Iterator iter = getAnnotations().iterator();
271: while (iter.hasNext()) {
272: PDAnnotation annotation = (PDAnnotation) iter.next();
273: annotation.dispose();
274: }
275: }
276: super .dispose();
277: }
278:
279: /**
280: * A collection of all {@link PDAcroFormField}s that have {@link
281: * PDAnnotation}s on the receiver that are children of <code>parent</code>.
282: *
283: * @param parent
284: * The parent {@link PDAcroForm} or {@link PDAcroFormField}.
285: * @param result
286: * The collection of {@link PDAnnotation}s collected so far.
287: */
288: protected void getAcroFormFields(PDObject parent, List result) {
289: List elements = parent.getGenericChildren();
290: List annotations = getAnnotations();
291: if (elements == null) {
292: return;
293: }
294: for (Iterator i = elements.iterator(); i.hasNext();) {
295: PDObject object = (PDObject) i.next();
296: if (annotations.contains(object)) {
297: result.add(object);
298: }
299: getAcroFormFields(object, result);
300: }
301: }
302:
303: /*
304: * (non-Javadoc)
305: *
306: * @see de.intarsys.pdf.pd.IAdditionalActionSupport#getAdditionalActions()
307: */
308: public PDAdditionalActions getAdditionalActions() {
309: COSDictionary field = cosGetField(DK_AA).asDictionary();
310: return (PDAdditionalActions) PDAdditionalActions.META
311: .createFromCos(field);
312: }
313:
314: /**
315: * Get a list of all {@link PDAnnotation} objects that are referenced in
316: * this page.
317: *
318: * @return A list of all {@link PDAnnotation} objects that are referenced in
319: * this page or null if none exist.
320: */
321: public List getAnnotations() {
322: if (cachedAnnotations == null) {
323: cachedAnnotations = getPDObjects(DK_Annots,
324: PDAnnotation.META, true);
325: if (cachedAnnotations == null) {
326: // avoid continued lookup when no annotations found
327: cachedAnnotations = NULL;
328: }
329: }
330: if (cachedAnnotations == NULL) {
331: return null;
332: }
333: return cachedAnnotations;
334: }
335:
336: /**
337: * The {@link PDApplicationData} associated with <code>name</code> on the
338: * page.
339: *
340: * @param name
341: * The name of the {@link PDApplicationData} to lookup.
342: * @return The {@link PDApplicationData} associated with <code>name</code>
343: * on the page.
344: */
345: public PDApplicationData getApplicationData(String name) {
346: COSDictionary pid = cosGetPieceInfo();
347: if (pid == null) {
348: return null;
349: }
350: COSName cosName = COSName.create(name);
351: COSDictionary pi = pid.get(cosName).asDictionary();
352: if (pi == null) {
353: return null;
354: }
355: return (PDApplicationData) PDApplicationData.META
356: .createFromCos(pi);
357: }
358:
359: protected int getContentsSize() {
360: COSObject contents = cosGetContents();
361: if (contents.isNull()) {
362: return 0;
363: }
364: if (contents instanceof COSStream) {
365: return 1;
366: }
367: return ((COSArray) contents).size();
368: }
369:
370: /**
371: * The {@link CSContent} defining the visual content of the page.
372: *
373: * @return The {@link CSContent} defining the visual content of the page.
374: */
375: public CSContent getContentStream() {
376: CSContent contentStream = null;
377: if (cachedContentStream != null) {
378: contentStream = (CSContent) cachedContentStream.get();
379: }
380: if (contentStream == null) {
381: COSObject contents = cosGetContents();
382: if (!contents.isNull()) {
383: if (contents instanceof COSStream) {
384: contentStream = CSContent.createFromCos(contents
385: .asStream());
386: } else {
387: contentStream = CSContent.createFromCos(contents
388: .asArray());
389: }
390: // just to be sure we are not registered before (soft ref!)
391: contents.removeObjectListener(this );
392: contents.addObjectListener(this );
393: // todo add listener to content streams in array...
394: cachedContentStream = new SoftReference(contentStream);
395: }
396: }
397: return contentStream;
398: }
399:
400: /*
401: * (non-Javadoc)
402: *
403: * @see de.intarsys.pdf.pd.PDPageNode#getCount()
404: */
405: public int getCount() {
406: return 1;
407: }
408:
409: /**
410: * @return The first {@link PDAnnotation} on the page or null
411: */
412: public PDAnnotation getFirstAnnotation() {
413: if (getAnnotations() == null) {
414: return null;
415: }
416: if (getAnnotations().size() == 0) {
417: return null;
418: }
419: return (PDAnnotation) getAnnotations().get(0);
420: }
421:
422: /*
423: * (non-Javadoc)
424: *
425: * @see de.intarsys.pdf.pd.PDPageNode#getFirst()
426: */
427: public PDPageNode getFirstNode() {
428: return this ;
429: }
430:
431: /*
432: * (non-Javadoc)
433: *
434: * @see de.intarsys.pdf.pd.PDPageNode#getFirstPage()
435: */
436: public PDPage getFirstPage() {
437: return this ;
438: }
439:
440: /**
441: * @return The last {@link PDAnnotation} on the page or null
442: */
443: public PDAnnotation getLastAnnotation() {
444: if (getAnnotations() == null) {
445: return null;
446: }
447: int size = getAnnotations().size();
448: if (size == 0) {
449: return null;
450: }
451: return (PDAnnotation) getAnnotations().get(size - 1);
452: }
453:
454: /*
455: * (non-Javadoc)
456: *
457: * @see de.intarsys.pdf.pd.PDPageNode#getLast()
458: */
459: public PDPageNode getLastNode() {
460: return this ;
461: }
462:
463: /*
464: * (non-Javadoc)
465: *
466: * @see de.intarsys.pdf.pd.PDPageNode#getLastPage()
467: */
468: public PDPage getLastPage() {
469: return this ;
470: }
471:
472: /**
473: * The {@link PDAnnotation} following the given {@link PDAnnotation} annot
474: * or null, if <code>annot</code> was the last one in the list or does't
475: * exist on this page.
476: *
477: * @param annot
478: * a PDAnnotation
479: * @return a PDAnnotation or null
480: */
481: public PDAnnotation getNextAnnotation(PDAnnotation annot) {
482: if (getAnnotations() == null) {
483: return null;
484: }
485: int resultIndex = getAnnotations().indexOf(annot);
486: if (resultIndex == -1) {
487: return null;
488: }
489: if ((resultIndex + 1) < getAnnotations().size()) {
490: return (PDAnnotation) getAnnotations().get(resultIndex + 1);
491: }
492: return null;
493: }
494:
495: /**
496: * The next page after the receiver.
497: *
498: * @return The next page after the receiver.
499: */
500: public PDPage getNextPage() {
501: return getParent().getNextPage(this );
502: }
503:
504: /*
505: * (non-Javadoc)
506: *
507: * @see de.intarsys.pdf.pd.PDPageNode#getPageAt(int)
508: */
509: public PDPage getPageAt(int index) {
510: if (index == 0) {
511: return this ;
512: }
513: return super .getPageAt(index);
514: }
515:
516: /**
517: * Returns the {@link PDAnnotation} preceding the given {@link PDAnnotation}
518: * annot or null, if annot was the first one in the list or does't exist on
519: * this page.
520: *
521: * @param annot
522: * a PDAnnotation
523: * @return a PDAnnotation or null
524: */
525: public PDAnnotation getPreviousAnnotation(PDAnnotation annot) {
526: if (getAnnotations() == null) {
527: return null;
528: }
529: int resultIndex = getAnnotations().indexOf(annot);
530: if (resultIndex == -1) {
531: return null;
532: }
533: if ((resultIndex - 1) >= 0) {
534: return (PDAnnotation) getAnnotations().get(resultIndex - 1);
535: }
536: return null;
537: }
538:
539: /**
540: * Get the previous page before the receiver.
541: *
542: * @return Get the previous page before the receiver.
543: */
544: public PDPage getPreviousPage() {
545: return getParent().getPreviousPage(this );
546: }
547:
548: /**
549: * return a PDCResourceDict. this is a wrapper around a COSDictionary that
550: * handles Resource specific details.
551: *
552: * @return the resource dict as a PDCResourceDict
553: */
554: public PDResources getResources() {
555: COSDictionary dict = cosGetFieldInheritable(DK_Resources)
556: .asDictionary();
557: return (PDResources) PDResources.META.createFromCos(dict);
558: }
559:
560: /*
561: * (non-Javadoc)
562: *
563: * @see de.intarsys.pdf.pd.IAdditionalActionSupport#getSupportedTriggerEvents()
564: */
565: public Set getSupportedTriggerEvents() {
566: return PAGE_ACTION_TRIGGERS;
567: }
568:
569: /*
570: * (non-Javadoc)
571: *
572: * @see de.intarsys.pdf.pd.PDComplexBase#initializeFromScratch()
573: */
574: protected void initializeFromScratch() {
575: super .initializeFromScratch();
576: // todo 3 get default paper size from environment
577: setMediaBox(new CDSRectangle(CDSRectangle.SIZE_A4));
578: }
579:
580: /*
581: * (non-Javadoc)
582: *
583: * @see de.intarsys.pdf.pd.PDPageNode#invalidateCaches()
584: */
585: public void invalidateCaches() {
586: super .invalidateCaches();
587: cachedAnnotations = null;
588: cachedContentStream = null;
589: COSObject cosAnnotations = cosGetField(DK_Annots);
590: cosAnnotations.removeObjectListener(this );
591: COSObject cosContents = cosGetField(DK_Contents);
592: cosContents.removeObjectListener(this );
593: }
594:
595: /*
596: * (non-Javadoc)
597: *
598: * @see de.intarsys.pdf.pd.PDPageNode#isPage()
599: */
600: public boolean isPage() {
601: return true;
602: }
603:
604: /**
605: * Remove a {@link PDAnnotation} from the page.
606: *
607: * @param annot
608: * The {@link PDAnnotation} to remove from the page.
609: */
610: public void removeAnnotation(PDAnnotation annot) {
611: COSArray cosAnnots = cosGetField(DK_Annots).asArray();
612: if (cosAnnots != null) {
613: cosAnnots.remove(annot.cosGetDict());
614: if (cosAnnots.isEmpty()) {
615: cosRemoveField(DK_Annots);
616: }
617: }
618: }
619:
620: /**
621: * Remove the {@link PDApplicationData} associated with <code>name</code>
622: * from this page.
623: *
624: * @param name
625: * The name of the application data object to be removed.
626: */
627: public void removeApplicationData(String name) {
628: COSDictionary pid = cosGetPieceInfo();
629: if (pid == null) {
630: return;
631: }
632: COSName cosName = COSName.create(name);
633: pid.remove(cosName);
634: }
635:
636: /*
637: * (non-Javadoc)
638: *
639: * @see de.intarsys.pdf.pd.IAdditionalActionSupport#setActions(de.intarsys.pdf.pd.PDAdditionalActions)
640: */
641: public void setAdditionalActions(PDAdditionalActions actions) {
642: setFieldObject(DK_AA, actions);
643: }
644:
645: /**
646: * Associate a {@link PDApplicationData} instance with this using
647: * <code>name</code>.
648: *
649: * @param name
650: * The name for the {@link PDApplicationData} instance within
651: * this.
652: * @param data
653: * The {@link PDApplicationData} instance.
654: */
655: public void setApplicationData(String name, PDApplicationData data) {
656: COSDictionary pid = cosGetPieceInfo();
657: if (pid == null) {
658: pid = COSDictionary.create();
659: cosSetPieceInfo(pid);
660: }
661: COSName cosName = COSName.create(name);
662: pid.put(cosName, data.cosGetDict());
663: }
664:
665: /**
666: * Assign a new visual appearance to the page.
667: *
668: * @param contentStream
669: * The new visual appearance.
670: */
671: public void setContentStream(CSContent contentStream) {
672: if (contentStream != null) {
673: cosSetContents(contentStream.createStream());
674: } else {
675: cosSetContents(null);
676: }
677: }
678:
679: /*
680: * (non-Javadoc)
681: *
682: * @see de.intarsys.pdf.pd.IResourcesProvider#setResources(de.intarsys.pdf.pd.PDResources)
683: */
684: public void setResources(PDResources resources) {
685: basicSetResourceDict(resources.cosGetDict());
686: }
687: }
|