001: /*
002: * Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * -Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * -Redistribution in binary form must reproduct 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: *
014: * Neither the name of Sun Microsystems, Inc. or the names of contributors may
015: * be used to endorse or promote products derived from this software without
016: * specific prior written permission.
017: *
018: * This software is provided "AS IS," without a warranty of any kind. ALL
019: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
020: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
021: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
022: * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
023: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
024: * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
025: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
026: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
027: * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGES.
029: *
030: * You acknowledge that Software is not designed,licensed or intended for use in
031: * the design, construction, operation or maintenance of any nuclear facility.
032: */
033: package com.lowagie.text.pdf.codec;
034:
035: import java.io.EOFException;
036: import java.io.IOException;
037: import java.io.Serializable;
038: import java.util.ArrayList;
039: import java.util.Enumeration;
040: import java.util.Hashtable;
041:
042: import com.lowagie.text.pdf.RandomAccessFileOrArray;
043:
044: /**
045: * A class representing an Image File Directory (IFD) from a TIFF 6.0
046: * stream. The TIFF file format is described in more detail in the
047: * comments for the TIFFDescriptor class.
048: *
049: * <p> A TIFF IFD consists of a set of TIFFField tags. Methods are
050: * provided to query the set of tags and to obtain the raw field
051: * array. In addition, convenience methods are provided for acquiring
052: * the values of tags that contain a single value that fits into a
053: * byte, int, long, float, or double.
054: *
055: * <p> Every TIFF file is made up of one or more public IFDs that are
056: * joined in a linked list, rooted in the file header. A file may
057: * also contain so-called private IFDs that are referenced from
058: * tag data and do not appear in the main list.
059: *
060: * <p><b> This class is not a committed part of the JAI API. It may
061: * be removed or changed in future releases of JAI.</b>
062: *
063: * @see TIFFField
064: */
065: public class TIFFDirectory extends Object implements Serializable {
066:
067: private static final long serialVersionUID = -168636766193675380L;
068:
069: /** A boolean storing the endianness of the stream. */
070: boolean isBigEndian;
071:
072: /** The number of entries in the IFD. */
073: int numEntries;
074:
075: /** An array of TIFFFields. */
076: TIFFField[] fields;
077:
078: /** A Hashtable indexing the fields by tag number. */
079: Hashtable fieldIndex = new Hashtable();
080:
081: /** The offset of this IFD. */
082: long IFDOffset = 8;
083:
084: /** The offset of the next IFD. */
085: long nextIFDOffset = 0;
086:
087: /** The default constructor. */
088: TIFFDirectory() {
089: }
090:
091: private static boolean isValidEndianTag(int endian) {
092: return ((endian == 0x4949) || (endian == 0x4d4d));
093: }
094:
095: /**
096: * Constructs a TIFFDirectory from a SeekableStream.
097: * The directory parameter specifies which directory to read from
098: * the linked list present in the stream; directory 0 is normally
099: * read but it is possible to store multiple images in a single
100: * TIFF file by maintaing multiple directories.
101: *
102: * @param stream a SeekableStream to read from.
103: * @param directory the index of the directory to read.
104: */
105: public TIFFDirectory(RandomAccessFileOrArray stream, int directory)
106: throws IOException {
107:
108: long global_save_offset = stream.getFilePointer();
109: long ifd_offset;
110:
111: // Read the TIFF header
112: stream.seek(0L);
113: int endian = stream.readUnsignedShort();
114: if (!isValidEndianTag(endian)) {
115: throw new IllegalArgumentException(
116: "Bad endianness tag (not 0x4949 or 0x4d4d).");
117: }
118: isBigEndian = (endian == 0x4d4d);
119:
120: int magic = readUnsignedShort(stream);
121: if (magic != 42) {
122: throw new IllegalArgumentException(
123: "Bad magic number, should be 42.");
124: }
125:
126: // Get the initial ifd offset as an unsigned int (using a long)
127: ifd_offset = readUnsignedInt(stream);
128:
129: for (int i = 0; i < directory; i++) {
130: if (ifd_offset == 0L) {
131: throw new IllegalArgumentException(
132: "Directory number too large.");
133: }
134:
135: stream.seek(ifd_offset);
136: int entries = readUnsignedShort(stream);
137: stream.skip(12 * entries);
138:
139: ifd_offset = readUnsignedInt(stream);
140: }
141:
142: stream.seek(ifd_offset);
143: initialize(stream);
144: stream.seek(global_save_offset);
145: }
146:
147: /**
148: * Constructs a TIFFDirectory by reading a SeekableStream.
149: * The ifd_offset parameter specifies the stream offset from which
150: * to begin reading; this mechanism is sometimes used to store
151: * private IFDs within a TIFF file that are not part of the normal
152: * sequence of IFDs.
153: *
154: * @param stream a SeekableStream to read from.
155: * @param ifd_offset the long byte offset of the directory.
156: * @param directory the index of the directory to read beyond the
157: * one at the current stream offset; zero indicates the IFD
158: * at the current offset.
159: */
160: public TIFFDirectory(RandomAccessFileOrArray stream,
161: long ifd_offset, int directory) throws IOException {
162:
163: long global_save_offset = stream.getFilePointer();
164: stream.seek(0L);
165: int endian = stream.readUnsignedShort();
166: if (!isValidEndianTag(endian)) {
167: throw new IllegalArgumentException(
168: "Bad endianness tag (not 0x4949 or 0x4d4d).");
169: }
170: isBigEndian = (endian == 0x4d4d);
171:
172: // Seek to the first IFD.
173: stream.seek(ifd_offset);
174:
175: // Seek to desired IFD if necessary.
176: int dirNum = 0;
177: while (dirNum < directory) {
178: // Get the number of fields in the current IFD.
179: int numEntries = readUnsignedShort(stream);
180:
181: // Skip to the next IFD offset value field.
182: stream.seek(ifd_offset + 12 * numEntries);
183:
184: // Read the offset to the next IFD beyond this one.
185: ifd_offset = readUnsignedInt(stream);
186:
187: // Seek to the next IFD.
188: stream.seek(ifd_offset);
189:
190: // Increment the directory.
191: dirNum++;
192: }
193:
194: initialize(stream);
195: stream.seek(global_save_offset);
196: }
197:
198: private static final int[] sizeOfType = { 0, // 0 = n/a
199: 1, // 1 = byte
200: 1, // 2 = ascii
201: 2, // 3 = short
202: 4, // 4 = long
203: 8, // 5 = rational
204: 1, // 6 = sbyte
205: 1, // 7 = undefined
206: 2, // 8 = sshort
207: 4, // 9 = slong
208: 8, // 10 = srational
209: 4, // 11 = float
210: 8 // 12 = double
211: };
212:
213: private void initialize(RandomAccessFileOrArray stream)
214: throws IOException {
215: long nextTagOffset = 0L;
216: long maxOffset = stream.length();
217: int i, j;
218:
219: IFDOffset = stream.getFilePointer();
220:
221: numEntries = readUnsignedShort(stream);
222: fields = new TIFFField[numEntries];
223:
224: for (i = 0; (i < numEntries) && (nextTagOffset < maxOffset); i++) {
225: int tag = readUnsignedShort(stream);
226: int type = readUnsignedShort(stream);
227: int count = (int) (readUnsignedInt(stream));
228: boolean processTag = true;
229:
230: // The place to return to to read the next tag
231: nextTagOffset = stream.getFilePointer() + 4;
232:
233: try {
234: // If the tag data can't fit in 4 bytes, the next 4 bytes
235: // contain the starting offset of the data
236: if (count * sizeOfType[type] > 4) {
237: long valueOffset = readUnsignedInt(stream);
238:
239: // bounds check offset for EOF
240: if (valueOffset < maxOffset) {
241: stream.seek(valueOffset);
242: } else {
243: // bad offset pointer .. skip tag
244: processTag = false;
245: }
246: }
247: } catch (ArrayIndexOutOfBoundsException ae) {
248: // if the data type is unknown we should skip this TIFF Field
249: processTag = false;
250: }
251:
252: if (processTag) {
253: fieldIndex.put(new Integer(tag), new Integer(i));
254: Object obj = null;
255:
256: switch (type) {
257: case TIFFField.TIFF_BYTE:
258: case TIFFField.TIFF_SBYTE:
259: case TIFFField.TIFF_UNDEFINED:
260: case TIFFField.TIFF_ASCII:
261: byte[] bvalues = new byte[count];
262: stream.readFully(bvalues, 0, count);
263:
264: if (type == TIFFField.TIFF_ASCII) {
265:
266: // Can be multiple strings
267: int index = 0, prevIndex = 0;
268: ArrayList v = new ArrayList();
269:
270: while (index < count) {
271:
272: while ((index < count)
273: && (bvalues[index++] != 0))
274: ;
275:
276: // When we encountered zero, means one string has ended
277: v.add(new String(bvalues, prevIndex,
278: (index - prevIndex)));
279: prevIndex = index;
280: }
281:
282: count = v.size();
283: String strings[] = new String[count];
284: for (int c = 0; c < count; c++) {
285: strings[c] = (String) v.get(c);
286: }
287:
288: obj = strings;
289: } else {
290: obj = bvalues;
291: }
292:
293: break;
294:
295: case TIFFField.TIFF_SHORT:
296: char[] cvalues = new char[count];
297: for (j = 0; j < count; j++) {
298: cvalues[j] = (char) (readUnsignedShort(stream));
299: }
300: obj = cvalues;
301: break;
302:
303: case TIFFField.TIFF_LONG:
304: long[] lvalues = new long[count];
305: for (j = 0; j < count; j++) {
306: lvalues[j] = readUnsignedInt(stream);
307: }
308: obj = lvalues;
309: break;
310:
311: case TIFFField.TIFF_RATIONAL:
312: long[][] llvalues = new long[count][2];
313: for (j = 0; j < count; j++) {
314: llvalues[j][0] = readUnsignedInt(stream);
315: llvalues[j][1] = readUnsignedInt(stream);
316: }
317: obj = llvalues;
318: break;
319:
320: case TIFFField.TIFF_SSHORT:
321: short[] svalues = new short[count];
322: for (j = 0; j < count; j++) {
323: svalues[j] = readShort(stream);
324: }
325: obj = svalues;
326: break;
327:
328: case TIFFField.TIFF_SLONG:
329: int[] ivalues = new int[count];
330: for (j = 0; j < count; j++) {
331: ivalues[j] = readInt(stream);
332: }
333: obj = ivalues;
334: break;
335:
336: case TIFFField.TIFF_SRATIONAL:
337: int[][] iivalues = new int[count][2];
338: for (j = 0; j < count; j++) {
339: iivalues[j][0] = readInt(stream);
340: iivalues[j][1] = readInt(stream);
341: }
342: obj = iivalues;
343: break;
344:
345: case TIFFField.TIFF_FLOAT:
346: float[] fvalues = new float[count];
347: for (j = 0; j < count; j++) {
348: fvalues[j] = readFloat(stream);
349: }
350: obj = fvalues;
351: break;
352:
353: case TIFFField.TIFF_DOUBLE:
354: double[] dvalues = new double[count];
355: for (j = 0; j < count; j++) {
356: dvalues[j] = readDouble(stream);
357: }
358: obj = dvalues;
359: break;
360:
361: default:
362: break;
363: }
364:
365: fields[i] = new TIFFField(tag, type, count, obj);
366: }
367:
368: stream.seek(nextTagOffset);
369: }
370:
371: // Read the offset of the next IFD.
372: try {
373: nextIFDOffset = readUnsignedInt(stream);
374: } catch (Exception e) {
375: // broken tiffs may not have this pointer
376: nextIFDOffset = 0;
377: }
378: }
379:
380: /** Returns the number of directory entries. */
381: public int getNumEntries() {
382: return numEntries;
383: }
384:
385: /**
386: * Returns the value of a given tag as a TIFFField,
387: * or null if the tag is not present.
388: */
389: public TIFFField getField(int tag) {
390: Integer i = (Integer) fieldIndex.get(new Integer(tag));
391: if (i == null) {
392: return null;
393: } else {
394: return fields[i.intValue()];
395: }
396: }
397:
398: /**
399: * Returns true if a tag appears in the directory.
400: */
401: public boolean isTagPresent(int tag) {
402: return fieldIndex.containsKey(new Integer(tag));
403: }
404:
405: /**
406: * Returns an ordered array of ints indicating the tag
407: * values.
408: */
409: public int[] getTags() {
410: int[] tags = new int[fieldIndex.size()];
411: Enumeration e = fieldIndex.keys();
412: int i = 0;
413:
414: while (e.hasMoreElements()) {
415: tags[i++] = ((Integer) e.nextElement()).intValue();
416: }
417:
418: return tags;
419: }
420:
421: /**
422: * Returns an array of TIFFFields containing all the fields
423: * in this directory.
424: */
425: public TIFFField[] getFields() {
426: return fields;
427: }
428:
429: /**
430: * Returns the value of a particular index of a given tag as a
431: * byte. The caller is responsible for ensuring that the tag is
432: * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or
433: * TIFF_UNDEFINED.
434: */
435: public byte getFieldAsByte(int tag, int index) {
436: Integer i = (Integer) fieldIndex.get(new Integer(tag));
437: byte[] b = (fields[i.intValue()]).getAsBytes();
438: return b[index];
439: }
440:
441: /**
442: * Returns the value of index 0 of a given tag as a
443: * byte. The caller is responsible for ensuring that the tag is
444: * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or
445: * TIFF_UNDEFINED.
446: */
447: public byte getFieldAsByte(int tag) {
448: return getFieldAsByte(tag, 0);
449: }
450:
451: /**
452: * Returns the value of a particular index of a given tag as a
453: * long. The caller is responsible for ensuring that the tag is
454: * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED,
455: * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG.
456: */
457: public long getFieldAsLong(int tag, int index) {
458: Integer i = (Integer) fieldIndex.get(new Integer(tag));
459: return fields[i.intValue()].getAsLong(index);
460: }
461:
462: /**
463: * Returns the value of index 0 of a given tag as a
464: * long. The caller is responsible for ensuring that the tag is
465: * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED,
466: * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG.
467: */
468: public long getFieldAsLong(int tag) {
469: return getFieldAsLong(tag, 0);
470: }
471:
472: /**
473: * Returns the value of a particular index of a given tag as a
474: * float. The caller is responsible for ensuring that the tag is
475: * present and has numeric type (all but TIFF_UNDEFINED and
476: * TIFF_ASCII).
477: */
478: public float getFieldAsFloat(int tag, int index) {
479: Integer i = (Integer) fieldIndex.get(new Integer(tag));
480: return fields[i.intValue()].getAsFloat(index);
481: }
482:
483: /**
484: * Returns the value of index 0 of a given tag as a float. The
485: * caller is responsible for ensuring that the tag is present and
486: * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII).
487: */
488: public float getFieldAsFloat(int tag) {
489: return getFieldAsFloat(tag, 0);
490: }
491:
492: /**
493: * Returns the value of a particular index of a given tag as a
494: * double. The caller is responsible for ensuring that the tag is
495: * present and has numeric type (all but TIFF_UNDEFINED and
496: * TIFF_ASCII).
497: */
498: public double getFieldAsDouble(int tag, int index) {
499: Integer i = (Integer) fieldIndex.get(new Integer(tag));
500: return fields[i.intValue()].getAsDouble(index);
501: }
502:
503: /**
504: * Returns the value of index 0 of a given tag as a double. The
505: * caller is responsible for ensuring that the tag is present and
506: * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII).
507: */
508: public double getFieldAsDouble(int tag) {
509: return getFieldAsDouble(tag, 0);
510: }
511:
512: // Methods to read primitive data types from the stream
513:
514: private short readShort(RandomAccessFileOrArray stream)
515: throws IOException {
516: if (isBigEndian) {
517: return stream.readShort();
518: } else {
519: return stream.readShortLE();
520: }
521: }
522:
523: private int readUnsignedShort(RandomAccessFileOrArray stream)
524: throws IOException {
525: if (isBigEndian) {
526: return stream.readUnsignedShort();
527: } else {
528: return stream.readUnsignedShortLE();
529: }
530: }
531:
532: private int readInt(RandomAccessFileOrArray stream)
533: throws IOException {
534: if (isBigEndian) {
535: return stream.readInt();
536: } else {
537: return stream.readIntLE();
538: }
539: }
540:
541: private long readUnsignedInt(RandomAccessFileOrArray stream)
542: throws IOException {
543: if (isBigEndian) {
544: return stream.readUnsignedInt();
545: } else {
546: return stream.readUnsignedIntLE();
547: }
548: }
549:
550: private long readLong(RandomAccessFileOrArray stream)
551: throws IOException {
552: if (isBigEndian) {
553: return stream.readLong();
554: } else {
555: return stream.readLongLE();
556: }
557: }
558:
559: private float readFloat(RandomAccessFileOrArray stream)
560: throws IOException {
561: if (isBigEndian) {
562: return stream.readFloat();
563: } else {
564: return stream.readFloatLE();
565: }
566: }
567:
568: private double readDouble(RandomAccessFileOrArray stream)
569: throws IOException {
570: if (isBigEndian) {
571: return stream.readDouble();
572: } else {
573: return stream.readDoubleLE();
574: }
575: }
576:
577: private static int readUnsignedShort(
578: RandomAccessFileOrArray stream, boolean isBigEndian)
579: throws IOException {
580: if (isBigEndian) {
581: return stream.readUnsignedShort();
582: } else {
583: return stream.readUnsignedShortLE();
584: }
585: }
586:
587: private static long readUnsignedInt(RandomAccessFileOrArray stream,
588: boolean isBigEndian) throws IOException {
589: if (isBigEndian) {
590: return stream.readUnsignedInt();
591: } else {
592: return stream.readUnsignedIntLE();
593: }
594: }
595:
596: // Utilities
597:
598: /**
599: * Returns the number of image directories (subimages) stored in a
600: * given TIFF file, represented by a <code>SeekableStream</code>.
601: */
602: public static int getNumDirectories(RandomAccessFileOrArray stream)
603: throws IOException {
604: long pointer = stream.getFilePointer(); // Save stream pointer
605:
606: stream.seek(0L);
607: int endian = stream.readUnsignedShort();
608: if (!isValidEndianTag(endian)) {
609: throw new IllegalArgumentException(
610: "Bad endianness tag (not 0x4949 or 0x4d4d).");
611: }
612: boolean isBigEndian = (endian == 0x4d4d);
613: int magic = readUnsignedShort(stream, isBigEndian);
614: if (magic != 42) {
615: throw new IllegalArgumentException(
616: "Bad magic number, should be 42.");
617: }
618:
619: stream.seek(4L);
620: long offset = readUnsignedInt(stream, isBigEndian);
621:
622: int numDirectories = 0;
623: while (offset != 0L) {
624: ++numDirectories;
625:
626: // EOFException means IFD was probably not properly terminated.
627: try {
628: stream.seek(offset);
629: int entries = readUnsignedShort(stream, isBigEndian);
630: stream.skip(12 * entries);
631: offset = readUnsignedInt(stream, isBigEndian);
632: } catch (EOFException eof) {
633: //numDirectories--;
634: break;
635: }
636: }
637:
638: stream.seek(pointer); // Reset stream pointer
639: return numDirectories;
640: }
641:
642: /**
643: * Returns a boolean indicating whether the byte order used in the
644: * the TIFF file is big-endian (i.e. whether the byte order is from
645: * the most significant to the least significant)
646: */
647: public boolean isBigEndian() {
648: return isBigEndian;
649: }
650:
651: /**
652: * Returns the offset of the IFD corresponding to this
653: * <code>TIFFDirectory</code>.
654: */
655: public long getIFDOffset() {
656: return IFDOffset;
657: }
658:
659: /**
660: * Returns the offset of the next IFD after the IFD corresponding to this
661: * <code>TIFFDirectory</code>.
662: */
663: public long getNextIFDOffset() {
664: return nextIFDOffset;
665: }
666: }
|