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