001: /**
002: * Copyright (c) 2003-2006, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdfwriter;
031:
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.io.OutputStream;
035: import java.security.MessageDigest;
036: import java.security.NoSuchAlgorithmException;
037: import java.text.DecimalFormat;
038: import java.text.NumberFormat;
039: import java.util.ArrayList;
040: import java.util.Collections;
041: import java.util.HashSet;
042: import java.util.Hashtable;
043: import java.util.Iterator;
044: import java.util.List;
045: import java.util.Locale;
046: import java.util.Map;
047: import java.util.Set;
048:
049: import org.pdfbox.cos.COSArray;
050: import org.pdfbox.cos.COSBase;
051: import org.pdfbox.cos.COSBoolean;
052: import org.pdfbox.cos.COSDictionary;
053: import org.pdfbox.cos.COSDocument;
054: import org.pdfbox.cos.COSFloat;
055: import org.pdfbox.cos.COSInteger;
056: import org.pdfbox.cos.COSName;
057: import org.pdfbox.cos.COSNull;
058: import org.pdfbox.cos.COSObject;
059: import org.pdfbox.cos.COSStream;
060: import org.pdfbox.cos.COSString;
061: import org.pdfbox.cos.ICOSVisitor;
062: import org.pdfbox.exceptions.COSVisitorException;
063: import org.pdfbox.exceptions.CryptographyException;
064: import org.pdfbox.pdmodel.PDDocument;
065: import org.pdfbox.pdmodel.encryption.SecurityHandler;
066: import org.pdfbox.persistence.util.COSObjectKey;
067:
068: /**
069: * this class acts on a in-memory representation of a pdf document.
070: *
071: * todo no support for incremental updates
072: * todo single xref section only
073: * todo no linearization
074: *
075: * @author Michael Traut
076: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
077: * @version $Revision: 1.36 $
078: */
079: public class COSWriter implements ICOSVisitor {
080: /**
081: * The dictionary open token.
082: */
083: public static final byte[] DICT_OPEN = "<<".getBytes();
084: /**
085: * The dictionary close token.
086: */
087: public static final byte[] DICT_CLOSE = ">>".getBytes();
088: /**
089: * space character.
090: */
091: public static final byte[] SPACE = " ".getBytes();
092: /**
093: * The start to a PDF comment.
094: */
095: public static final byte[] COMMENT = "%".getBytes();
096:
097: /**
098: * The output version of the PDF.
099: */
100: public static final byte[] VERSION = "PDF-1.4".getBytes();
101: /**
102: * Garbage bytes used to create the PDF header.
103: */
104: public static final byte[] GARBAGE = new byte[] { (byte) 0xf6,
105: (byte) 0xe4, (byte) 0xfc, (byte) 0xdf };
106: /**
107: * The EOF constant.
108: */
109: public static final byte[] EOF = "%%EOF".getBytes();
110: // pdf tokens
111:
112: /**
113: * The reference token.
114: */
115: public static final byte[] REFERENCE = "R".getBytes();
116: /**
117: * The XREF token.
118: */
119: public static final byte[] XREF = "xref".getBytes();
120: /**
121: * The xref free token.
122: */
123: public static final byte[] XREF_FREE = "f".getBytes();
124: /**
125: * The xref used token.
126: */
127: public static final byte[] XREF_USED = "n".getBytes();
128: /**
129: * The trailer token.
130: */
131: public static final byte[] TRAILER = "trailer".getBytes();
132: /**
133: * The start xref token.
134: */
135: public static final byte[] STARTXREF = "startxref".getBytes();
136: /**
137: * The starting object token.
138: */
139: public static final byte[] OBJ = "obj".getBytes();
140: /**
141: * The end object token.
142: */
143: public static final byte[] ENDOBJ = "endobj".getBytes();
144: /**
145: * The array open token.
146: */
147: public static final byte[] ARRAY_OPEN = "[".getBytes();
148: /**
149: * The array close token.
150: */
151: public static final byte[] ARRAY_CLOSE = "]".getBytes();
152: /**
153: * The open stream token.
154: */
155: public static final byte[] STREAM = "stream".getBytes();
156: /**
157: * The close stream token.
158: */
159: public static final byte[] ENDSTREAM = "endstream".getBytes();
160:
161: private NumberFormat formatXrefOffset = new DecimalFormat(
162: "0000000000");
163: /**
164: * The decimal format for the xref object generation number data.
165: */
166: private NumberFormat formatXrefGeneration = new DecimalFormat(
167: "00000");
168:
169: private NumberFormat formatDecimal = NumberFormat
170: .getNumberInstance(Locale.US);
171:
172: // the stream where we create the pdf output
173: private OutputStream output;
174: // the stream used to write standard cos data
175: private COSStandardOutputStream standardOutput;
176:
177: // the start position of the x ref section
178: private long startxref = 0;
179:
180: // the current object number
181: private long number = 0;
182:
183: // maps the object to the keys generated in the writer
184: // these are used for indirect refrences in other objects
185: //A hashtable is used on purpose over a hashmap
186: //so that null entries will not get added.
187: private Map objectKeys = new Hashtable();
188:
189: // the list of x ref entries to be made so far
190: private List xRefEntries = new ArrayList();
191:
192: //A list of objects to write.
193: private List objectsToWrite = new ArrayList();
194:
195: //a list of objects already written
196: private Set writtenObjects = new HashSet();
197: //An 'actual' is any COSBase that is not a COSObject.
198: //need to keep a list of the actuals that are added
199: //as well as the objects because there is a problem
200: //when adding a COSObject and then later adding
201: //the actual for that object, so we will track
202: //actuals separately.
203: private Set actualsAdded = new HashSet();
204:
205: private COSObjectKey currentObjectKey = null;
206:
207: private PDDocument document = null;
208:
209: private boolean willEncrypt = false;
210:
211: /**
212: * COSWriter constructor comment.
213: *
214: * @param os The wrapped output stream.
215: */
216: public COSWriter(OutputStream os) {
217: super ();
218: setOutput(os);
219: setStandardOutput(new COSStandardOutputStream(getOutput()));
220: formatDecimal.setMaximumFractionDigits(10);
221: formatDecimal.setGroupingUsed(false);
222: }
223:
224: /**
225: * add an entry in the x ref table for later dump.
226: *
227: * @param entry The new entry to add.
228: */
229: protected void addXRefEntry(COSWriterXRefEntry entry) {
230: getXRefEntries().add(entry);
231: }
232:
233: /**
234: * This will close the stream.
235: *
236: * @throws IOException If the underlying stream throws an exception.
237: */
238: public void close() throws IOException {
239: if (getStandardOutput() != null) {
240: getStandardOutput().close();
241: }
242: if (getOutput() != null) {
243: getOutput().close();
244: }
245: }
246:
247: /**
248: * This will get the current object number.
249: *
250: * @return The current object number.
251: */
252: protected long getNumber() {
253: return number;
254: }
255:
256: /**
257: * This will get all available object keys.
258: *
259: * @return A map of all object keys.
260: */
261: public java.util.Map getObjectKeys() {
262: return objectKeys;
263: }
264:
265: /**
266: * This will get the output stream.
267: *
268: * @return The output stream.
269: */
270: protected java.io.OutputStream getOutput() {
271: return output;
272: }
273:
274: /**
275: * This will get the standard output stream.
276: *
277: * @return The standard output stream.
278: */
279: protected COSStandardOutputStream getStandardOutput() {
280: return standardOutput;
281: }
282:
283: /**
284: * This will get the current start xref.
285: *
286: * @return The current start xref.
287: */
288: protected long getStartxref() {
289: return startxref;
290: }
291:
292: /**
293: * This will get the xref entries.
294: *
295: * @return All available xref entries.
296: */
297: protected java.util.List getXRefEntries() {
298: return xRefEntries;
299: }
300:
301: /**
302: * This will set the current object number.
303: *
304: * @param newNumber The new object number.
305: */
306: protected void setNumber(long newNumber) {
307: number = newNumber;
308: }
309:
310: /**
311: * This will set the output stream.
312: *
313: * @param newOutput The new output stream.
314: */
315: private void setOutput(OutputStream newOutput) {
316: output = newOutput;
317: }
318:
319: /**
320: * This will set the standard output stream.
321: *
322: * @param newStandardOutput The new standard output stream.
323: */
324: private void setStandardOutput(
325: COSStandardOutputStream newStandardOutput) {
326: standardOutput = newStandardOutput;
327: }
328:
329: /**
330: * This will set the start xref.
331: *
332: * @param newStartxref The new start xref attribute.
333: */
334: protected void setStartxref(long newStartxref) {
335: startxref = newStartxref;
336: }
337:
338: /**
339: * This will write the body of the document.
340: *
341: * @param doc The document to write the body for.
342: *
343: * @throws IOException If there is an error writing the data.
344: * @throws COSVisitorException If there is an error generating the data.
345: */
346: protected void doWriteBody(COSDocument doc) throws IOException,
347: COSVisitorException {
348: COSDictionary trailer = doc.getTrailer();
349: COSDictionary root = (COSDictionary) trailer
350: .getDictionaryObject(COSName.ROOT);
351: COSDictionary info = (COSDictionary) trailer
352: .getDictionaryObject(COSName.getPDFName("Info"));
353: COSDictionary encrypt = (COSDictionary) trailer
354: .getDictionaryObject(COSName.getPDFName("Encrypt"));
355: if (root != null) {
356: addObjectToWrite(root);
357: }
358: if (info != null) {
359: addObjectToWrite(info);
360: }
361:
362: while (objectsToWrite.size() > 0) {
363: COSBase nextObject = (COSBase) objectsToWrite.remove(0);
364: doWriteObject(nextObject);
365: }
366:
367: willEncrypt = false;
368:
369: if (encrypt != null) {
370: addObjectToWrite(encrypt);
371: }
372:
373: while (objectsToWrite.size() > 0) {
374: COSBase nextObject = (COSBase) objectsToWrite.remove(0);
375: doWriteObject(nextObject);
376: }
377:
378: // write all objects
379: /**
380: for (Iterator i = doc.getObjects().iterator(); i.hasNext();)
381: {
382: COSObject obj = (COSObject) i.next();
383: doWriteObject(obj);
384: }**/
385: }
386:
387: private void addObjectToWrite(COSBase object) {
388: COSBase actual = object;
389: if (actual instanceof COSObject) {
390: actual = ((COSObject) actual).getObject();
391: }
392:
393: if (!writtenObjects.contains(object)
394: && !objectsToWrite.contains(object)
395: && !actualsAdded.contains(actual)) {
396: objectsToWrite.add(object);
397: if (actual != null) {
398: actualsAdded.add(actual);
399: }
400: }
401: }
402:
403: /**
404: * This will write a COS object.
405: *
406: * @param obj The object to write.
407: *
408: * @throws COSVisitorException If there is an error visiting objects.
409: */
410: public void doWriteObject(COSBase obj) throws COSVisitorException {
411: try {
412: writtenObjects.add(obj);
413: // find the physical reference
414: currentObjectKey = getObjectKey(obj);
415: // add a x ref entry
416: addXRefEntry(new COSWriterXRefEntry(getStandardOutput()
417: .getPos(), obj, currentObjectKey));
418: // write the object
419: getStandardOutput().write(
420: String.valueOf(currentObjectKey.getNumber())
421: .getBytes());
422: getStandardOutput().write(SPACE);
423: getStandardOutput().write(
424: String.valueOf(currentObjectKey.getGeneration())
425: .getBytes());
426: getStandardOutput().write(SPACE);
427: getStandardOutput().write(OBJ);
428: getStandardOutput().writeEOL();
429: obj.accept(this );
430: getStandardOutput().writeEOL();
431: getStandardOutput().write(ENDOBJ);
432: getStandardOutput().writeEOL();
433: } catch (IOException e) {
434: throw new COSVisitorException(e);
435: }
436: }
437:
438: /**
439: * This will write the header to the PDF document.
440: *
441: * @param doc The document to get the data from.
442: *
443: * @throws IOException If there is an error writing to the stream.
444: */
445: protected void doWriteHeader(COSDocument doc) throws IOException {
446: getStandardOutput().write(doc.getHeaderString().getBytes());
447: getStandardOutput().writeEOL();
448: getStandardOutput().write(COMMENT);
449: getStandardOutput().write(GARBAGE);
450: getStandardOutput().writeEOL();
451: }
452:
453: /**
454: * This will write the trailer to the PDF document.
455: *
456: * @param doc The document to create the trailer for.
457: *
458: * @throws IOException If there is an IOError while writing the document.
459: * @throws COSVisitorException If there is an error while generating the data.
460: */
461: protected void doWriteTrailer(COSDocument doc) throws IOException,
462: COSVisitorException {
463: getStandardOutput().write(TRAILER);
464: getStandardOutput().writeEOL();
465:
466: COSDictionary trailer = doc.getTrailer();
467: //sort xref, needed only if object keys not regenerated
468: Collections.sort(getXRefEntries());
469: COSWriterXRefEntry lastEntry = (COSWriterXRefEntry) getXRefEntries()
470: .get(getXRefEntries().size() - 1);
471: trailer.setInt(COSName.getPDFName("Size"), (int) lastEntry
472: .getKey().getNumber() + 1);
473: trailer.removeItem(COSName.PREV);
474: /**
475: COSObject catalog = doc.getCatalog();
476: if (catalog != null)
477: {
478: trailer.setItem(COSName.getPDFName("Root"), catalog);
479: }
480: */
481: trailer.accept(this );
482:
483: getStandardOutput().write(STARTXREF);
484: getStandardOutput().writeEOL();
485: getStandardOutput().write(
486: String.valueOf(getStartxref()).getBytes());
487: getStandardOutput().writeEOL();
488: getStandardOutput().write(EOF);
489: }
490:
491: /**
492: * write the x ref section for the pdf file
493: *
494: * currently, the pdf is reconstructed from the scratch, so we write a single section
495: *
496: * todo support for incremental writing?
497: *
498: * @param doc The document to write the xref from.
499: *
500: * @throws IOException If there is an error writing the data to the stream.
501: */
502: protected void doWriteXRef(COSDocument doc) throws IOException {
503: String offset;
504: String generation;
505:
506: // sort xref, needed only if object keys not regenerated
507: Collections.sort(getXRefEntries());
508: COSWriterXRefEntry lastEntry = (COSWriterXRefEntry) getXRefEntries()
509: .get(getXRefEntries().size() - 1);
510:
511: // remember the position where x ref is written
512: setStartxref(getStandardOutput().getPos());
513: //
514: getStandardOutput().write(XREF);
515: getStandardOutput().writeEOL();
516: // write start object number and object count for this x ref section
517: // we assume starting from scratch
518: getStandardOutput().write(String.valueOf(0).getBytes());
519: getStandardOutput().write(SPACE);
520: getStandardOutput().write(
521: String.valueOf(lastEntry.getKey().getNumber() + 1)
522: .getBytes());
523: getStandardOutput().writeEOL();
524: // write initial start object with ref to first deleted object and magic generation number
525: offset = formatXrefOffset.format(0);
526: generation = formatXrefGeneration.format(65535);
527: getStandardOutput().write(offset.getBytes());
528: getStandardOutput().write(SPACE);
529: getStandardOutput().write(generation.getBytes());
530: getStandardOutput().write(SPACE);
531: getStandardOutput().write(XREF_FREE);
532: getStandardOutput().writeCRLF();
533: // write entry for every object
534: long lastObjectNumber = 0;
535: for (Iterator i = getXRefEntries().iterator(); i.hasNext();) {
536: COSWriterXRefEntry entry = (COSWriterXRefEntry) i.next();
537: while (lastObjectNumber < entry.getKey().getNumber() - 1) {
538: offset = formatXrefOffset.format(0);
539: generation = formatXrefGeneration.format(65535);
540: getStandardOutput().write(offset.getBytes());
541: getStandardOutput().write(SPACE);
542: getStandardOutput().write(generation.getBytes());
543: getStandardOutput().write(SPACE);
544: getStandardOutput().write(XREF_FREE);
545: getStandardOutput().writeCRLF();
546: lastObjectNumber++;
547: }
548: lastObjectNumber = entry.getKey().getNumber();
549: offset = formatXrefOffset.format(entry.getOffset());
550: generation = formatXrefGeneration.format(entry.getKey()
551: .getGeneration());
552: getStandardOutput().write(offset.getBytes());
553: getStandardOutput().write(SPACE);
554: getStandardOutput().write(generation.getBytes());
555: getStandardOutput().write(SPACE);
556: getStandardOutput().write(
557: entry.isFree() ? XREF_FREE : XREF_USED);
558: getStandardOutput().writeCRLF();
559: }
560: }
561:
562: /**
563: * This will get the object key for the object.
564: *
565: * @param obj The object to get the key for.
566: *
567: * @return The object key for the object.
568: */
569: private COSObjectKey getObjectKey(COSBase obj) {
570: COSBase actual = obj;
571: if (actual instanceof COSObject) {
572: actual = ((COSObject) obj).getObject();
573: }
574: COSObjectKey key = null;
575: if (actual != null) {
576: key = (COSObjectKey) objectKeys.get(actual);
577: }
578: if (key == null) {
579: key = (COSObjectKey) objectKeys.get(obj);
580: }
581: if (key == null) {
582: setNumber(getNumber() + 1);
583: key = new COSObjectKey(getNumber(), 0);
584: objectKeys.put(obj, key);
585: if (actual != null) {
586: objectKeys.put(actual, key);
587: }
588: }
589: return key;
590: }
591:
592: /**
593: * visitFromArray method comment.
594: *
595: * @param obj The object that is being visited.
596: *
597: * @throws COSVisitorException If there is an exception while visiting this object.
598: *
599: * @return null
600: */
601: public Object visitFromArray(COSArray obj)
602: throws COSVisitorException {
603: try {
604: int count = 0;
605: getStandardOutput().write(ARRAY_OPEN);
606: for (Iterator i = obj.iterator(); i.hasNext();) {
607: COSBase current = (COSBase) i.next();
608: if (current instanceof COSDictionary) {
609: addObjectToWrite(current);
610: writeReference(current);
611: } else if (current instanceof COSObject) {
612: COSBase subValue = ((COSObject) current)
613: .getObject();
614: if (subValue instanceof COSDictionary
615: || subValue == null) {
616: addObjectToWrite(current);
617: writeReference(current);
618: } else {
619: subValue.accept(this );
620: }
621: } else if (current == null) {
622: COSNull.NULL.accept(this );
623: } else {
624: current.accept(this );
625: }
626: count++;
627: if (i.hasNext()) {
628: if (count % 10 == 0) {
629: getStandardOutput().writeEOL();
630: } else {
631: getStandardOutput().write(SPACE);
632: }
633: }
634: }
635: getStandardOutput().write(ARRAY_CLOSE);
636: getStandardOutput().writeEOL();
637: return null;
638: } catch (IOException e) {
639: throw new COSVisitorException(e);
640: }
641: }
642:
643: /**
644: * visitFromBoolean method comment.
645: *
646: * @param obj The object that is being visited.
647: *
648: * @throws COSVisitorException If there is an exception while visiting this object.
649: *
650: * @return null
651: */
652: public Object visitFromBoolean(COSBoolean obj)
653: throws COSVisitorException {
654:
655: try {
656: obj.writePDF(getStandardOutput());
657: return null;
658: } catch (IOException e) {
659: throw new COSVisitorException(e);
660: }
661: }
662:
663: /**
664: * visitFromDictionary method comment.
665: *
666: * @param obj The object that is being visited.
667: *
668: * @throws COSVisitorException If there is an exception while visiting this object.
669: *
670: * @return null
671: */
672: public Object visitFromDictionary(COSDictionary obj)
673: throws COSVisitorException {
674: try {
675: getStandardOutput().write(DICT_OPEN);
676: getStandardOutput().writeEOL();
677: for (Iterator i = obj.keyList().iterator(); i.hasNext();) {
678: COSName name = (COSName) i.next();
679: COSBase value = obj.getItem(name);
680: if (value != null) {
681: name.accept(this );
682: getStandardOutput().write(SPACE);
683: if (value instanceof COSDictionary) {
684: addObjectToWrite(value);
685: writeReference(value);
686: } else if (value instanceof COSObject) {
687: COSBase subValue = ((COSObject) value)
688: .getObject();
689: if (subValue instanceof COSDictionary
690: || subValue == null) {
691: addObjectToWrite(value);
692: writeReference(value);
693: } else {
694: subValue.accept(this );
695: }
696: } else {
697: value.accept(this );
698: }
699: getStandardOutput().writeEOL();
700:
701: } else {
702: //then we won't write anything, there are a couple cases
703: //were the value of an entry in the COSDictionary will
704: //be a dangling reference that points to nothing
705: //so we will just not write out the entry if that is the case
706: }
707: }
708: getStandardOutput().write(DICT_CLOSE);
709: getStandardOutput().writeEOL();
710: return null;
711: } catch (IOException e) {
712: throw new COSVisitorException(e);
713: }
714: }
715:
716: /**
717: * The visit from document method.
718: *
719: * @param doc The object that is being visited.
720: *
721: * @throws COSVisitorException If there is an exception while visiting this object.
722: *
723: * @return null
724: */
725: public Object visitFromDocument(COSDocument doc)
726: throws COSVisitorException {
727: try {
728: doWriteHeader(doc);
729: doWriteBody(doc);
730: doWriteXRef(doc);
731: doWriteTrailer(doc);
732: return null;
733: } catch (IOException e) {
734: throw new COSVisitorException(e);
735: }
736: }
737:
738: /**
739: * visitFromFloat method comment.
740: *
741: * @param obj The object that is being visited.
742: *
743: * @throws COSVisitorException If there is an exception while visiting this object.
744: *
745: * @return null
746: */
747: public Object visitFromFloat(COSFloat obj)
748: throws COSVisitorException {
749:
750: try {
751: obj.writePDF(getStandardOutput());
752: return null;
753: } catch (IOException e) {
754: throw new COSVisitorException(e);
755: }
756: }
757:
758: /**
759: * visitFromFloat method comment.
760: *
761: * @param obj The object that is being visited.
762: *
763: * @throws COSVisitorException If there is an exception while visiting this object.
764: *
765: * @return null
766: */
767: public Object visitFromInt(COSInteger obj)
768: throws COSVisitorException {
769: try {
770: obj.writePDF(getStandardOutput());
771: return null;
772: } catch (IOException e) {
773: throw new COSVisitorException(e);
774: }
775: }
776:
777: /**
778: * visitFromName method comment.
779: *
780: * @param obj The object that is being visited.
781: *
782: * @throws COSVisitorException If there is an exception while visiting this object.
783: *
784: * @return null
785: */
786: public Object visitFromName(COSName obj) throws COSVisitorException {
787: try {
788: obj.writePDF(getStandardOutput());
789: return null;
790: } catch (IOException e) {
791: throw new COSVisitorException(e);
792: }
793: }
794:
795: /**
796: * visitFromNull method comment.
797: *
798: * @param obj The object that is being visited.
799: *
800: * @throws COSVisitorException If there is an exception while visiting this object.
801: *
802: * @return null
803: */
804: public Object visitFromNull(COSNull obj) throws COSVisitorException {
805: try {
806: obj.writePDF(getStandardOutput());
807: return null;
808: } catch (IOException e) {
809: throw new COSVisitorException(e);
810: }
811: }
812:
813: /**
814: * visitFromObjRef method comment.
815: *
816: * @param obj The object that is being visited.
817: *
818: * @throws COSVisitorException If there is an exception while visiting this object.
819: */
820: public void writeReference(COSBase obj) throws COSVisitorException {
821: try {
822: COSObjectKey key = getObjectKey(obj);
823: getStandardOutput().write(
824: String.valueOf(key.getNumber()).getBytes());
825: getStandardOutput().write(SPACE);
826: getStandardOutput().write(
827: String.valueOf(key.getGeneration()).getBytes());
828: getStandardOutput().write(SPACE);
829: getStandardOutput().write(REFERENCE);
830: } catch (IOException e) {
831: throw new COSVisitorException(e);
832: }
833: }
834:
835: /**
836: * visitFromStream method comment.
837: *
838: * @param obj The object that is being visited.
839: *
840: * @throws COSVisitorException If there is an exception while visiting this object.
841: *
842: * @return null
843: */
844: public Object visitFromStream(COSStream obj)
845: throws COSVisitorException {
846: try {
847: if (willEncrypt) {
848: document.getSecurityHandler().decryptStream(obj,
849: currentObjectKey.getNumber(),
850: currentObjectKey.getGeneration());
851: }
852:
853: InputStream input = obj.getFilteredStream();
854: // set the length of the stream and write stream dictionary
855: COSObject lengthObject = new COSObject(null);
856:
857: obj.setItem(COSName.LENGTH, lengthObject);
858: //obj.accept(this);
859: // write the stream content
860: visitFromDictionary(obj);
861: getStandardOutput().write(STREAM);
862: getStandardOutput().writeCRLF();
863: byte[] buffer = new byte[1024];
864: int amountRead = 0;
865: int totalAmountWritten = 0;
866: while ((amountRead = input.read(buffer, 0, 1024)) != -1) {
867: getStandardOutput().write(buffer, 0, amountRead);
868: totalAmountWritten += amountRead;
869: }
870: lengthObject.setObject(new COSInteger(totalAmountWritten));
871: getStandardOutput().writeCRLF();
872: getStandardOutput().write(ENDSTREAM);
873: getStandardOutput().writeEOL();
874: return null;
875: } catch (Exception e) {
876: throw new COSVisitorException(e);
877: }
878: }
879:
880: /**
881: * visitFromString method comment.
882: *
883: * @param obj The object that is being visited.
884: *
885: * @return null
886: *
887: * @throws COSVisitorException If there is an exception while visiting this object.
888: */
889: public Object visitFromString(COSString obj)
890: throws COSVisitorException {
891: try {
892: if (willEncrypt) {
893: document.getSecurityHandler().decryptString(obj,
894: currentObjectKey.getNumber(),
895: currentObjectKey.getGeneration());
896: }
897:
898: obj.writePDF(getStandardOutput());
899: } catch (Exception e) {
900: throw new COSVisitorException(e);
901: }
902: return null;
903: }
904:
905: /**
906: * This will write the pdf document.
907: *
908: * @param doc The document to write.
909: *
910: * @throws COSVisitorException If an error occurs while generating the data.
911: */
912: public void write(COSDocument doc) throws COSVisitorException {
913: PDDocument pdDoc = new PDDocument(doc);
914: write(pdDoc);
915: }
916:
917: /**
918: * This will write the pdf document.
919: *
920: * @param doc The document to write.
921: *
922: * @throws COSVisitorException If an error occurs while generating the data.
923: */
924: public void write(PDDocument doc) throws COSVisitorException {
925: document = doc;
926:
927: SecurityHandler securityHandler = document.getSecurityHandler();
928: if (securityHandler != null) {
929: try {
930: securityHandler.prepareDocumentForEncryption(document);
931: this .willEncrypt = true;
932: } catch (IOException e) {
933: throw new COSVisitorException(e);
934: } catch (CryptographyException e) {
935: throw new COSVisitorException(e);
936: }
937: } else {
938: this .willEncrypt = false;
939: }
940:
941: COSDocument cosDoc = document.getDocument();
942: COSDictionary trailer = cosDoc.getTrailer();
943: COSArray idArray = (COSArray) trailer.getDictionaryObject("ID");
944: if (idArray == null) {
945: try {
946:
947: //algothim says to use time/path/size/values in doc to generate
948: //the id. We don't have path or size, so do the best we can
949: MessageDigest md = MessageDigest.getInstance("MD5");
950: md.update(Long.toString(System.currentTimeMillis())
951: .getBytes());
952: COSDictionary info = (COSDictionary) trailer
953: .getDictionaryObject("Info");
954: if (info != null) {
955: Iterator values = info.getValues().iterator();
956: while (values.hasNext()) {
957: md.update(values.next().toString().getBytes());
958: }
959: }
960: idArray = new COSArray();
961: COSString id = new COSString(md.digest());
962: idArray.add(id);
963: idArray.add(id);
964: trailer.setItem("ID", idArray);
965: } catch (NoSuchAlgorithmException e) {
966: throw new COSVisitorException(e);
967: }
968: }
969:
970: /*
971: List objects = doc.getObjects();
972: Iterator iter = objects.iterator();
973: long maxNumber = 0;
974: while( iter.hasNext() )
975: {
976: COSObject object = (COSObject)iter.next();
977: if( object.getObjectNumber() != null &&
978: object.getGenerationNumber() != null )
979: {
980: COSObjectKey key = new COSObjectKey( object.getObjectNumber().longValue(),
981: object.getGenerationNumber().longValue() );
982: objectKeys.put( object.getObject(), key );
983: objectKeys.put( object, key );
984: maxNumber = Math.max( key.getNumber(), maxNumber );
985: setNumber( maxNumber );
986: }
987: }*/
988: cosDoc.accept(this);
989: }
990: }
|