001: /*
002: Copyright (C) Etymon Systems, Inc. <http://www.etymon.com/>
003: */
004:
005: package com.etymon.pjx;
006:
007: import java.io.*;
008: import java.util.*;
009:
010: /**
011: Represents the PDF cross-reference table and associated trailer
012: dictionary. This class is immutable.
013: @author Nassib Nassar
014: */
015: public class XrefTable {
016:
017: /**
018: The array of generation values.
019: */
020: protected int[] _generation;
021:
022: /**
023: The array of index values. Each value represents either a
024: byte offset (if in-use) or the next free object number (if
025: free).
026: */
027: protected long[] _index;
028:
029: /**
030: The array of sorted index values. This contains only
031: in-use entries, to be used for binary searching and
032: locating the offset of the next object in the file.
033: */
034: protected long[] _index_sorted;
035:
036: /**
037: The list of startxref values associated with this
038: cross-reference table.
039: */
040: protected List _startxrefs;
041:
042: /**
043: The trailer dictionary associated with this cross-reference
044: table.
045: */
046: protected PdfDictionary _trailer;
047:
048: /**
049: The array of usage values. Each values is ENTRY_FREE,
050: ENTRY_IN_USE, or ENTRY_UNDEFINED.
051: */
052: protected byte[] _usage;
053:
054: /**
055: This indicates that an entry is free.
056: */
057: public static final byte ENTRY_FREE = 1;
058:
059: /**
060: This indicates that an entry is in-use.
061: */
062: public static final byte ENTRY_IN_USE = 2;
063:
064: /**
065: This indicates that an entry is undefined.
066: */
067: public static final byte ENTRY_UNDEFINED = 0; // do not change
068:
069: /**
070: A protected constructor intended to be called only from
071: {@link #wrap(long[], int[], byte[], PdfDictionary)
072: wrap(long[], int[], byte[], PdfDictionary)}.
073: */
074: protected XrefTable() {
075: }
076:
077: /**
078: Constructs a cross-reference table from a set of arrays and
079: a trailer dictionary.
080: @param index the array of index values. Each value
081: represents either a byte offset (if in-use) or the next
082: free object number (if free).
083: @param generation the array of generation values.
084: @param usage the array of usage values. Each value is
085: {@link #ENTRY_FREE ENTRY_FREE}, {@link #ENTRY_IN_USE
086: ENTRY_IN_USE}, or {@link #ENTRY_UNDEFINED ENTRY_UNDEFINED}.
087: @param trailerDictionary the trailer dictionary.
088: @throws PdfFormatException
089: */
090: public XrefTable(long[] index, int[] generation, byte[] usage,
091: PdfDictionary trailerDictionary) throws PdfFormatException {
092:
093: if ((index.length != generation.length)
094: && (index.length != usage.length)) {
095: throw new PdfFormatException(
096: "Xref arrays are not the same length.");
097: }
098: _index = new long[index.length];
099: _generation = new int[generation.length];
100: _usage = new byte[usage.length];
101: System.arraycopy(index, 0, _index, 0, index.length);
102: System.arraycopy(generation, 0, _generation, 0,
103: generation.length);
104: System.arraycopy(usage, 0, _usage, 0, usage.length);
105: _startxrefs = new ArrayList();
106: createSortedIndexArray();
107:
108: _trailer = trailerDictionary;
109: }
110:
111: /**
112: Returns a shallow copy of this instance.
113: @return a clone of this instance.
114: */
115: public Object clone() throws CloneNotSupportedException {
116: return super .clone();
117: }
118:
119: protected void createSortedIndexArray() {
120:
121: // determine number of entries that are in use
122: int size = 0;
123: for (int x = 0; x < _usage.length; x++) {
124: if (_usage[x] == ENTRY_IN_USE) {
125: size++;
126: }
127: }
128:
129: _index_sorted = new long[size + _startxrefs.size()];
130: int y = 0;
131: // add index values
132: for (int x = 0; x < _usage.length; x++) {
133: if (_usage[x] == ENTRY_IN_USE) {
134: _index_sorted[y++] = _index[x];
135: }
136: }
137: // add startxref values
138: for (Iterator p = _startxrefs.iterator(); p.hasNext();) {
139: _index_sorted[y++] = ((Long) p.next()).longValue();
140: }
141:
142: Arrays.sort(_index_sorted);
143:
144: }
145:
146: /**
147: Returns an offset estimated to be relatively close to the
148: end of the object (specified by object number). The offset
149: will be no earlier than the end of the object.
150: @param n the specified object number.
151: @return the index value, or -1 if the specified object
152: number corresponds to the last object in the document. If
153: the object number does not correspond to an in-use entry,
154: then -1 is returned.
155: */
156: public long estimateObjectEnd(int n) {
157:
158: if (_usage[n] != ENTRY_IN_USE) {
159: return -1;
160: }
161:
162: long start = _index[n];
163:
164: int x = Arrays.binarySearch(_index_sorted, start);
165:
166: while ((x < _index_sorted.length)
167: && (_index_sorted[x] == start)) {
168: x++;
169: }
170:
171: if (x < _index_sorted.length) {
172: return _index_sorted[x];
173: } else {
174: return -1;
175: }
176:
177: }
178:
179: /**
180: Returns the generation value for a specified object.
181: @param n the object number.
182: @return the generation value.
183: */
184: public int getGeneration(int n) {
185: return _generation[n];
186: }
187:
188: /**
189: Returns the array of generation values. The calling method
190: must not modify this array unless it guarantees that this
191: object exists in no other thread, in order to comply with
192: the immutability requirement of this class.
193: @return the array of generation values.
194: */
195: public int[] getGenerationArray() {
196: int[] a = new int[_generation.length];
197: System.arraycopy(_generation, 0, a, 0, _generation.length);
198: return a;
199: }
200:
201: /**
202: Returns the index value for a specified object.
203: @param n the object number.
204: @return the index value.
205: */
206: public long getIndex(int n) {
207: return _index[n];
208: }
209:
210: /**
211: Returns the array of index values. The calling method must
212: not modify this array unless it guarantees that this object
213: exists in no other thread, in order to comply with the
214: immutability requirement of this class.
215: @return the array of index values.
216: */
217: public long[] getIndexArray() {
218: long[] a = new long[_index.length];
219: System.arraycopy(_index, 0, a, 0, _index.length);
220: return a;
221: }
222:
223: /**
224: Returns the list of startxref values associated with this
225: cross-reference table. The calling method must not modify
226: this list unless it guarantees that this object exists in
227: no other thread, in order to comply with the immutability
228: requirement of this class.
229: @return the list of startxref values (as
230: <code>Long</code> objects.
231: */
232: protected List getStartxrefList() {
233: return _startxrefs;
234: }
235:
236: /**
237: Returns the trailer dictionary associated with this
238: cross-reference table.
239: @return the trailer dictionary.
240: */
241: public PdfDictionary getTrailerDictionary() {
242: return _trailer;
243: }
244:
245: /**
246: Returns the usage value for a specified object.
247: @param n the object number.
248: @return the usage value.
249: */
250: public byte getUsage(int n) {
251: return _usage[n];
252: }
253:
254: /**
255: Returns the array of usage values. The calling method must
256: not modify this array unless it guarantees that this object
257: exists in no other thread, in order to comply with the
258: immutability requirement of this class.
259: @return the array of usage values.
260: */
261: public byte[] getUsageArray() {
262: byte[] a = new byte[_usage.length];
263: System.arraycopy(_usage, 0, a, 0, _usage.length);
264: return a;
265: }
266:
267: /**
268: Returns the number of entries in this cross-reference
269: table.
270: @return the number of entries.
271: */
272: public int size() {
273: return _usage.length;
274: }
275:
276: /**
277: Returns the cross-reference table, associated trailer
278: dictionary, and a complete PDF trailer as a string in PDF
279: format.
280: @return the PDF string.
281: */
282: public String toString() {
283: ByteArrayOutputStream baos = new ByteArrayOutputStream();
284: try {
285: PdfWriter w = new PdfWriter(baos);
286: writePdf(w, 0);
287: w.close();
288: baos.close();
289: } catch (IOException e) {
290: return null;
291: }
292: return baos.toString();
293: }
294:
295: /**
296: Returns the array of generation values. The calling method
297: must ensure that the array is never externally modified, in
298: order to meet the immutability requirement of this class.
299: @return the array of generation values.
300: */
301: protected int[] unwrapGenerationArray() {
302: return _generation;
303: }
304:
305: /**
306: Returns the array of index values. The calling method must
307: ensure that the array is never externally modified, in
308: order to meet the immutability requirement of this class.
309: @return the array of index values.
310: */
311: protected long[] unwrapIndexArray() {
312: return _index;
313: }
314:
315: /**
316: Returns the array of usage values. The calling method must
317: ensure that the array is never externally modified, in
318: order to meet the immutability requirement of this class.
319: @return the array of usage values.
320: */
321: protected byte[] unwrapUsageArray() {
322: return _usage;
323: }
324:
325: /**
326: A factory for fast construction of this class. The
327: constructed object will be a wrapper around the specified
328: objects. The calling method must ensure that the arrays
329: are never externally modified, in order to meet the
330: immutability requirement of this class. It must also
331: ensure that all three arrays have the same length. It is
332: also the calling method's responsibility to call {@link
333: #createSortedIndexArray() createSortedIndexArray()} before
334: the instance will be used.
335: @param index the array of index values.
336: @param generation the array of generation values.
337: @param usage the array of usage values.
338: @param trailerDictionary the trailer dictionary.
339: @return the constructed object.
340: */
341: protected static XrefTable wrap(long[] index, int[] generation,
342: byte[] usage, PdfDictionary trailerDictionary) {
343: XrefTable xt = new XrefTable();
344: xt._index = index;
345: xt._generation = generation;
346: xt._usage = usage;
347: xt._trailer = trailerDictionary;
348: xt._startxrefs = new ArrayList();
349: return xt;
350: }
351:
352: /**
353: Writes the cross-reference table, associated trailer
354: dictionary, and a complete PDF trailer in PDF format.
355: Before writing the cross-reference table, the free entries
356: are combined into a linked list as required by the PDF
357: specification.
358: @param w the <code>PdfWriter</code> to write to.
359: @param startxref the byte offset within the output file
360: where the cross-reference table begins.
361: @return the number of bytes written by this method.
362: @throws IOException
363: */
364: protected int writePdf(PdfWriter w, long startxref)
365: throws IOException {
366:
367: xrefGenerateFreeList();
368:
369: DataOutputStream dos = w.getDataOutputStream();
370:
371: String s, t;
372:
373: dos.writeBytes("xref\n");
374: int count = 5;
375:
376: int x, xx;
377: x = 0;
378: while (x < size()) {
379:
380: if (_usage[x] != ENTRY_UNDEFINED) {
381:
382: xx = x + 1;
383: while ((xx < size()) && (_usage[xx] != ENTRY_UNDEFINED)) {
384: xx++;
385: }
386:
387: s = Integer.toString(x);
388: dos.writeBytes(s);
389: dos.write(' ');
390: t = Integer.toString(xx - x);
391: dos.writeBytes(t);
392: dos.write('\n');
393: count += s.length() + t.length() + 2;
394:
395: while (x < xx) {
396: // write index
397: s = Long.toString(_index[x]);
398: int y = s.length();
399: for (int z = y; z < 10; z++) {
400: dos.write('0');
401: }
402: dos.writeBytes(s);
403: dos.write(' ');
404: // write generation
405: s = Integer.toString(_generation[x]);
406: y = s.length();
407: for (int z = y; z < 5; z++) {
408: dos.write('0');
409: }
410: dos.writeBytes(s);
411: dos.write(' ');
412: if (_usage[x] == ENTRY_IN_USE) {
413: dos.write('n');
414: } else {
415: dos.write('f');
416: }
417: dos.writeBytes(" \n");
418: count += 20;
419: x++;
420: }
421: }
422: x++;
423: }
424:
425: dos.writeBytes("trailer\n");
426: count += 8;
427:
428: count += _trailer.writePdf(w, false);
429:
430: s = "\nstartxref\n" + Long.toString(startxref) + "\n%%EOF\n";
431: dos.writeBytes(s);
432: return count + s.length();
433: }
434:
435: /**
436: Combines the free elements of this cross-reference table
437: into a linked list as required by the PDF specification.
438: */
439: protected void xrefGenerateFreeList() {
440: int lastFree = 0;
441: long[] index = _index;
442: byte[] usage = _usage;
443: for (int x = usage.length - 1; x >= 0; x--) {
444: if (usage[x] == ENTRY_FREE) {
445: index[x] = lastFree;
446: lastFree = x;
447: }
448: }
449: }
450:
451: }
|