001: /*
002: Copyright © 2006,2007 Stefano Chizzolini. http://clown.stefanochizzolini.it
003:
004: Contributors:
005: * Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it)
006: * Haakan Aakerberg (bugfix contributor):
007: - [FIX:0.0.4:5]
008:
009: This file should be part of the source code distribution of "PDF Clown library"
010: (the Program): see the accompanying README files for more info.
011:
012: This Program is free software; you can redistribute it and/or modify it under
013: the terms of the GNU General Public License as published by the Free Software
014: Foundation; either version 2 of the License, or (at your option) any later version.
015:
016: This Program is distributed in the hope that it will be useful, but WITHOUT ANY
017: WARRANTY, either expressed or implied; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
019:
020: You should have received a copy of the GNU General Public License along with this
021: Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
022:
023: Redistribution and use, with or without modification, are permitted provided that such
024: redistributions retain the above copyright notice, license and disclaimer, along with
025: this list of conditions.
026: */
027:
028: package it.stefanochizzolini.clown.tokens;
029:
030: import it.stefanochizzolini.clown.bytes.IOutputStream;
031: import it.stefanochizzolini.clown.files.File;
032: import it.stefanochizzolini.clown.files.IndirectObjects;
033: import it.stefanochizzolini.clown.objects.PdfDictionary;
034: import it.stefanochizzolini.clown.objects.PdfIndirectObject;
035: import it.stefanochizzolini.clown.objects.PdfInteger;
036: import it.stefanochizzolini.clown.objects.PdfLong;
037: import it.stefanochizzolini.clown.objects.PdfName;
038: import it.stefanochizzolini.clown.objects.PdfReference;
039: import java.io.EOFException;
040: import java.util.Map;
041:
042: /**
043: PDF file writer.
044: */
045: public class Writer {
046: // <class>
047: // <dynamic>
048: // <fields>
049: private File file;
050: private IOutputStream stream;
051:
052: // </fields>
053:
054: // <constructors>
055: /**
056: <h3>Remarks</h3>
057: <p>For internal use only.</p>
058: */
059: public Writer(IOutputStream stream, File file) {
060: this .stream = stream;
061: this .file = file;
062: }
063:
064: // </constructors>
065:
066: // <interface>
067: public IOutputStream getStream() {
068: return stream;
069: }
070:
071: /**
072: Serializes the PDF file compactly [PDF:1.6:3.4].
073: */
074: public void writeStandard() {
075: StringBuilder xrefBuilder = new StringBuilder();
076: int xrefSize = file.getIndirectObjects().size();
077: int offset;
078:
079: // Header [PDF:1.6:3.4.1].
080: {
081: offset = stream.write("%PDF-1.6\r" + "%âãÏÓ\r" // Arbitrary binary characters (code >= 128) for ensuring proper behavior of file transfer applications.
082: );
083: }
084:
085: // Body [PDF:1.6:3.4.2].
086: {
087: /*
088: NOTE: A compact xref table comprises just one section composed by just one subsection.
089: NOTE: As xref-table free entries MUST be arrayed as a linked list,
090: it's needed to cache intermingled in-use entries in order to properly render
091: the object number of the next free entry inside the previous one.
092: */
093: StringBuilder xrefInUseBlockBuilder = new StringBuilder();
094: IndirectObjects indirectObjects = file.getIndirectObjects();
095: PdfReference freeReference = indirectObjects.get(0)
096: .getReference(); // Initialized to the first free entry.
097: for (int index = 1; index < xrefSize; index++) {
098: PdfIndirectObject indirectObject = indirectObjects
099: .get(index);
100: // Is the object entry in use?
101: if (indirectObject.isInUse()) // In-use entry.
102: {
103: // Indirect object.
104: // Append to the xref table its xref!
105: xrefInUseBlockBuilder.append(indirectObject
106: .getReference().getCrossReference(offset));
107: // Serialize its content!
108: offset += indirectObject.writeTo(stream);
109: } else // Free entry.
110: {
111: // Flush current xref-table cache!
112: xrefBuilder.append(freeReference
113: .getCrossReference(index)
114: + xrefInUseBlockBuilder.toString());
115: // Initialize next xref-table subsection!
116: xrefInUseBlockBuilder.setLength(0);
117: freeReference = indirectObject.getReference();
118: }
119: }
120: // Flush current xref-table cache!
121: xrefBuilder.append(freeReference.getCrossReference(0)
122: + xrefInUseBlockBuilder.toString());
123: }
124:
125: // XRef table (unique section) [PDF:1.6:3.4.3]...
126: {
127: // ...header.
128: stream.write("xref\r" + "0 " + xrefSize + "\r");
129: // ...body.
130: stream.write(xrefBuilder.toString());
131: }
132:
133: // Trailer [PDF:1.6:3.4.4]...
134: {
135: // ...header.
136: stream.write("trailer\r");
137: // ...body.
138: // Update the counter!
139: PdfDictionary trailer = file.getTrailer();
140: trailer.put(PdfName.Size, new PdfInteger(xrefSize));
141: trailer.remove(PdfName.Prev); // [FIX:0.0.4:5] It (wrongly) kept the 'Prev' entry of multiple-section xref tables.
142: // Serialize the contents!
143: trailer.writeTo(stream);
144: // ...tail.
145: stream
146: .write("\r" + "startxref\r" + offset + "\r"
147: + "%%EOF");
148: }
149: }
150:
151: /**
152: Serializes the PDF file as incremental update [PDF:1.6:3.4.5].
153: */
154: public void writeIncremental() {
155: StringBuilder xrefBuilder = new StringBuilder();
156: int xrefSize = file.getIndirectObjects().size();
157: int offset;
158: Parser parser = file.getReader().getParser();
159:
160: // Original content.
161: offset = stream.write(parser.getStream());
162:
163: // Body update.
164: {
165: /*
166: NOTE: incremental xref table comprises multiple sections each one composed by multiple
167: subsections.
168: */
169: // Insert modified indirect objects.
170: StringBuilder xrefSubBuilder = new StringBuilder(); // xref-table subsection builder.
171: int xrefSubCount = 0; // xref-table subsection counter.
172: int prevKey = 0; // Previous-entry object number.
173: for (Map.Entry<Integer, PdfIndirectObject> indirectObjectEntry : file
174: .getIndirectObjects().getModifiedObjects()
175: .entrySet()) {
176: // Is the object in the current subsection?
177: /*
178: NOTE: to belong to the current subsection, the object entry MUST be contiguous with the
179: previous (1° condition) or the iteration has to have been just started (2° condition).
180: */
181: if (indirectObjectEntry.getKey() - prevKey == 1
182: || prevKey == 0) // Current subsection continues.
183: {
184: xrefSubCount++;
185: } else // Current subsection terminates.
186: {
187: // Flush current xref-table subsection!
188: xrefBuilder.append((prevKey - xrefSubCount + 1)
189: + " " + xrefSubCount + "\r"
190: + xrefSubBuilder.toString());
191: // Initialize next xref-table subsection!
192: xrefSubBuilder.setLength(0);
193: xrefSubCount = 1;
194: }
195:
196: prevKey = indirectObjectEntry.getKey();
197:
198: // Modified indirect object.
199: if (indirectObjectEntry.getValue().isInUse()) // In-use entry.
200: {
201: // Append to the current xref-table subsection its xref!
202: xrefSubBuilder.append(indirectObjectEntry
203: .getValue().getReference()
204: .getCrossReference(offset));
205: // Serialize its content!
206: offset += indirectObjectEntry.getValue().writeTo(
207: stream);
208: } else // Free entry.
209: {
210: // Append to the current xref-table subsection its xref!
211: /*
212: NOTE: We purposely neglect the linked list of free entries (see IndirectObjects.remove(int)),
213: so that this entry links directly back to object number 0, having a generation number of
214: 65535 (not reusable) [PDF:1.6:3.4.3].
215: */
216: xrefSubBuilder.append(indirectObjectEntry
217: .getValue().getReference()
218: .getCrossReference(0));
219: }
220: }
221: // Flush current xref-table subsection!
222: xrefBuilder.append((prevKey - xrefSubCount + 1) + " "
223: + xrefSubCount + "\r" + xrefSubBuilder.toString());
224: }
225:
226: // XRef-table last section...
227: {
228: // ...header.
229: stream.write("xref\r");
230: // ...body.
231: stream.write(xrefBuilder.toString());
232: }
233:
234: // Updated trailer...
235: try {
236: // ...header.
237: stream.write("trailer\r");
238: // ...body.
239: // Update the entries!
240: PdfDictionary trailer = file.getTrailer();
241: trailer.put(PdfName.Size, new PdfInteger(xrefSize));
242: trailer.put(PdfName.Prev, new PdfLong(parser
243: .retrieveXRefOffset()));
244: // Serialize the contents!
245: trailer.writeTo(stream);
246: // ...tail.
247: stream
248: .write("\r" + "startxref\r" + offset + "\r"
249: + "%%EOF");
250: } catch (Exception e) {
251: // Propagate the exception!
252: throw new RuntimeException(e);
253: }
254: }
255: // </interface>
256: // </dynamic>
257: // </class>
258: }
|