001: package com.etymon.pj.object;
002:
003: import java.io.*;
004: import java.util.*;
005: import java.util.zip.*;
006: import com.etymon.pj.*;
007: import com.etymon.pj.exception.*;
008:
009: /**
010: A representation of the PDF stream type.
011: @author Nassib Nassar
012: */
013: public class PjStream extends PjObject {
014:
015: /**
016: Creates a stream as a wrapper around a byte array.
017: @param s the byte array to use for this stream.
018: */
019: public PjStream(byte[] s) {
020: _d = new PjStreamDictionary();
021: _s = s;
022: }
023:
024: /**
025: Creates a stream as a wrapper around a PjStreamDictionary and
026: byte array.
027: @param d the dictionary to use for this stream.
028: @param s the byte array to use for this stream.
029: */
030: public PjStream(PjStreamDictionary d, byte[] s) {
031: _d = d;
032: _s = s;
033: }
034:
035: /**
036: Returns the PjStreamDictionary used in the representation of this
037: stream.
038: @return the PjStreamDictionary used in the representation of this
039: stream.
040: */
041: public PjStreamDictionary getStreamDictionary() {
042: return _d;
043: }
044:
045: /**
046: Returns the byte array used in the representation of this
047: stream.
048: @return the byte array used in the representation of this
049: stream.
050: */
051: public byte[] getBuffer() {
052: return _s;
053: }
054:
055: /**
056: Decompresses this stream if it is compressed with the Flate
057: algorithm.
058: @return a cloned, uncompressed version of this stream; or
059: this stream if it is not marked as being compressed with
060: Flate.
061: @exception InvalidPdfObjectException if an invalid object
062: type is encountered.
063: */
064: public PjStream flateDecompress() throws InvalidPdfObjectException {
065: // first check if the FlateDecode filter is turned on;
066: // if not, return this (we don't need to decompress).
067: // if so, turn off the filter in the new dictionary
068: Hashtable ht = _d.getHashtable();
069: Object obj = ht.get(PjName.FILTER);
070: if (obj == null) {
071: return this ;
072: }
073: if ((obj instanceof PjName) || (obj instanceof PjArray)) {
074: PjStreamDictionary newPjd = null;
075: Hashtable newHt;
076: if (obj instanceof PjName) {
077: PjName pjn = (PjName) obj;
078: if (!pjn.equals(PjName.FLATEDECODE)) {
079: return this ;
080: } else {
081: // remove the element from the cloned dictionary
082: try {
083: newPjd = (PjStreamDictionary) (_d.clone());
084: } catch (CloneNotSupportedException e) {
085: throw new InvalidPdfObjectException(e
086: .getMessage());
087: }
088: newHt = newPjd.getHashtable();
089: newHt.remove(PjName.FILTER);
090: }
091: } else if (obj instanceof PjArray) {
092: PjArray pja = (PjArray) obj;
093: Vector v = pja.getVector();
094: int x;
095: if ((x = v.indexOf(PjName.FLATEDECODE)) == -1) {
096: return this ;
097: } else {
098: // remove the element from the cloned dictionary
099: try {
100: newPjd = (PjStreamDictionary) (_d.clone());
101: } catch (CloneNotSupportedException e) {
102: throw new InvalidPdfObjectException(e
103: .getMessage());
104: }
105: newHt = newPjd.getHashtable();
106: pja = (PjArray) (newHt.get(PjName.FILTER));
107: v = pja.getVector();
108: v.removeElementAt(x);
109: }
110: }
111: // do the decompression
112: InflaterInputStream in = new InflaterInputStream(
113: new ByteArrayInputStream(_s));
114: ByteArrayOutputStream out = new ByteArrayOutputStream();
115: int length;
116: byte[] buffer = new byte[PjConst.FLATE_BUFFER_SIZE];
117: try {
118: while ((length = in.read(buffer, 0, buffer.length)) != -1) {
119: out.write(buffer, 0, length);
120: }
121: out.close();
122: in.close();
123: } catch (IOException e) {
124: // not sure what would cause this exception in this case
125: return this ;
126: }
127: return new PjStream(newPjd, out.toByteArray());
128: } else {
129: throw new InvalidPdfObjectException(
130: "Stream filter is not a name or array.");
131: }
132: }
133:
134: /**
135: Compress this stream with the Flate algorithm if it is not
136: already compressed.
137: @return a cloned, compressed version of this stream; or
138: this stream if it is already compressed.
139: @exception InvalidPdfObjectException if an invalid object
140: type is encountered.
141: */
142: public PjStream flateCompress() throws InvalidPdfObjectException {
143: // first check if any compression filters are turned on;
144: // if so, return this (we don't need to compress).
145: // if not, turn on the FlateDecode filter in the new dictionary
146: Hashtable ht = _d.getHashtable();
147: Object filter = ht.get(PjName.FILTER);
148: if (filter != null) {
149: if ((!(filter instanceof PjName))
150: && (!(filter instanceof PjArray))) {
151: throw new InvalidPdfObjectException(
152: "Stream filter is not a name or array.");
153: }
154: // get or create a vector with the list of filters
155: Vector v = null;
156: if (filter instanceof PjName) {
157: v = new Vector();
158: v.addElement(filter);
159: } else if (filter instanceof PjArray) {
160: v = ((PjArray) filter).getVector();
161: }
162: // see if any of the filters are compression filters
163: PjName name;
164: Enumeration m = v.elements();
165: Object obj;
166: while (m.hasMoreElements()) {
167: obj = m.nextElement();
168: if (!(obj instanceof PjName)) {
169: throw new InvalidPdfObjectException(
170: "Stream filter array contins an object that is not a name.");
171: }
172: name = (PjName) obj;
173: if ((name.equals(PjName.LZWDECODE))
174: || (name.equals(PjName.RUNLENGTHDECODE))
175: || (name.equals(PjName.CCITTFAXDECODE))
176: || (name.equals(PjName.DCTDECODE))
177: || (name.equals(PjName.FLATEDECODE))) {
178: return this ;
179: }
180: }
181: }
182: // ok, clone the dictionary and add the FlateDecode filter
183: PjStreamDictionary newPjd;
184: try {
185: newPjd = (PjStreamDictionary) (_d.clone());
186: } catch (CloneNotSupportedException e) {
187: throw new InvalidPdfObjectException(e.getMessage());
188: }
189: Hashtable newHt = newPjd.getHashtable();
190: if (filter == null) {
191: newHt.put(PjName.FILTER, PjName.FLATEDECODE);
192: } else {
193: if (filter instanceof PjArray) {
194: Vector v = ((PjArray) filter).getVector();
195: v.addElement(PjName.FLATEDECODE);
196: } else {
197: // filter must be a name, so make it into an array
198: Vector v = new Vector();
199: v.addElement(filter);
200: v.addElement(PjName.FLATEDECODE);
201: newHt.put(PjName.FILTER, new PjArray(v));
202: }
203: }
204: // do the compression
205: ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
206: DeflaterOutputStream out = new DeflaterOutputStream(
207: byteArrayOut);
208: ByteArrayInputStream in = new ByteArrayInputStream(_s);
209: int length;
210: byte[] buffer = new byte[PjConst.FLATE_BUFFER_SIZE];
211: try {
212: while ((length = in.read(buffer, 0, buffer.length)) != -1) {
213: out.write(buffer, 0, length);
214: }
215: out.close();
216: in.close();
217: } catch (IOException e) {
218: // not sure what would cause this exception in this case
219: return this ;
220: }
221: return new PjStream(newPjd, byteArrayOut.toByteArray());
222: }
223:
224: /**
225: Sets the Length field in the stream dictionary to
226: accurately reflect the length of the stream. It is not
227: normally necessary to call this method, because it gets
228: called implicitly by methods that output the object in PDF
229: format.
230: */
231: public void setLength() {
232: _d.getHashtable().put(new PjName("Length"),
233: new PjNumber(_s.length));
234: }
235:
236: /**
237: Writes this PDF stream to a stream in PDF format.
238: @param os the stream to write to.
239: @return the number of bytes written.
240: @exception IOException if an I/O error occurs.
241: */
242: public long writePdf(OutputStream os) throws IOException {
243: setLength();
244: long z = _d.writePdf(os);
245: z = z + writeln(os, "");
246: z = z + write(os, "stream\n");
247: z = z + write(os, _s);
248: z = z + write(os, "endstream");
249: return z;
250: }
251:
252: /**
253: Returns a deep copy of this object.
254: @return a deep copy of this object.
255: @exception CloneNotSupportedException if the instance can not be cloned.
256: */
257: public Object clone() throws CloneNotSupportedException {
258: return new PjStream((PjStreamDictionary) (_d.clone()),
259: (byte[]) (_s.clone()));
260: }
261:
262: /**
263: Renumbers object references within this object. This
264: method calls itself recursively to comprehensively renumber
265: all objects contained within this object.
266: @param map the table of object number mappings. Each
267: object number is looked up by key in the hash table, and
268: the associated value is assigned as the new object number.
269: The map hash table should consist of PjNumber keys and
270: PjReference values.
271: */
272: public void renumber(Hashtable map) {
273: _d.renumber(map);
274: }
275:
276: /**
277: Decode this stream if it is compressed with the Ascii85
278: algorithm.
279: @return a cloned, unencoded version of this stream; or
280: this stream if it is not marked as being compressed with
281: Ascii85.
282: @exception InvalidPdfObjectException if an invalid object
283: type is encountered.
284: */
285: public PjStream ascii85Decode() throws InvalidPdfObjectException {
286: // first check if the Ascii85Decode filter is turned on;
287: // if not, return this (we don't need to decompress).
288: // if so, turn off the filter in the new dictionary
289: Hashtable ht = _d.getHashtable();
290: Object obj = ht.get(PjName.FILTER);
291: if (obj == null) {
292: return this ;
293: }
294: if ((obj instanceof PjName) || (obj instanceof PjArray)) {
295: PjStreamDictionary newPjd = null;
296: Hashtable newHt;
297: if (obj instanceof PjName) {
298: PjName pjn = (PjName) obj;
299: if (!pjn.equals(PjName.ASCII85DECODE)) {
300: return this ;
301: } else {
302: // remove the element from the cloned dictionary
303: try {
304: newPjd = (PjStreamDictionary) (_d.clone());
305: } catch (CloneNotSupportedException e) {
306: throw new InvalidPdfObjectException(e
307: .getMessage());
308: }
309: newHt = newPjd.getHashtable();
310: newHt.remove(PjName.FILTER);
311: }
312: } else if (obj instanceof PjArray) {
313: PjArray pja = (PjArray) obj;
314: Vector v = pja.getVector();
315: int x;
316: if ((x = v.indexOf(PjName.ASCII85DECODE)) == -1) {
317: return this ;
318: } else {
319: // remove the element from the cloned dictionary
320: try {
321: newPjd = (PjStreamDictionary) (_d.clone());
322: } catch (CloneNotSupportedException e) {
323: throw new InvalidPdfObjectException(e
324: .getMessage());
325: }
326: newHt = newPjd.getHashtable();
327: pja = (PjArray) (newHt.get(PjName.FILTER));
328: v = pja.getVector();
329: v.removeElementAt(x);
330: }
331: }
332: // do the decoding
333: // the raw data resides in _s.
334: byte[] decodedBuf = ascii85Decode(_s);
335: if (decodedBuf == null)
336: return this ;
337: return new PjStream(newPjd, decodedBuf);
338: } else {
339: throw new InvalidPdfObjectException(
340: "Stream filter is not a name or array.");
341: }
342: }
343:
344: protected PjStreamDictionary _d;
345: protected byte[] _s;
346:
347: static long bytesToLong(byte[] b, int offset, int len) {
348: long val = 0, exp = 0;
349: for (int i = offset + len - 1; i >= offset; i--) {
350: for (int j = 7; j >= 0; j--) {
351: byte mask = (byte) (128 >> j);
352: if ((b[i] & mask) == mask) {
353: val += Math.pow(2, exp);
354: }
355: exp++;
356: }
357: }
358: return val;
359: }
360:
361: static char[] ascii85EncodeWord(long word) {
362: long v1, v2, v3, v4, v5;
363: long p4 = (long) Math.pow(85, 4);
364: long p3 = (long) Math.pow(85, 3);
365: long p2 = (long) Math.pow(85, 2);
366: v1 = word / p4;
367: word = word - (v1 * p4);
368: v2 = word / p3;
369: word = word - (v2 * p3);
370: v3 = word / p2;
371: word = word - (v3 * p2);
372: v4 = word / 85;
373: word = word - (v4 * 85);
374: v5 = word;
375: char[] c = new char[5];
376: c[0] = (char) (v1 + '!');
377: c[1] = (char) (v2 + '!');
378: c[2] = (char) (v3 + '!');
379: c[3] = (char) (v4 + '!');
380: c[4] = (char) (v5 + '!');
381:
382: return c;
383: }
384:
385: public static byte[] ascii85Encode(byte[] src) {
386: ByteArrayOutputStream out = new ByteArrayOutputStream();
387: try {
388: int groupCount = src.length / 4;
389: int extraCount = src.length % 4;
390: int i;
391: for (i = 0; i < groupCount; i++) {
392: long word = bytesToLong(src, i * 4, 4);
393: if (word == 0) {
394: out.write('z');
395: continue;
396: }
397: char[] c = ascii85EncodeWord(word);
398: out.write(c[0]);
399: out.write(c[1]);
400: out.write(c[2]);
401: out.write(c[3]);
402: out.write(c[4]);
403: }
404: if (extraCount > 0) {
405: byte[] buf = new byte[4];
406: if (extraCount == 1) {
407: buf[3] = src[i * 4];
408: buf[2] = 0;
409: buf[1] = 0;
410: buf[0] = 0;
411: } else if (extraCount == 2) {
412: buf[3] = src[i * 4 + 1];
413: buf[2] = src[i * 4];
414: buf[1] = 0;
415: buf[0] = 0;
416: } else {
417: buf[3] = src[i * 4 + 2];
418: buf[2] = src[i * 4 + 1];
419: buf[1] = src[i * 4];
420: buf[0] = 0;
421: }
422: long word = bytesToLong(buf, 0, 4);
423: char[] c = ascii85EncodeWord(word);
424: for (i = 5 - (extraCount + 1); i < 5; i++) {
425: out.write(c[i]);
426: }
427: }
428: out.write('~');
429: out.write('>');
430: out.close();
431: } catch (IOException e) {
432: System.out.println(e);
433: }
434: return out.toByteArray();
435: }
436:
437: static long toWord(byte[] b, int offset, int sigDigits) {
438: long v1, v2, v3, v4, v5;
439: long p4 = (long) Math.pow(85, 4);
440: long p3 = (long) Math.pow(85, 3);
441: long p2 = (long) Math.pow(85, 2);
442: v1 = (b[offset] - '!') * p4;
443: v2 = (b[offset + 1] - '!') * p3;
444: v3 = (b[offset + 2] - '!') * p2;
445: v4 = (b[offset + 3] - '!') * 85;
446: v5 = (b[offset + 4] - '!');
447: if (sigDigits == 5)
448: return v1 + v2 + v3 + v4 + v5;
449: else if (sigDigits == 4)
450: return v2 + v3 + v4 + v5;
451: else if (sigDigits == 3)
452: return v3 + v4 + v5;
453: else if (sigDigits == 2)
454: return v4 + v5;
455: else
456: return v5;
457: }
458:
459: public static byte[] ascii85Decode(byte[] src) {
460: ByteArrayOutputStream out = new ByteArrayOutputStream();
461: try {
462: int ptr = 0;
463: int len = src.length - 2; // ignore end of
464: // data marker
465:
466: for (;;) {
467: if (((len - ptr) < 5) && (src[ptr] != 'z')) {
468: break;
469: }
470: long word = 0;
471: if (src[ptr] == 'z') {
472: ptr++;
473: } else {
474: word = toWord(src, ptr, 5);
475: ptr += 5;
476: }
477: byte b4 = (byte) (word & 255);
478: byte b3 = (byte) ((word >>> 8) & 255);
479: byte b2 = (byte) ((word >>> 16) & 255);
480: byte b1 = (byte) ((word >>> 24) & 255);
481: out.write(b1);
482: out.write(b2);
483: out.write(b3);
484: out.write(b4);
485: }
486: if ((len - ptr) > 0) {
487: // We have extra bytes
488: int count = len - ptr;
489: // The acutal number of binary bytes is 1 less
490: // than count
491: byte[] buf = new byte[5];
492:
493: int pad = 5 - count;
494: int j = 0;
495: for (int i = 0; i < 5; i++) {
496: if (i < pad) {
497: buf[j++] = 0;
498: } else {
499: buf[j++] = src[ptr];
500: ptr++;
501: }
502: }
503: long word = toWord(buf, 0, count);
504: byte b4 = (byte) (word & 255);
505: byte b3 = (byte) ((word >>> 8) & 255);
506: byte b2 = (byte) ((word >>> 16) & 255);
507: byte b1 = (byte) ((word >>> 24) & 255);
508: count -= 1;
509: if (count == 1) {
510: out.write(b4);
511: } else if (count == 2) {
512: out.write(b3);
513: out.write(b4);
514: } else if (count == 3) {
515: out.write(b2);
516: out.write(b3);
517: out.write(b4);
518: }
519: }
520: out.close();
521: } catch (IOException e) {
522: System.out.println(e);
523: }
524: return out.toByteArray();
525: }
526: }
|