KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > etymon > pj > Pdf


1 package com.etymon.pj;
2
3 import java.io.*;
4 import java.util.*;
5 import com.etymon.pj.exception.*;
6 import com.etymon.pj.object.*;
7 import com.etymon.pj.object.pagemark.*;
8 import com.etymon.pj.util.*;
9
10 /**
11    A document representation of a PDF file.
12    @author Nassib Nassar
13  */

14 public class Pdf {
15
16     /**
17        Creates an empty PDF document.
18     */

19     public Pdf() {
20         init();
21         createEmpty();
22     }
23
24     /**
25        Creates a PDF document from an existing PDF file.
26        @param filename the name of the PDF file to read.
27        @exception IOException if an I/O error occurs.
28        @exception PjException if a PDF error occurs.
29     */

30     public Pdf(String JavaDoc filename) throws IOException, PjException {
31
32         readFromFile(filename);
33
34         // set the Producer in the Info dictionary to pj
35
// get the Info dictionary
36
PjReference infoRef;
37         try {
38             infoRef = getInfoDictionary();
39         }
40         catch (InvalidPdfObjectException e) {
41             infoRef = null;
42         }
43         PjInfo info;
44         if (infoRef == null) {
45             // create a new Info dictionary and add it
46
info = new PjInfo();
47             int infoId = registerObject(info);
48             infoRef = new PjReference(new PjNumber(infoId));
49             setInfoDictionary(infoRef);
50         } else {
51             PjDictionary d = (PjDictionary)(getObject(infoRef.getObjNumber().getInt()));
52             info = new PjInfo(d.getHashtable());
53         }
54         // set the Producer field
55
// PjInfo.setProducer(PjObject) automatically includes pj in the string
56
info.setProducer(new PjString(""));
57         }
58
59     /**
60        Writes this PDF document to a file in PDF format.
61        @param filename the name of the PDF file to create.
62        @exception IOException if an I/O error occurs.
63      */

64     public void writeToFile(String JavaDoc filename) throws IOException {
65         File file = new File(filename);
66         file.delete();
67         FileOutputStream fos = new FileOutputStream(file);
68         BufferedOutputStream bos = new BufferedOutputStream(fos);
69         writeToStream(bos);
70         bos.close();
71         fos.close();
72     }
73
74     /**
75        Writes this PDF document to a stream in PDF format.
76        @param os the stream to write to.
77        @exception IOException if an I/O error occurs.
78      */

79     public void writeToStream(OutputStream os) throws IOException {
80         // first make sure to remove the Prev field from the
81
// trailer if it is left over from having read a
82
// multi-part xref!
83
_trailer.remove(PjName.PREV);
84         // remove the ID (if there is one) from the trailer
85
_trailer.remove(PjName.ID);
86         // ok, go ahead
87
long z = 0;
88         z = z + PjObject.writeln(os, "%PDF-" + PjConst.PDF_VERSION);
89         z = z + PjObject.writeln(os, PjConst.VERSION_IN_PDF);
90         // The pj copyright notice is inserted into all PDF
91
// files output by pj; you may not remove this
92
// copyright notice.
93
z = z + PjObject.writeln(os, PjConst.COPYRIGHT_IN_PDF);
94         z = z + PjObject.writeln(os, "%\323\343\317\342");
95         PjObject obj;
96         Integer JavaDoc objnum;
97         int highest = 0;
98         int size = _objects.size();
99         long[] position = new long[size];
100         for (int x = 1; x < size; x++) {
101             if (x > highest) {
102                 highest = x;
103             }
104             obj = _objects.objectAt(x);
105             position[x] = z;
106             z = z + PjObject.writeln(os, x + " 0 obj");
107             if (obj != null) {
108                 z = z + obj.writePdf(os);
109             } else {
110                 // this is a small hack to avoid having to create "f" entries in the xref table
111
z = z + PjNumber.ZERO.writePdf(os);
112             }
113             z = z + PjObject.writeln(os, "");
114             z = z + PjObject.writeln(os, "endobj");
115         }
116         // write out xref
117
long startxref = z;
118         z = z + PjObject.writeln(os, "xref");
119         int p = 0;
120         int r;
121         Long JavaDoc g;
122         String JavaDoc s;
123         position[0] = -1;
124         int count = 0;
125         while (p <= highest) {
126             while ( (p <= highest) && (position[p] == 0) ) {
127                 p++;
128             }
129             r = p;
130             while ( (r <= highest) && (position[r] != 0) ) {
131                 r++;
132             }
133             z = z + PjObject.write(os, p + " ");
134             z = z + PjObject.writeln(os, new Integer JavaDoc(r - p));
135             for (int x = p; x < r; x++) {
136                 count++;
137                 if (x == 0) {
138                     z = z + PjObject.write(os,
139                                  "0000000000 65535 f \n");
140                 } else {
141                     s = new Long JavaDoc(position[x]).toString();
142                     for (int w = 1;
143                          (w + s.length()) <= 10; w++) {
144                         z = z + PjObject.write(os, "0");
145                     }
146                     z = z + PjObject.write(os, s);
147                     z = z + PjObject.write(os, " 00000 n \n");
148                 }
149             }
150             p = r;
151         }
152         // write out trailer
153
z = z + PjObject.writeln(os, "trailer");
154         _trailer.put(new PjName("Size"), new PjNumber(count));
155         PjDictionary trailer = new PjDictionary(_trailer);
156         z = z + trailer.writePdf(os);
157         z = z + PjObject.writeln(os, "");
158         z = z + PjObject.writeln(os, "startxref");
159         z = z + PjObject.writeln(os, new Long JavaDoc(startxref));
160         z = z + PjObject.writeln(os, "%%EOF");
161     }
162
163     /**
164        Registers a PjObject within this PDF document.
165        @param obj the PjObject to register.
166        @return the new object number of the registered PjObject.
167      */

168     public int registerObject(PjObject obj) {
169         int n = _objects.getFirstFree();
170         _objects.setObjectAt(obj, n);
171         return n;
172     }
173
174     /**
175        Registers a PjObject within this PDF document using a
176        specified object number.
177        @param obj the PjObject to register.
178        @param objectNumber the object number to register obj under.
179     */

180     public void registerObject(PjObject obj, int objectNumber) {
181         _objects.setObjectAt(obj, objectNumber);
182     }
183
184     /**
185        Adds a PjObject to a page in this PDF document.
186        @param page the page object to add to.
187        @param objectNumber the object number of the PjObject to add.
188        @exception InvalidPdfObjectException if an invalid object
189        type is encountered.
190      */

191     public void addToPage(PjPage page, int objectNumber) throws InvalidPdfObjectException {
192         PjReference objectToAdd = new PjReference(new PjNumber(objectNumber));
193         // we handle four cases of /Contents:
194
// 1) does not exist
195
// 2) reference to a stream object
196
// 3) reference to an array of references to stream objects
197
// 4) array of references to stream objects
198
// the last of these appears not to be supported by the PDF spec,
199
// however we will accept it just in case
200
PjObject contents = page.getContents();
201         if (contents == null) {
202             // set the page Contents to reference the new object
203
page.setContents(objectToAdd);
204         }
205         else if (contents instanceof PjReference) {
206             // find out whether the reference is to a stream or array
207
PjObject indirectContents =
208                 getObject(((PjReference)contents).getObjNumber().getInt());
209             if (indirectContents instanceof PjArray) {
210                 // add the new object to the existing array
211
((PjArray)indirectContents).getVector().addElement(objectToAdd);
212             }
213             else if (indirectContents instanceof PjStream) {
214                 // create a new array that includes
215
// the existing reference to the
216
// stream as well as the new object
217
// reference
218
Vector v = new Vector();
219                 v.addElement(contents);
220                 v.addElement(objectToAdd);
221                 PjArray array = new PjArray(v);
222                 // add the new array to the document
223
int arrayId = registerObject(array);
224                 // set the page Contents to reference this new array
225
page.setContents(new PjReference(new PjNumber(arrayId)));
226             }
227             else {
228                 throw new InvalidPdfObjectException(
229                     "Contents reference in page does not reference a stream or array.");
230             }
231         }
232         else if (contents instanceof PjArray) {
233             // add the new object to the existing array
234
((PjArray)contents).getVector().addElement(objectToAdd);
235         }
236         else {
237             throw new InvalidPdfObjectException("Contents object in page is not a reference or array.");
238         }
239     }
240
241     /**
242        Looks up a PjObject by its object number.
243        @param objectNumber the object number of the PjObject to retrieve.
244        @return the requested PjObject.
245      */

246     public PjObject getObject(int objectNumber) {
247         return _objects.objectAt(objectNumber);
248     }
249
250     /**
251        Dereferences a PjObject if it is a PjReference.
252        @param obj the PjObject to dereference.
253        @return the referenced PjObject if obj is a PjReference, or obj otherwise.
254      */

255     public PjObject resolve(PjObject obj) {
256         if (obj == null) {
257             return null;
258         } else {
259             if (obj instanceof PjReference) {
260                 return resolve( getObject( ((PjReference)obj).getObjNumber().getInt() ) );
261             } else {
262                 return obj;
263             }
264         }
265     }
266
267     /**
268        Determines the number of pages in this PDF document.
269        @return the number of pages in this PDF document.
270        @exception InvalidPdfObjectException if an invalid object
271        type is encountered.
272      */

273     public int getPageCount() throws InvalidPdfObjectException {
274         // the total number of pages should always be stored
275
// in the root Pages node
276
int pagesId = getRootPages();
277         PjDictionary d;
278         try {
279             d = (PjDictionary)getObject(pagesId);
280         }
281         catch (ClassCastException JavaDoc e) {
282             throw new InvalidPdfObjectException("Root pages object is not a dictionary.");
283         }
284         PjPages pages = new PjPages(d.getHashtable());
285
286         PjObject countObj = pages.getCount();
287         PjNumber count;
288         try {
289             count = (PjNumber)(resolve(countObj));
290             if (count.isInteger() == false) {
291                 throw new ClassCastException JavaDoc();
292             }
293         }
294         catch (ClassCastException JavaDoc e) {
295             throw new InvalidPdfObjectException("Count field in root pages object is not an integer.");
296         }
297         return count.getInt();
298     }
299
300     private int findPage(int pageNumber, int objectNumber, PjPages parentPages, IntCounter counter, boolean delete)
301         throws InvalidPdfObjectException {
302         PjDictionary node;
303         try {
304             node = (PjDictionary)getObject(objectNumber);
305         }
306         catch (ClassCastException JavaDoc e) {
307             throw new InvalidPdfObjectException("Object in page tree is not a dictionary.");
308         }
309         // figure out whether node is a Page or Pages object
310
PjName type;
311         try {
312             type = (PjName)(node.getHashtable().get(PjName.TYPE));
313         }
314         catch (ClassCastException JavaDoc e) {
315             throw new InvalidPdfObjectException(
316                 "Type field in dictionary in page tree is not a name object.");
317         }
318         if (type.equals(PjName.PAGES)) {
319             PjPages pages = new PjPages(node.getHashtable());
320             PjArray kids;
321             try {
322                 kids = (PjArray)(resolve((PjObject)(pages.getKids())));
323             }
324             catch (ClassCastException JavaDoc e) {
325                 throw new InvalidPdfObjectException("Kids field in pages object is not an array.");
326             }
327             if (kids != null) {
328                 Vector v = kids.getVector();
329                 int size = v.size();
330                 PjReference nodeRef;
331                 int found;
332                 for (int x = 0; x < size; x++) {
333                     try {
334                         nodeRef = (PjReference)(v.elementAt(x));
335                     }
336                     catch (ClassCastException JavaDoc e) {
337                         throw new InvalidPdfObjectException(
338                             "Object is kids array in pages object is not an indirect reference.");
339                     }
340                     found = findPage(pageNumber, nodeRef.getObjNumber().getInt(),
341                              pages, counter, delete);
342                     if (found != -1) {
343                         if (delete) {
344                             // decrement the page count in this Pages node
345
PjNumber count;
346                             try {
347                                 count = (PjNumber)(resolve((PjObject)(pages.getCount())));
348                                 if (count.isInteger() == false) {
349                                     throw new ClassCastException JavaDoc();
350                                 }
351                             }
352                             catch (ClassCastException JavaDoc e) {
353                                 throw new InvalidPdfObjectException(
354                                     "Count field in pages object is not an integer.");
355                             }
356                             pages.setCount(new PjNumber(count.getInt() - 1));
357                         }
358                         return found;
359                     }
360                 }
361             }
362             return -1;
363         }
364         if (type.equals(PjName.PAGE)) {
365             counter.inc();
366             if (counter.value() == pageNumber) {
367                 if (delete) {
368                     // remove the page from the kids array
369
((PjArray)(parentPages.getKids())).getVector().removeElement(
370                         new PjReference(new PjNumber(objectNumber)));
371                 }
372                 return objectNumber;
373             } else {
374                 return -1;
375             }
376         }
377         return -1;
378     }
379
380     /**
381        Looks up a page in this document by page number.
382        @param pageNumber the page number. Pages are numbered
383        starting with 1.
384        @return the object number of the identified Page object.
385        @exception IndexOutOfBoundsException if an invalid page
386        number was given.
387        @exception InvalidPdfObjectException if an invalid object
388        type is encountered.
389     */

390     public int getPage(int pageNumber) throws IndexOutOfBoundsException JavaDoc, InvalidPdfObjectException {
391         if (pageNumber < 1) {
392             throw new IndexOutOfBoundsException JavaDoc("Page number " + pageNumber + " is not >= 1.");
393         }
394         IntCounter counter = new IntCounter(0);
395         int found = findPage(pageNumber, getRootPages(), null, counter, false);
396         if (found == -1) {
397             if (pageNumber > getPageCount()) {
398                 throw new IndexOutOfBoundsException JavaDoc("Page number " + pageNumber + " is not <= " +
399                                     getPageCount() + ".");
400             } else {
401                 throw new InvalidPdfObjectException("Page number " + pageNumber +
402                                    " not found; ran out of pages.");
403             }
404         } else {
405             return found;
406         }
407     }
408
409     /**
410        Deletes a page in this document by page number. The page
411        is deleted by removing the reference to it from the page
412        tree; however, no objects are actually deleted from the
413        document.
414        @param pageNumber the page number. Pages are numbered
415        starting with 1.
416        @return the object number of the deleted Page object.
417        @exception IndexOutOfBoundsException if an invalid page
418        number was given.
419        @exception InvalidPdfObjectException if an invalid object
420        type is encountered.
421     */

422     public int deletePage(int pageNumber) throws IndexOutOfBoundsException JavaDoc, InvalidPdfObjectException {
423         if (pageNumber < 1) {
424             throw new IndexOutOfBoundsException JavaDoc("Page number " + pageNumber + " is not >= 1.");
425         }
426         IntCounter counter = new IntCounter(0);
427         int found = findPage(pageNumber, getRootPages(), null, counter, true);
428         if (found == -1) {
429             if (pageNumber > getPageCount()) {
430                 throw new IndexOutOfBoundsException JavaDoc("Page number " + pageNumber + " is not <= " +
431                                     getPageCount() + ".");
432             } else {
433                 throw new InvalidPdfObjectException("Page number " + pageNumber +
434                                    " not found; ran out of pages.");
435             }
436         } else {
437             return found;
438         }
439     }
440     
441     // we should split this out so that once we find the parent
442
// node, we call a method to add the new page; we'll need to
443
// use it in insertPage() also.
444
/**
445        Appends a PjPage to the end of this PDF document.
446        @param objectNumber the object number of the PjPage to append.
447        @return the new object number of the appended PjPage. */

448     public int appendPage(int objectNumber) {
449         // we do this the quickest way: go to the root Pages
450
// node and add a link to the page at the top level.
451
// this ignores the issue of maintaining a balanced
452
// tree; we probably need some tree algorithms to deal
453
// with general functions to manipulate the page tree.
454
PjReference catalogRef = (PjReference)(_trailer.get(PjName.ROOT));
455         PjDictionary catalog = (PjDictionary)getObject(catalogRef.getObjNumber().getInt());
456         PjReference pagesRef = (PjReference)(catalog.getHashtable().get(PjName.PAGES));
457         PjDictionary pages = (PjDictionary)getObject(pagesRef.getObjNumber().getInt());
458         // we want to add the new page to the Kids array
459
PjArray kids = (PjArray)(pages.getHashtable().get(PjName.KIDS));
460         if (kids == null) {
461             kids = new PjArray();
462             pages.getHashtable().put(PjName.KIDS, kids);
463         }
464         kids.getVector().addElement( new PjReference(new PjNumber(objectNumber)) );
465         // also need to set the parent
466
PjPage page = (PjPage)getObject(objectNumber);
467         page.setParent(pagesRef);
468         // while we're here we need to increment the page count
469
PjObject countObj = (PjObject)(pages.getHashtable().get(PjName.COUNT));
470         PjNumber count = (PjNumber)resolve(countObj);
471         int newCount = count.getInt() + 1;
472         pages.getHashtable().put(PjName.COUNT, new PjNumber(newCount));
473         return newCount;
474     }
475
476     /**
477        Appends the pages of a PDF document to this document. Note
478        that this does not clone the other document but simply
479        includes references to its objects. Therefore the other
480        document should be discarded immediately after a call to
481        this method, otherwise you could get very strange results.
482        @param pdf the PDF document to append.
483        @exception InvalidPdfObjectException if an invalid object
484        type is encountered in either document. */

485     public void appendPdfDocument(Pdf pdf) throws InvalidPdfObjectException {
486
487         // first gather some information
488

489         // look up AcroForm in other document, and extract the
490
// array of references to field objects; so that we
491
// can add them to the field array in this document
492
int otherCatalogId = pdf.getCatalog();
493         PjCatalog otherCatalog;
494         try {
495             otherCatalog = (PjCatalog)(pdf.getObject(otherCatalogId));
496         }
497         catch (ClassCastException JavaDoc e) {
498             throw new InvalidPdfObjectException("Catalog object is not a dictionary.");
499         }
500         // get the AcroForm
501
PjDictionary otherAcroForm;
502         try {
503             otherAcroForm = (PjDictionary)(pdf.resolve(otherCatalog.getAcroForm()));
504         }
505         catch (ClassCastException JavaDoc e) {
506             throw new InvalidPdfObjectException("AcroForm object is not a dictionary.");
507         }
508         Vector otherFieldsV = null;
509         if (otherAcroForm != null) {
510             PjArray otherFields = (PjArray)(otherAcroForm.getHashtable().get(PjName.FIELDS));
511             if (otherFields != null) {
512                 otherFieldsV = otherFields.getVector();
513             }
514         }
515         
516         // locate the root Pages node in the other document
517
int pagesId = pdf.getRootPages();
518         PjDictionary d;
519         try {
520             d = (PjDictionary)(pdf.getObject(pagesId));
521         }
522         catch (ClassCastException JavaDoc e) {
523             throw new InvalidPdfObjectException("Root pages object is not a dictionary.");
524         }
525         PjPages pages = new PjPages(d.getHashtable());
526
527         // get the page count of the other document
528
int pageCount = pdf.getPageCount();
529
530         // locate the root Pages node in this document
531
int thisPagesId = getRootPages();
532         try {
533             d = (PjDictionary)(getObject(thisPagesId));
534         }
535         catch (ClassCastException JavaDoc e) {
536             throw new InvalidPdfObjectException("Root pages object is not a dictionary.");
537         }
538         PjPages thisPages = new PjPages(d.getHashtable());
539
540         // at this point we haven't changed anything
541

542         // register all the objects with this document,
543
// building a mapping table as we go along
544
int id;
545         PjObject obj;
546         int pagesIdNew = -1;
547         int size = pdf._objects.size();
548         Hashtable map = new Hashtable(size);
549         for (int x = 1; x < size; x++) {
550             obj = pdf._objects.objectAt(x);
551             if (obj != null) {
552                 id = registerObject(obj);
553                 // new object number for the root Pages node
554
if (x == pagesId) {
555                     pagesIdNew = id;
556                 }
557                 // add mapping
558
map.put(new PjNumber(x),
559                     new PjReference(new PjNumber(id)));
560             }
561         }
562
563         // renumber objects
564
// enumerate map as a way of enumerating the objects we added
565
for (Enumeration m = map.keys(); m.hasMoreElements();) {
566             // get the object number of an object we added
567
id = ((PjReference)(map.get(m.nextElement()))).getObjNumber().getInt();
568             obj = _objects.objectAt(id);
569             if (obj instanceof PjReference) {
570                 registerObject((PjReference)(map.get(((PjReference)obj).getObjNumber())), id);
571             } else {
572                 obj.renumber(map);
573             }
574         }
575         
576         // create a new root Pages node that includes the root nodes from the two documents
577
PjPages newPages = new PjPages();
578         int newPagesId = registerObject(newPages);
579         Vector v = new Vector();
580         v.addElement(new PjReference(new PjNumber(thisPagesId)));
581         v.addElement(new PjReference(new PjNumber(pagesIdNew)));
582         newPages.setKids(new PjArray(v));
583         newPages.setCount(new PjNumber(getPageCount() + pageCount));
584         // set the old root nodes' Parent to point to the new root node
585
PjReference newPagesRef = new PjReference(new PjNumber(newPagesId));
586         thisPages.setParent(newPagesRef);
587         pages.setParent(newPagesRef);
588
589         // update the catalog to point to the new root Pages node
590
int catalogId = getCatalog();
591         PjCatalog catalog;
592         try {
593             catalog = (PjCatalog)(getObject(catalogId));
594         }
595         catch (ClassCastException JavaDoc e) {
596             throw new InvalidPdfObjectException("Catalog object is not a dictionary.");
597         }
598         catalog.setPages(newPagesRef);
599
600         // merge AcroForm from the two documents
601
PjDictionary acroForm = (PjDictionary)(resolve(catalog.getAcroForm()));
602         if (acroForm == null) {
603             // use the other document's AcroForm
604
PjDictionary otherAf = (PjDictionary)(otherCatalog.getAcroForm());
605             if (otherAcroForm != null) {
606                 catalog.setAcroForm(otherAf);
607             }
608         } else {
609             // add the fields extracted from other document's AcroForm
610
// locate the fields array
611
PjArray fields = (PjArray)(acroForm.getHashtable().get(PjName.FIELDS));
612             if ( (otherFieldsV != null) && (fields != null) ) {
613                 Vector fieldsV = fields.getVector();
614                 int otherFieldsV_n = otherFieldsV.size();
615                 for (int x = 0; x < otherFieldsV_n; x++) {
616                     fieldsV.addElement(otherFieldsV.elementAt(x));
617                 }
618             }
619         }
620         
621     }
622
623     /**
624        Looks up the Catalog object in this document.
625        @return the object number of the Catalog object.
626        @exception InvalidPdfObjectException if an invalid object
627        type is encountered.
628      */

629     public int getCatalog() throws InvalidPdfObjectException {
630         PjReference catalogRef;
631         try {
632             catalogRef = (PjReference)(_trailer.get(PjName.ROOT));
633         }
634         catch (ClassCastException JavaDoc e) {
635             throw new InvalidPdfObjectException("Root field in trailer is not an indirect reference.");
636         }
637         return catalogRef.getObjNumber().getInt();
638     }
639     
640     /**
641        Looks up the root Pages object of this document's Pages tree.
642        @return the object number of the root Pages object.
643        @exception InvalidPdfObjectException if an invalid object
644        type is encountered.
645      */

646     public int getRootPages() throws InvalidPdfObjectException {
647         // we find the root Pages node via the Catalog object
648
int catalogId = getCatalog();
649         PjDictionary catalog;
650         try {
651             catalog = (PjDictionary)getObject(catalogId);
652         }
653         catch (ClassCastException JavaDoc e) {
654             throw new InvalidPdfObjectException("Catalog is not a dictionary.");
655         }
656         PjReference pagesRef;
657         try {
658             pagesRef = (PjReference)(catalog.getHashtable().get(PjName.PAGES));
659         }
660         catch (ClassCastException JavaDoc e) {
661             throw new InvalidPdfObjectException("Pages field in catalog is not an indirect reference.");
662         }
663         return pagesRef.getObjNumber().getInt();
664     }
665     
666     /**
667        Looks up the Info dictionary within this document's trailer.
668        The Info dictionary contains general information about the
669        document.
670        @return a reference to the Info dictionary, or null if no
671        Info field is present in the trailer.
672        @exception InvalidPdfObjectException if the Info field in
673        the trailer is not a reference (PjReference) object.
674     */

675     public PjReference getInfoDictionary() throws InvalidPdfObjectException {
676         PjReference r;
677         try {
678             r = (PjReference)(_trailer.get(PjName.INFO));
679         }
680         catch (ClassCastException JavaDoc e) {
681             throw new InvalidPdfObjectException("Info field is not an indirect reference.");
682         }
683         return r;
684     }
685     
686     /**
687        Sets the Info dictionary within this document's trailer.
688        @param ref a reference to the Info dictionary.
689     */

690     public void setInfoDictionary(PjReference ref) {
691         _trailer.put(PjName.INFO, ref);
692     }
693
694     /**
695        Looks up the Encrypt dictionary within this document's trailer.
696        The Encrypt dictionary contains information for decrypting a
697        document.
698        @return the Encrypt dictionary, or null if no Encrypt field is
699        present in the trailer.
700        @exception InvalidPdfObjectException if the Encrypt field in
701        the trailer is not a dictionary (PjDictionary) object.
702     */

703     public PjDictionary getEncryptDictionary() throws InvalidPdfObjectException {
704         PjDictionary d;
705         try {
706             d = (PjDictionary)(resolve((PjObject)(_trailer.get(PjName.ENCRYPT))));
707         }
708         catch (ClassCastException JavaDoc e) {
709             throw new InvalidPdfObjectException("Encrypt field is not a dictionary.");
710         }
711         return d;
712     }
713     
714     /**
715        Sets the Encrypt dictionary within this document's trailer.
716        @param ref a reference to the Encrypt dictionary.
717     */

718     public void setEncryptDictionary(PjReference ref) {
719         _trailer.put(PjName.ENCRYPT, ref);
720     }
721
722     /**
723        Sets the Encrypt dictionary within this document's trailer.
724        @param dict the Encrypt dictionary.
725     */

726     public void setEncryptDictionary(PjDictionary dict) {
727         _trailer.put(PjName.ENCRYPT, dict);
728     }
729
730     /**
731        Returns a clone of a pages node such that all inherited
732        attributes of the given pages node are made explicit. For
733        example, if MediaBox is not defined in the given pages
734        node, this method ascends the pages tree (via the Parent
735        reference) looking for an ancestor node that does contain a
736        value for MediaBox; if it finds one, it assigns that value
737        in the cloned (returned) pages node. This is done for all
738        inheritable attributes.
739        @param node a pages node for which inherited attributes are
740        to be retrieved.
741        @return a cloned copy of the given pages node with actual
742        values substituted for all inherited attributes.
743        @exception InvalidPdfObjectException if an invalid object
744        type is encountered.
745     */

746     public PjPagesNode inheritPageAttributes(PjPagesNode node) throws InvalidPdfObjectException {
747         PjPagesNode newNode;
748         try {
749             newNode = (PjPagesNode)(node.clone());
750         }
751         catch (CloneNotSupportedException JavaDoc e) {
752             throw new InvalidPdfObjectException(e.getMessage());
753         }
754         Hashtable ht = newNode.getHashtable();
755         PjObject parentRef = newNode.getParent();
756         while (parentRef != null) {
757             PjObject parentObj = resolve(parentRef);
758             if ( ! (parentObj instanceof PjPagesNode) ) {
759                 throw new InvalidPdfObjectException("Ancestor of pages node is not a pages node.");
760             }
761             PjPagesNode parent = (PjPagesNode)parentObj;
762             inheritPageAttributesCollapse(PjName.MEDIABOX, ht, newNode, parent);
763             inheritPageAttributesCollapse(PjName.RESOURCES, ht, newNode, parent);
764             inheritPageAttributesCollapse(PjName.CROPBOX, ht, newNode, parent);
765             inheritPageAttributesCollapse(PjName.ROTATE, ht, newNode, parent);
766             inheritPageAttributesCollapse(PjName.DUR, ht, newNode, parent);
767             inheritPageAttributesCollapse(PjName.HID, ht, newNode, parent);
768             inheritPageAttributesCollapse(PjName.TRANS, ht, newNode, parent);
769             inheritPageAttributesCollapse(PjName.AA, ht, newNode, parent);
770             parentRef = parent.getParent();
771         }
772         return newNode;
773     }
774
775     /**
776        Returns a clone of a field node such that all inherited
777        attributes of the given field node are made explicit. For
778        example, if the V key is not defined in the given field
779        node, this method ascends the field tree (via the Parent
780        reference) looking for an ancestor node that does contain a
781        value for the V key; if it finds one, it assigns that value
782        in the cloned (returned) field node. This is done for all
783        inheritable attributes.
784        @param node a field node for which inherited attributes are
785        to be retrieved.
786        @return a cloned copy of the given field node with actual
787        values substituted for all inherited attributes.
788        @exception InvalidPdfObjectException if an invalid object
789        type is encountered.
790     */

791     public PjDictionary inheritFieldAttributes(PjDictionary node) throws InvalidPdfObjectException {
792         PjDictionary newNode;
793         try {
794             newNode = (PjDictionary)(node.clone());
795         }
796         catch (CloneNotSupportedException JavaDoc e) {
797             throw new InvalidPdfObjectException(e.getMessage());
798         }
799         Hashtable ht = newNode.getHashtable();
800         PjObject parentRef = (PjObject)(newNode.getHashtable().get(PjName.PARENT));
801         while (parentRef != null) {
802             PjObject parentObj = resolve(parentRef);
803             if ( ! (parentObj instanceof PjDictionary) ) {
804                 throw new InvalidPdfObjectException("Ancestor of field node is not a dictionary.");
805             }
806             PjDictionary parent = (PjDictionary)parentObj;
807             inheritFieldAttributesCollapse(PjName.FT, ht, newNode, parent);
808             inheritFieldAttributesCollapse(PjName.V, ht, newNode, parent);
809             inheritFieldAttributesCollapse(PjName.DV, ht, newNode, parent);
810             inheritFieldAttributesCollapse(PjName.FF, ht, newNode, parent);
811             inheritFieldAttributesCollapse(PjName.DR, ht, newNode, parent);
812             inheritFieldAttributesCollapse(PjName.DA, ht, newNode, parent);
813             inheritFieldAttributesCollapse(PjName.Q, ht, newNode, parent);
814             inheritFieldAttributesCollapse(PjName.OPT, ht, newNode, parent);
815             inheritFieldAttributesCollapse(PjName.TOPINDEX, ht, newNode, parent);
816             inheritFieldAttributesCollapse(PjName.MAXLEN, ht, newNode, parent);
817             parentRef = (PjObject)(parent.getHashtable().get(PjName.PARENT));
818         }
819         return newNode;
820     }
821
822     /**
823        Returns the largest object number in the list of registered
824        PjObjects. This is useful mainly for functions that need
825        to run through the list and process each object, because
826        this provides the maximum object number they need to
827        examine. The object number may not currently be assigned
828        to an object, but probably was at some point in the past.
829        @return the size of the object list.
830     */

831     public int getMaxObjectNumber() {
832         return Math.max(_objects.size() - 1, 0);
833     }
834
835
836     public Vector getFields() throws InvalidPdfObjectException {
837
838         Vector fieldList = new Vector();
839
840         // get the Catalog
841
int catalogId = getCatalog();
842         PjCatalog catalog;
843         try {
844             catalog = (PjCatalog)(getObject(catalogId));
845         }
846         catch (ClassCastException JavaDoc e) {
847             throw new InvalidPdfObjectException("Catalog object is not a dictionary.");
848         }
849
850         // get the AcroForm
851
PjDictionary acroForm;
852         try {
853             acroForm = (PjDictionary)(resolve(catalog.getAcroForm()));
854         }
855         catch (ClassCastException JavaDoc e) {
856             throw new InvalidPdfObjectException("AcroForm object is not a dictionary.");
857         }
858
859         if (acroForm == null) {
860             return fieldList;
861         }
862         
863         // for now we assume that all root fields have no
864
// children; so we treat Fields as an array
865

866         // get Fields array
867
PjArray fields = (PjArray)(acroForm.getHashtable().get(PjName.FIELDS));
868         if (fields == null) {
869             return fieldList;
870         }
871         Vector fieldsV = fields.getVector();
872         
873         // loop through all fields
874
int fieldsV_n = fieldsV.size();
875         for (int x = 0; x < fieldsV_n; x++) {
876
877             // get the field object
878
PjReference fieldRef;
879             try {
880                 fieldRef = (PjReference)(fieldsV.elementAt(x));
881             }
882             catch (ClassCastException JavaDoc e) {
883                 throw new InvalidPdfObjectException("Fields array element is not a reference.");
884             }
885
886             getFieldsAddField(fieldList, fieldRef);
887
888         }
889
890         return fieldList;
891         
892     }
893
894
895     private void getFieldsAddField(Vector fieldList, PjReference fieldRef)
896         throws InvalidPdfObjectException {
897
898         // resolve field reference
899
PjDictionary field;
900         try {
901             field = (PjDictionary)(resolve(fieldRef));
902         }
903         catch (ClassCastException JavaDoc e) {
904             throw new InvalidPdfObjectException("Field object is not a dictionary.");
905         }
906
907         Hashtable fieldHt = field.getHashtable();
908
909         // add the field to the list
910
fieldList.addElement(field);
911         
912         // check if there are any kids
913
PjArray kids;
914         try {
915             kids = (PjArray)(resolve((PjObject)(fieldHt.get(PjName.KIDS))));
916         }
917         catch (ClassCastException JavaDoc e) {
918             throw new InvalidPdfObjectException("Kids object is not an array.");
919         }
920         
921         // if there are kids, descend the tree
922
if (kids != null) {
923             Vector kidsV = kids.getVector();
924             int kidsV_n = kidsV.size();
925             for (int x = 0; x < kidsV_n; x++) {
926
927                 // get the field object
928
PjReference fieldRef2;
929                 try {
930                     fieldRef2 = (PjReference)(kidsV.elementAt(x));
931                 }
932                 catch (ClassCastException JavaDoc e) {
933                     throw new InvalidPdfObjectException("Kids array element is not a reference.");
934                 }
935                 
936                 getFieldsAddField(fieldList, fieldRef2);
937
938             }
939         }
940         
941     }
942
943     
944     public void updateFieldValue(PjDictionary origField, PjDictionary field, String JavaDoc value)
945         throws PdfFormatException, InvalidPdfObjectException {
946
947         Hashtable origFieldHt = origField.getHashtable();
948
949         Hashtable fieldHt = field.getHashtable();
950
951         // store old value for use in search/replace within appeareances stream(s)
952
PjString oldValue = (PjString)(fieldHt.get(PjName.V));
953             
954         PjString valueString = new PjString(value);
955         origFieldHt.put(PjName.V, valueString);
956         origFieldHt.put(PjName.DV, valueString);
957         
958         // determine quadding
959
PjNumber q = (PjNumber)(resolve((PjObject)(fieldHt.get(PjName.Q))));
960         boolean leftJustified = false;
961         boolean centered = false;
962         boolean rightJustified = false;
963         if (q == null) {
964             leftJustified = true;
965         } else {
966             switch (q.getInt()) {
967             case 1:
968                 centered = true;
969                 break;
970             case 2:
971                 rightJustified = true;
972                 break;
973             default:
974                 leftJustified = true;
975             }
976         }
977
978         PjDictionary ap = (PjDictionary)(resolve((PjObject)(fieldHt.get(PjName.AP))));
979         if (ap != null) {
980             Hashtable apHt = ap.getHashtable();
981             PjObject apnObj = (PjObject)(apHt.get(PjName.N));
982             int apnId;
983             PjReference apnRef;
984             PjObject apn;
985             PjDictionary apnDict;
986             byte[] apnBuffer;
987             if (apnObj instanceof PjReference) {
988                 // it's an indirect object
989
apnRef = (PjReference)apnObj;
990                 apnId = apnRef.getObjNumber().getInt();
991                 apn = resolve(apnRef);
992             } else {
993                 // if it's not an indirect object, let's make it indirect
994
apnId = registerObject(apnObj);
995                 apnRef = new PjReference(new PjNumber(apnId));
996                 apHt.put(PjName.N, apnRef);
997                 apn = apnObj;
998             }
999
1000            // "/C" = center text
1001
// this assumes Courier 10 pt; we can add support
1002
// for others if needed.
1003
// it also assumes a page width of 8.5"; this also could
1004
// be adjusted or read from the document.
1005

1006            float rectX1 = 0;
1007            float rectX2 = 0;
1008            float rectWidth = 0;
1009            if (centered) {
1010                // adjust RECT
1011
PjRectangle rect =
1012                    (PjRectangle)(fieldHt.get(PjName.RECT));
1013                rectX1 = rect.getLowerLeftX().getFloat();
1014                rectX2 = rect.getUpperRightX().getFloat();
1015                rectWidth = rectX2 - rectX1;
1016            }
1017
1018            if ( (apn != null) && (apn instanceof PjStream) ) {
1019                // if centered: remove any text matrix adjustments.
1020
// get page mark operators
1021
Vector pmVector = new StreamParser().parse(
1022                    ((PjStream)(apn)).flateDecompress());
1023                if (oldValue != null) {
1024                    replaceTextData(pmVector, oldValue, valueString);
1025                }
1026                if (centered) {
1027                    adjustTextMatrixX(pmVector, rectWidth);
1028                }
1029                // reconstruct stream from modified pmVector
1030
ByteArrayOutputStream baos =
1031                    new ByteArrayOutputStream();
1032                for (int pmX = 0; pmX < pmVector.size(); pmX++) {
1033                    PageMark pm = (PageMark)(pmVector.elementAt(pmX));
1034                    try {
1035                        pm.writePdf(baos);
1036                    }
1037                    catch (IOException e) {
1038                        e.printStackTrace();
1039                    }
1040                }
1041                byte[] ba = baos.toByteArray();
1042                // register new (modified) stream in pdf document
1043
registerObject(new PjStream(((PjStream)(apn)).getStreamDictionary(), ba), apnId);
1044                
1045            }
1046        }
1047                
1048    }
1049    
1050
1051    // used exclusively by updateFieldValue()
1052
private static void replaceTextData(Vector pmVector, PjString oldText, PjString newText) {
1053        // this method replaces text data oldS with newS
1054

1055        int pmX = pmVector.size();
1056
1057        // no particular reason for searching backwards; just
1058
// because this was adapted from clearTextMatrixX()
1059
while (pmX > 0) {
1060            
1061            pmX--;
1062            PageMark pm = (PageMark)(pmVector.elementAt(pmX));
1063            
1064            if (pm instanceof XTj) {
1065                XTj tj = (XTj)pm;
1066                if (tj.getText().equals(oldText)) {
1067                    XTj newTj = new XTj(newText);
1068                    pmVector.setElementAt(newTj, pmX);
1069                }
1070            }
1071            
1072        }
1073    }
1074
1075    
1076    // used exclusively by updateFieldValue()
1077
private static void adjustTextMatrixX(Vector pmVector, float rectWidth) {
1078        // this method examines the last text matrix in
1079
// pmVector and sets the X matrix value in order to
1080
// center the text written by the subsequent Tj
1081
// operator.
1082

1083        int pmX = pmVector.size();
1084        float textWidth = 0;
1085        float rectCenter = rectWidth / 2;
1086        
1087        while (pmX > 0) {
1088            
1089            pmX--;
1090            PageMark pm = (PageMark)(pmVector.elementAt(pmX));
1091            
1092            if (pm instanceof XTj) {
1093                XTj tj = (XTj)pm;
1094                textWidth = tj.getText().getString().length() * 6;
1095            }
1096            
1097            if (pm instanceof XTm) {
1098                float newX = rectCenter - (textWidth / 2);
1099                if (newX < 0) {
1100                    newX = 0;
1101                }
1102                XTm tm = (XTm)pm;
1103                XTm newTm = new XTm(
1104                    tm.getA(),
1105                    tm.getB(),
1106                    tm.getC(),
1107                    tm.getD(),
1108                    new PjNumber(newX),
1109                    tm.getY());
1110                pmVector.setElementAt(newTm, pmX);
1111                pmX = 0; // Tm found, now we can stop
1112
}
1113            
1114        }
1115    }
1116
1117    
1118    // used exclusively by updateFieldValue()
1119
private static void clearTextMatrixX(Vector pmVector) {
1120        // this method examines the last text matrix in
1121
// pmVector and sets the X matrix value to 0.
1122

1123        int pmX = pmVector.size();
1124
1125        while (pmX > 0) {
1126            
1127            pmX--;
1128            PageMark pm = (PageMark)(pmVector.elementAt(pmX));
1129            
1130            if (pm instanceof XTm) {
1131                XTm tm = (XTm)pm;
1132                XTm newTm = new XTm(
1133                    tm.getA(),
1134                    tm.getB(),
1135                    tm.getC(),
1136                    tm.getD(),
1137                    PjNumber.ZERO,
1138                    tm.getY());
1139                pmVector.setElementAt(newTm, pmX);
1140                pmX = 0; // Tm found, now we can stop
1141
}
1142            
1143        }
1144    }
1145
1146    
1147    private void inheritPageAttributesCollapse(PjName name, Hashtable ht, PjPagesNode newNode, PjPagesNode parent) {
1148        if (ht.get(name) == null) {
1149            Object JavaDoc obj = parent.getHashtable().get(name);
1150            if (obj != null) {
1151                ht.put(name, obj);
1152            }
1153        }
1154    }
1155
1156
1157    private void inheritFieldAttributesCollapse(PjName name, Hashtable ht, PjDictionary newNode, PjDictionary parent) {
1158        if (ht.get(name) == null) {
1159            Object JavaDoc obj = parent.getHashtable().get(name);
1160            if (obj != null) {
1161                ht.put(name, obj);
1162            }
1163        }
1164    }
1165
1166
1167    private void init() {
1168        _objects = new PjObjectVector();
1169        _trailer = new Hashtable();
1170    }
1171
1172    // this creates the minimal data structures for an empty Pdf object
1173
// (a single blank page)
1174
private void createEmpty() {
1175        // make a ProcSet
1176
Vector v = new Vector();
1177        v.addElement(PjName.PDF);
1178        v.addElement(PjName.TEXT);
1179        PjProcSet procSet = new PjProcSet(v);
1180        int procSetId = registerObject(procSet);
1181        // make a Resources dictionary
1182
PjResources resources = new PjResources();
1183        resources.setProcSet(new PjReference(new PjNumber(procSetId)));
1184        int resourcesId = registerObject(resources);
1185        // make a MediaBox rectangle
1186
PjRectangle mediaBox = new PjRectangle();
1187        mediaBox.setLowerLeftX(PjNumber.ZERO);
1188        mediaBox.setLowerLeftY(PjNumber.ZERO);
1189        mediaBox.setUpperRightX(new PjNumber(612));
1190        mediaBox.setUpperRightY(new PjNumber(792));
1191        // make a blank Page
1192
PjPage page = new PjPage();
1193        int pageId = registerObject(page);
1194        // make the kids array
1195
v = new Vector();
1196        v.addElement(new PjReference(new PjNumber(pageId)));
1197        PjArray kids = new PjArray(v);
1198        // make the root Pages node
1199
PjPages root = new PjPages();
1200        root.setResources(new PjReference(new PjNumber(resourcesId)));
1201        root.setMediaBox(mediaBox);
1202        root.setCount(PjNumber.ONE);
1203        root.setKids(kids);
1204        int rootId = registerObject(root);
1205        // we have to go back and set the blank page's parent to root
1206
page.setParent(new PjReference(new PjNumber(rootId)));
1207        // make the Catalog
1208
PjCatalog catalog = new PjCatalog();
1209        catalog.setPages(new PjReference(new PjNumber(rootId)));
1210        int catalogId = registerObject(catalog);
1211        // set Root in the trailer to point to the Catalog
1212
_trailer.put(PjName.ROOT, new PjReference(new PjNumber(catalogId)));
1213        // create an Info dictionary with default fields
1214
PjInfo info = new PjInfo();
1215        info.setCreator(PjConst.COPYRIGHT_IN_INFO);
1216        // need to add CreationDate and ModDate here, once we implement PjDate(Date)
1217
int infoId = registerObject(info);
1218        _trailer.put(PjName.INFO, new PjReference(new PjNumber(infoId)));
1219    }
1220
1221    private void readFromFile(String JavaDoc filename) throws IOException,
1222        PjException {
1223        init();
1224        RandomAccessFile raf = new RandomAccessFile(filename,
1225                                "r");
1226        try {
1227            PdfParser.getObjects(this, raf);
1228        }
1229        finally {
1230            // make an attempt to close the file
1231
try {
1232                raf.close();
1233            }
1234            catch (IOException e) {
1235            }
1236        }
1237    }
1238
1239    protected PjObjectVector _objects;
1240    protected Hashtable _trailer;
1241
1242}
1243
Popular Tags