001: /*
002: Copyright © 2006,2007 Stefano Chizzolini. http://clown.stefanochizzolini.it
003:
004: Contributors:
005: * Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it):
006: contributed code is Copyright © 2006,2007 by Stefano Chizzolini.
007:
008: This file should be part of the source code distribution of "PDF Clown library"
009: (the Program): see the accompanying README files for more info.
010:
011: This Program is free software; you can redistribute it and/or modify it under
012: the terms of the GNU General Public License as published by the Free Software
013: Foundation; either version 2 of the License, or (at your option) any later version.
014:
015: This Program is distributed in the hope that it will be useful, but WITHOUT ANY
016: WARRANTY, either expressed or implied; without even the implied warranty of
017: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
018:
019: You should have received a copy of the GNU General Public License along with this
020: Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
021:
022: Redistribution and use, with or without modification, are permitted provided that such
023: redistributions retain the above copyright notice, license and disclaimer, along with
024: this list of conditions.
025: */
026:
027: package it.stefanochizzolini.clown.documents;
028:
029: import it.stefanochizzolini.clown.bytes.IBuffer;
030: import it.stefanochizzolini.clown.documents.contents.Contents;
031: import it.stefanochizzolini.clown.documents.contents.IContentContext;
032: import it.stefanochizzolini.clown.documents.contents.IContentEntity;
033: import it.stefanochizzolini.clown.documents.contents.Resources;
034: import it.stefanochizzolini.clown.documents.contents.objects.ContentObject;
035: import it.stefanochizzolini.clown.documents.contents.xObjects.FormXObject;
036: import it.stefanochizzolini.clown.documents.contents.xObjects.XObject;
037: import it.stefanochizzolini.clown.files.File;
038: import it.stefanochizzolini.clown.objects.IPdfNumber;
039: import it.stefanochizzolini.clown.objects.PdfArray;
040: import it.stefanochizzolini.clown.objects.PdfDataObject;
041: import it.stefanochizzolini.clown.objects.PdfDictionary;
042: import it.stefanochizzolini.clown.objects.PdfDirectObject;
043: import it.stefanochizzolini.clown.objects.PdfInteger;
044: import it.stefanochizzolini.clown.objects.PdfName;
045: import it.stefanochizzolini.clown.objects.PdfObjectWrapper;
046: import it.stefanochizzolini.clown.objects.PdfRectangle;
047: import it.stefanochizzolini.clown.objects.PdfReference;
048: import it.stefanochizzolini.clown.objects.PdfStream;
049: import it.stefanochizzolini.clown.util.NotImplementedException;
050:
051: import java.awt.Dimension;
052: import java.awt.geom.Dimension2D;
053: import java.util.Map;
054:
055: /**
056: Document page [PDF:1.6:3.6.2].
057: @version 0.0.5
058: @since 0.0.0
059: */
060: public class Page extends PdfObjectWrapper<PdfDictionary> implements
061: IContentContext {
062: /*
063: NOTE: Inheritable attributes are NOT early-collected, as they are NOT part
064: of the explicit representation of a page. They are retrieved everytime
065: clients call.
066: */
067: // <class>
068: // <static>
069: // <interface>
070: // <public>
071: public static Page wrap(PdfReference reference) {
072: return new Page(reference);
073: }
074:
075: // </public>
076: // </interface>
077: // </static>
078:
079: // <dynamic>
080: // <constructors>
081: /**
082: Creates a new page within the given document context, using default resources.
083: */
084: public Page(Document context) {
085: super (context.getFile(), new PdfDictionary(new PdfName[] {
086: PdfName.Type, PdfName.Contents },
087: new PdfDirectObject[] { PdfName.Page,
088: context.getFile().register(new PdfStream()) }));
089: }
090:
091: /**
092: Creates a new page within the given document context, using custom resources.
093: */
094: public Page(Document context, Dimension2D size, Resources resources) {
095: super (context.getFile(), new PdfDictionary(new PdfName[] {
096: PdfName.Type, PdfName.MediaBox, PdfName.Contents,
097: PdfName.Resources }, new PdfDirectObject[] {
098: PdfName.Page,
099: new PdfRectangle(0, 0, size.getWidth(), size
100: .getHeight()),
101: context.getFile().register(new PdfStream()),
102: resources.getBaseObject() }));
103: }
104:
105: /**
106: <h3>Remarks</h3>
107: <p>For internal use only.</p>
108: */
109: public Page(PdfDirectObject baseObject) {
110: super (baseObject, null // NO container. NOTE: this is a simplification (the spec [PDF:1.6] doesn't apparently prescribe the use of an indirect object for page dictionary, whilst the general practice is as such. If an exception occur, you'll need to specify the proper container).
111: );
112: }
113:
114: // </constructors>
115:
116: // <interface>
117: // <public>
118: @Override
119: public Page clone(Document context) {
120: /*
121: NOTE: We cannot just delegate the cloning to the base object, as it would
122: involve some unwanted objects like those in 'Parent' and 'Annots' entries that may
123: cause infinite loops (due to circular references) and may include exceeding contents
124: (due to copy propagations to the whole page-tree which this page belongs to).
125: TODO: 'Annots' entry must be finely treated to include any non-circular reference.
126: */
127: // TODO:IMPL deal with inheritable attributes!!!
128: File contextFile = context.getFile();
129: PdfDictionary clone = new PdfDictionary(getBaseDataObject()
130: .size());
131: for (Map.Entry<PdfName, PdfDirectObject> entry : getBaseDataObject()
132: .entrySet()) {
133: PdfName key = entry.getKey();
134: // Is the entry unwanted?
135: if (key.equals(PdfName.Parent)
136: || key.equals(PdfName.Annots))
137: continue;
138:
139: // Insert the clone of the entry into the clone of the page dictionary!
140: clone.put(key, (PdfDirectObject) entry.getValue().clone(
141: contextFile));
142: }
143:
144: return new Page(contextFile.getIndirectObjects().add(clone)
145: .getReference());
146: }
147:
148: /**
149: Gets the index of the page.
150: <h3>Remarks</h3>
151: <p>The page index is not an explicit datum, therefore it needs to be
152: inferred from the position of the page object inside the page tree,
153: requiring a significant amount of computation: invoke it sparingly!</p>
154: */
155: public int getIndex() {
156: /*
157: NOTE: We'll scan sequentially each page-tree level above this page object
158: collecting page counts. At each level we'll scan the kids array from the
159: lower-indexed item to the ancestor of this page object at that level.
160: */
161: PdfReference ancestorKidReference = (PdfReference) getBaseObject();
162: PdfReference parentReference = (PdfReference) getBaseDataObject()
163: .get(PdfName.Parent);
164: PdfDictionary parent = (PdfDictionary) File
165: .resolve(parentReference);
166: PdfArray kids = (PdfArray) File.resolve(parent
167: .get(PdfName.Kids));
168: int index = 0;
169: for (int i = 0; true; i++) {
170: PdfReference kidReference = (PdfReference) kids.get(i);
171: // Is the current-level counting complete?
172: // NOTE: It's complete when it reaches the ancestor at the current level.
173: if (kidReference.equals(ancestorKidReference)) // Ancestor node.
174: {
175: // Does the current level correspond to the page-tree root node?
176: if (!parent.containsKey(PdfName.Parent)) {
177: // We reached the top: counting's finished.
178: return index;
179: }
180: // Set the ancestor at the next level!
181: ancestorKidReference = parentReference;
182: // Move up one level!
183: parentReference = (PdfReference) parent
184: .get(PdfName.Parent);
185: parent = (PdfDictionary) File.resolve(parentReference);
186: kids = (PdfArray) File
187: .resolve(parent.get(PdfName.Kids));
188: i = -1;
189: } else // Intermediate node.
190: {
191: PdfDictionary kid = (PdfDictionary) File
192: .resolve(kidReference);
193: if (kid.get(PdfName.Type).equals(PdfName.Page))
194: index++;
195: else
196: index += ((PdfInteger) kid.get(PdfName.Count))
197: .getValue();
198: }
199: }
200: }
201:
202: /**
203: Gets the next page.
204: <h3>Remarks</h3>
205: <p>Relative positions are not explicit data, so they need a substantial
206: amount of computation: invoke it sparingly!</p>
207: */
208: public Page getNext() {
209: return getPage(+1);
210: }
211:
212: /**
213: Gets a page at a position relative to this one.
214: <h3>Remarks</h3>
215: <p>Relative positions are not explicit data, so they need a substantial amount of
216: computation and should be used sparingly.</p>
217: */
218: public Page getPage(int offset) {
219: if (offset == 0)
220: return this ;
221: /*TODO:IMPL This is NOT good: it's needed an optimized algorithm in order to
222: avoid double scans (Index evaluation + page search): we need that index and
223: search be evaluated incrementally together! It should get the
224: pdfreference of the sought page and then use Page.wrap(PdfReference)!!!*/
225: return getDocument().getPages().get(getIndex() + offset);
226: }
227:
228: /**
229: Gets the previous page.
230: <h3>Remarks</h3>
231: <p>Relative positions are not explicit data, so they need a substantial amount of
232: computation: invoke it sparingly!</p>
233: */
234: public Page getPrevious() {
235: return getPage(-1);
236: }
237:
238: /**
239: Gets the page size.
240: */
241: public Dimension2D getSize() {
242: PdfArray box = (PdfArray) File
243: .resolve(getInheritableAttribute(PdfName.MediaBox));
244: if (box == null)
245: return null;
246:
247: return new Dimension((int) ((IPdfNumber) box.get(2))
248: .getNumberValue(), (int) ((IPdfNumber) box.get(3))
249: .getNumberValue());
250: }
251:
252: /**
253: Sets the page size.
254: */
255: public void setSize(Dimension2D value) {
256: /*
257: NOTE: When page size is about to be modified, we MUST ensure that the change will affect just
258: the mediaBox of this page; so, if such a mediaBox is implicit (inherited), it MUST be cloned
259: and explicitly assigned to this page in order to apply changes.
260: */
261: PdfDictionary dictionary = getBaseDataObject();
262: PdfDirectObject entry = dictionary.get(PdfName.MediaBox);
263: if (entry == null) {
264: // Clone the inherited attribute in order to restrict its change to this page's scope only!
265: entry = (PdfDirectObject) getInheritableAttribute(
266: PdfName.MediaBox).clone(getFile());
267: // Associate the cloned attribute to this page's dictionary!
268: dictionary.put(PdfName.MediaBox, entry);
269: }
270:
271: PdfArray box = (PdfArray) File.resolve(entry);
272: ((IPdfNumber) box.get(2)).setNumberValue(value.getWidth());
273: ((IPdfNumber) box.get(3)).setNumberValue(value.getHeight());
274: }
275:
276: // <IContentContext>
277: public PdfArray getBox() {
278: return (PdfArray) File
279: .resolve(getInheritableAttribute(PdfName.MediaBox));
280: }
281:
282: public Contents getContents() {
283: return new Contents(getBaseDataObject().get(PdfName.Contents),
284: ((PdfReference) getBaseObject()).getIndirectObject(),
285: this );
286: }
287:
288: public Resources getResources() {
289: return new Resources(
290: getInheritableAttribute(PdfName.Resources),
291: ((PdfReference) getBaseObject()).getIndirectObject());
292: }
293:
294: // <IContentEntity>
295: /**
296: @version 0.0.5
297: @since 0.0.5
298: */
299: public ContentObject toInlineObject(IContentContext context) {
300: throw new NotImplementedException();
301: }
302:
303: /**
304: @version 0.0.5
305: @since 0.0.5
306: */
307: public XObject toXObject(Document context) {
308: File contextFile = context.getFile();
309:
310: FormXObject form = new FormXObject(context);
311: PdfStream formStream = form.getBaseDataObject();
312:
313: // Header.
314: {
315: PdfDictionary formHeader = formStream.getHeader();
316: // Bounding box.
317: formHeader.put(PdfName.BBox,
318: (PdfDirectObject) getInheritableAttribute(
319: PdfName.MediaBox).clone(contextFile));
320: // Resources.
321: {
322: PdfDirectObject resourcesObject = getInheritableAttribute(PdfName.Resources);
323: formHeader
324: .put(
325: PdfName.Resources,
326: // Same document?
327: /* NOTE: Try to reuse the resource dictionary whenever possible. */
328: (context.equals(getDocument()) ? resourcesObject
329: : (PdfDirectObject) resourcesObject
330: .clone(contextFile)));
331: }
332: }
333:
334: // Body (contents).
335: {
336: IBuffer formBody = formStream.getBody();
337: PdfDataObject contentsDataObject = File
338: .resolve(getBaseDataObject().get(PdfName.Contents));
339: if (contentsDataObject instanceof PdfStream) {
340: formBody.append(((PdfStream) contentsDataObject)
341: .getBody());
342: } else {
343: for (PdfDirectObject contentStreamObject : (PdfArray) contentsDataObject) {
344: formBody.append(((PdfStream) File
345: .resolve(contentStreamObject)).getBody());
346: }
347: }
348: }
349:
350: return form;
351: }
352:
353: // </IContentEntity>
354: // </IContentContext>
355: // </public>
356:
357: // <protected>
358: protected PdfDirectObject getInheritableAttribute(PdfName key) {
359: /*
360: NOTE: It moves upward until it finds the inherited attribute.
361: */
362: PdfDictionary dictionary = getBaseDataObject();
363: while (true) {
364: PdfDirectObject entry = dictionary.get(key);
365: if (entry != null)
366: return entry;
367:
368: dictionary = (PdfDictionary) File.resolve(dictionary
369: .get(PdfName.Parent));
370: if (dictionary == null) {
371: // Isn't the page attached to the page tree?
372: /* NOTE: This condition is illegal. */
373: if (getBaseDataObject().get(PdfName.Parent) == null)
374: throw new RuntimeException(
375: "Inheritable attributes unreachable: Page objects MUST be inserted into their document's Pages collection before being used.");
376:
377: // Is mediabox undefined?
378: /* NOTE: Mediabox is required. */
379: if (key.equals(PdfName.MediaBox)) {
380: getDocument().setPageSize(PageFormat.getSize());
381: }
382: // Is resources collection undefined?
383: /* NOTE: Resources collection is required. */
384: else if (key.equals(PdfName.Resources)) {
385: getDocument().setResources(
386: new Resources(getDocument()));
387: } else {
388: return null;
389: }
390:
391: return getInheritableAttribute(key);
392: }
393: }
394: }
395: // </protected>
396: // </interface>
397: // </dynamic>
398: // </class>
399: }
|