001: /*
002: Copyright © 2006 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 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.files.File;
030: import it.stefanochizzolini.clown.objects.IPdfNumber;
031: import it.stefanochizzolini.clown.objects.PdfArray;
032: import it.stefanochizzolini.clown.objects.PdfDictionary;
033: import it.stefanochizzolini.clown.objects.PdfDirectObject;
034: import it.stefanochizzolini.clown.objects.PdfInteger;
035: import it.stefanochizzolini.clown.objects.PdfName;
036: import it.stefanochizzolini.clown.objects.PdfObjectWrapper;
037: import it.stefanochizzolini.clown.objects.PdfReference;
038: import it.stefanochizzolini.clown.util.NotImplementedException;
039:
040: import java.util.ArrayList;
041: import java.util.Arrays;
042: import java.util.Collection;
043: import java.util.Iterator;
044: import java.util.List;
045: import java.util.ListIterator;
046: import java.util.NoSuchElementException;
047: import java.util.Stack;
048:
049: /**
050: Document pages collection.
051: */
052: public class Pages extends PdfObjectWrapper<PdfDictionary> implements
053: List<Page> {
054: /*
055: TODO:IMPL A B-tree algorithm should be implemented to optimize the inner layout
056: of the page tree (better insertion/deletion performance). In this case, it would
057: be necessary to keep track of the modified tree nodes for incremental update.
058: */
059: // <class>
060: // <dynamic>
061: // <constructors>
062: Pages(Document context) {
063: super (context.getFile(), new PdfDictionary(new PdfName[] {
064: PdfName.Type, PdfName.Kids, PdfName.Count },
065: new PdfDirectObject[] { PdfName.Pages, new PdfArray(),
066: new PdfInteger(0) }));
067: }
068:
069: Pages(PdfDirectObject baseObject) {
070: super (baseObject, null // NO container (page tree root node MUST be an indirect object [PDF:1.6:3.6.1]).
071: );
072: }
073:
074: // </constructors>
075:
076: // <interface>
077: // <public>
078: @Override
079: public Pages clone(Document context) {
080: throw new NotImplementedException();
081: }
082:
083: // <List>
084: public void add(int index, Page page) {
085: commonAddAll(index, Arrays.asList(page));
086: }
087:
088: public boolean addAll(int index, Collection<? extends Page> pages) {
089: return commonAddAll(index, pages);
090: }
091:
092: public Page get(int index) {
093: /*
094: NOTE: As stated in [PDF:1.6:3.6.2], to retrieve pages is a matter of diving
095: inside a B-tree. To keep it as efficient as possible, this implementation
096: does NOT adopt recursion to deepen its search, opting for an iterative strategy
097: instead.
098: */
099: int pageOffset = 0;
100: PdfDictionary parent = getBaseDataObject();
101: PdfArray kids = (PdfArray) File.resolve(parent
102: .get(PdfName.Kids));
103: for (int i = 0; i < kids.size(); i++) {
104: PdfReference kidReference = (PdfReference) kids.get(i);
105: PdfDictionary kid = (PdfDictionary) File
106: .resolve(kidReference);
107: // Is current kid a page object?
108: if (kid.get(PdfName.Type).equals(PdfName.Page)) // Page object.
109: {
110: // Did we reach the searched position?
111: if (pageOffset == index) // Vertical scan (we finished).
112: {
113: // We got it!
114: return new Page(kidReference);
115: } else // Horizontal scan (go past).
116: {
117: // Cumulate current page object count!
118: pageOffset++;
119: }
120: } else // Page tree node.
121: {
122: // Does the current subtree contain the searched page?
123: if (((PdfInteger) kid.get(PdfName.Count)).getValue()
124: + pageOffset > index) // Vertical scan (deepen the search).
125: {
126: // Go down one level!
127: parent = kid;
128: kids = (PdfArray) File.resolve(parent
129: .get(PdfName.Kids));
130: i = -1;
131: } else // Horizontal scan (go past).
132: {
133: // Cumulate current subtree count!
134: pageOffset += ((PdfInteger) kid.get(PdfName.Count))
135: .getValue();
136: }
137: }
138: }
139:
140: return null;
141: }
142:
143: public int indexOf(Object page) {
144: return ((Page) page).getIndex();
145: }
146:
147: public int lastIndexOf(Object page) {
148: /*
149: NOTE: Each page object should NOT appear more than once inside the same document.
150: */
151: return indexOf(page);
152: }
153:
154: public ListIterator<Page> listIterator() {
155: throw new NotImplementedException();
156: }
157:
158: public ListIterator<Page> listIterator(int index) {
159: throw new NotImplementedException();
160: }
161:
162: public Page remove(int index) {
163: Page page = get(index);
164: remove(page);
165:
166: return page;
167: }
168:
169: public Page set(int index, Page page) {
170: Page old = remove(index);
171: add(index, page);
172:
173: return old;
174: }
175:
176: public List<Page> subList(int fromIndex, int toIndex) {
177: /*
178: TODO:IMPL this implementation is incoherent with the subList contract --> move to another location!
179: */
180: ArrayList<Page> pages = new ArrayList<Page>(toIndex - fromIndex);
181: int i = fromIndex;
182: Page page = get(i);
183: while (i++ < toIndex) {
184: pages.add(page);
185: page = page.getNext();
186: }
187:
188: return pages;
189: }
190:
191: // <Collection>
192: public boolean add(Page page) {
193: return commonAddAll(-1, Arrays.asList(page));
194: }
195:
196: public boolean addAll(Collection<? extends Page> pages) {
197: return commonAddAll(-1, pages);
198: }
199:
200: public void clear() {
201: throw new NotImplementedException();
202: }
203:
204: public boolean contains(Object page) {
205: throw new NotImplementedException();
206: }
207:
208: public boolean containsAll(Collection<?> pages) {
209: throw new NotImplementedException();
210: }
211:
212: public boolean equals(Object object) {
213: throw new NotImplementedException();
214: }
215:
216: public int hashCode() {
217: throw new NotImplementedException();
218: }
219:
220: public boolean isEmpty() {
221: throw new NotImplementedException();
222: }
223:
224: public boolean remove(Object page) {
225: Page pageObj = (Page) page;
226: PdfDictionary pageData = pageObj.getBaseDataObject();
227: // Get the parent tree node!
228: PdfDirectObject parent = pageData.get(PdfName.Parent);
229: PdfDictionary parentData = (PdfDictionary) File.resolve(parent);
230: // Get the parent's page collection!
231: PdfDirectObject kids = parentData.get(PdfName.Kids);
232: PdfArray kidsData = (PdfArray) File.resolve(kids);
233: // Remove the page!
234: kidsData.remove(pageObj.getBaseObject());
235: boolean updateParent = !File.update(kids); // Try to update the page collection.
236: // Unbind the page from its parent!
237: pageData.put(PdfName.Parent, null);
238: pageObj.update();
239: // Decrementing the pages counters...
240: do {
241: // Get the page collection counter!
242: PdfDirectObject count = parentData.get(PdfName.Count);
243: IPdfNumber countData = (IPdfNumber) File.resolve(count);
244: // Decrement the counter at the current level!
245: countData.translateNumberValue(-1);
246: updateParent |= !File.update(count); // Try to update the counter.
247: // Is the parent tree node to be updated?
248: /*
249: NOTE: It avoids to update the parent tree node if its modified fields are all
250: indirect objects which perform independent updates.
251: */
252: if (updateParent) {
253: File.update(parent);
254: updateParent = false; // Reset.
255: }
256:
257: // Iterate upward!
258: parent = parentData.get(PdfName.Parent);
259: parentData = (PdfDictionary) File.resolve(parent);
260: } while (parent != null);
261:
262: return true;
263: }
264:
265: public boolean removeAll(Collection<?> pages) {
266: /*
267: NOTE: The interface contract doesn't prescribe any relation among the removing-collection's
268: items, so we cannot adopt the optimized approach of the add*(...) methods family,
269: where adding-collection's items are explicitly ordered.
270: */
271: boolean changed = false;
272: for (Object page : pages) {
273: changed |= remove(page);
274: }
275:
276: return changed;
277: }
278:
279: public boolean retainAll(Collection<?> pages) {
280: throw new NotImplementedException();
281: }
282:
283: public int size() {
284: return ((PdfInteger) getBaseDataObject().get(PdfName.Count))
285: .getValue();
286: }
287:
288: public Page[] toArray() {
289: throw new NotImplementedException();
290: }
291:
292: public <Page> Page[] toArray(Page[] pages) {
293: throw new NotImplementedException();
294: }
295:
296: // <Iterable>
297: public Iterator<Page> iterator() {
298: return new Iterator<Page>() {
299: // <class>
300: // <dynamic>
301: // <fields>
302: /**
303: Index of the next item.
304: */
305: private int index = 0;
306: /**
307: Collection size.
308: */
309: private int size = size();
310:
311: /**
312: Current level index.
313: */
314: private int levelIndex = 0;
315: /**
316: Stacked level indexes.
317: */
318: private Stack<Integer> levelIndexes = new Stack<Integer>();
319: /**
320: Current parent tree node.
321: */
322: private PdfDictionary parent = getBaseDataObject();
323: /**
324: Current child tree nodes.
325: */
326: private PdfArray kids = (PdfArray) File.resolve(parent
327: .get(PdfName.Kids));
328:
329: // </fields>
330:
331: // <interface>
332: // <public>
333: // <Iterator>
334: public boolean hasNext() {
335: return (index < size);
336: }
337:
338: public Page next() {
339: if (!hasNext())
340: throw new NoSuchElementException();
341:
342: return getNext();
343: }
344:
345: public void remove() {
346: throw new UnsupportedOperationException();
347: }
348:
349: // </Iterator>
350: // </public>
351:
352: // <private>
353: private Page getNext() {
354: /*
355: NOTE: As stated in [PDF:1.6:3.6.2], to retrieve pages is a matter of diving
356: inside a B-tree.
357: This is a special adaptation of the get() algorithm necessary to keep
358: a low overhead throughout the page tree scan (using the get() method
359: would have implied a nonlinear computational cost).
360: */
361: /*
362: NOTE: Algorithm:
363: 1. [Vertical, down] We have to go downward the page tree till we reach
364: a page (leaf node).
365: 2. [Horizontal] Then we iterate across the page collection it belongs to,
366: repeating step 1 whenever we find a subtree.
367: 3. [Vertical, up] When leaf-nodes scan is complete, we go upward solving
368: parent nodes, repeating step 2.
369: */
370: while (true) {
371: // Did we complete current page-tree-branch level?
372: if (kids.size() == levelIndex) // Page subtree complete.
373: {
374: // 3. Go upward one level.
375: // Restore node index at the current level!
376: levelIndex = levelIndexes.pop() + 1; // Next node (partially scanned level).
377: // Move upward!
378: parent = (PdfDictionary) File.resolve(parent
379: .get(PdfName.Parent));
380: kids = (PdfArray) File.resolve(parent
381: .get(PdfName.Kids));
382: } else // Page subtree incomplete.
383: {
384: PdfReference kidReference = (PdfReference) kids
385: .get(levelIndex);
386: PdfDictionary kid = (PdfDictionary) File
387: .resolve(kidReference);
388: // Is current kid a page object?
389: if (kid.get(PdfName.Type).equals(PdfName.Page)) // Page object.
390: {
391: // 2. Page found.
392: index++; // Absolute page index.
393: levelIndex++; // Current level node index.
394:
395: return new Page(kidReference);
396: } else // Page tree node.
397: {
398: // 1. Go downward one level.
399: // Save node index at the current level!
400: levelIndexes.push(levelIndex);
401: // Move downward!
402: parent = kid;
403: kids = (PdfArray) File.resolve(parent
404: .get(PdfName.Kids));
405: levelIndex = 0; // First node (new level).
406: }
407: }
408: }
409: }
410: // </private>
411: // </interface>
412: // </dynamic>
413: // </class>
414: };
415: }
416:
417: // </Iterable>
418: // </Collection>
419: // </List>
420: // </public>
421:
422: // <private>
423: /**
424: Add a collection of pages at the specified position.
425: @param index Addition position. To append, use value -1.
426: @param pages Collection of pages to add.
427: */
428: private boolean commonAddAll(int index,
429: Collection<? extends Page> pages) {
430: PdfDirectObject parent;
431: PdfDictionary parentData;
432: PdfDirectObject kids;
433: PdfArray kidsData;
434: int offset;
435: // Append operation?
436: if (index == -1) // Append operation.
437: {
438: // Get the parent tree node!
439: parent = getBaseObject();
440: parentData = getBaseDataObject();
441: // Get the parent's page collection!
442: kids = parentData.get(PdfName.Kids);
443: kidsData = (PdfArray) File.resolve(kids);
444: offset = 0; // Not used.
445: } else // Insert operation.
446: {
447: // Get the page currently at the specified position!
448: Page pivotPage = get(index);
449: // Get the parent tree node!
450: parent = pivotPage.getBaseDataObject().get(PdfName.Parent);
451: parentData = (PdfDictionary) File.resolve(parent);
452: // Get the parent's page collection!
453: kids = parentData.get(PdfName.Kids);
454: kidsData = (PdfArray) File.resolve(kids);
455: // Get the insertion's relative position within the parent's page collection!
456: offset = kidsData.indexOf(pivotPage.getBaseObject());
457: }
458:
459: // Adding the pages...
460: for (Page page : pages) {
461: // Append?
462: if (index == -1) // Append.
463: {
464: // Append the page to the collection!
465: kidsData.add(page.getBaseObject());
466: } else // Insert.
467: {
468: // Insert the page into the collection!
469: kidsData.add(offset++, page.getBaseObject());
470: }
471: // Bind the page to the collection!
472: page.getBaseDataObject().put(PdfName.Parent, parent);
473: page.update();
474: }
475: boolean updateParent = !File.update(kids); // Try to update the page collection.
476:
477: // Incrementing the pages counters...
478: do {
479: // Get the page collection counter!
480: PdfDirectObject count = parentData.get(PdfName.Count);
481: IPdfNumber countData = (IPdfNumber) File.resolve(count);
482: // Increment the counter at the current level!
483: countData.translateNumberValue(pages.size());
484: updateParent |= !File.update(count); // Try to update the page counter.
485: // Is the parent tree node to be updated?
486: /*
487: NOTE: It avoids to update the parent tree node if its modified fields are all
488: indirect objects which perform independent updates.
489: */
490: if (updateParent) {
491: File.update(parent);
492: updateParent = false; // Reset.
493: }
494:
495: // Iterate upward!
496: parent = parentData.get(PdfName.Parent);
497: parentData = (PdfDictionary) File.resolve(parent);
498: } while (parent != null);
499:
500: return true;
501: }
502: // </private>
503: // </interface>
504: // </dynamic>
505: // </class>
506: }
|