001/*
002 * Copyright 2003-2008 by Paulo Soares.
003 *
004 * This code was originally released in 2001 by SUN (see class
005 * com.sun.media.imageio.plugins.tiff.TIFFDirectory.java)
006 * using the BSD license in a specific wording. In a mail dating from
007 * January 23, 2008, Brian Burkhalter (@sun.com) gave us permission
008 * to use the code under the following version of the BSD license:
009 *
010 * Copyright (c) 2006 Sun Microsystems, Inc. All  Rights Reserved.
011 *
012 * Redistribution and use in source and binary forms, with or without
013 * modification, are permitted provided that the following conditions
014 * are met:
015 *
016 * - Redistribution of source code must retain the above copyright
017 *   notice, this  list of conditions and the following disclaimer.
018 *
019 * - Redistribution in binary form must reproduce the above copyright
020 *   notice, this list of conditions and the following disclaimer in
021 *   the documentation and/or other materials provided with the
022 *   distribution.
023 *
024 * Neither the name of Sun Microsystems, Inc. or the names of
025 * contributors may be used to endorse or promote products derived
026 * from this software without specific prior written permission.
027 *
028 * This software is provided "AS IS," without a warranty of any
029 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
030 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
031 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
032 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
033 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
034 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
035 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
036 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
037 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
038 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
039 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
040 * POSSIBILITY OF SUCH DAMAGES.
041 *
042 * You acknowledge that this software is not designed or intended for
043 * use in the design, construction, operation or maintenance of any
044 * nuclear facility.
045 */
046package com.itextpdf.text.pdf.codec;
047import java.io.EOFException;
048import java.io.IOException;
049import java.io.Serializable;
050import java.util.ArrayList;
051import java.util.Enumeration;
052import java.util.Hashtable;
053
054import com.itextpdf.text.error_messages.MessageLocalization;
055import com.itextpdf.text.pdf.RandomAccessFileOrArray;
056
057/**
058 * A class representing an Image File Directory (IFD) from a TIFF 6.0
059 * stream.  The TIFF file format is described in more detail in the
060 * comments for the TIFFDescriptor class.
061 *
062 * <p> A TIFF IFD consists of a set of TIFFField tags.  Methods are
063 * provided to query the set of tags and to obtain the raw field
064 * array.  In addition, convenience methods are provided for acquiring
065 * the values of tags that contain a single value that fits into a
066 * byte, int, long, float, or double.
067 *
068 * <p> Every TIFF file is made up of one or more public IFDs that are
069 * joined in a linked list, rooted in the file header.  A file may
070 * also contain so-called private IFDs that are referenced from
071 * tag data and do not appear in the main list.
072 *
073 * <p><b> This class is not a committed part of the JAI API.  It may
074 * be removed or changed in future releases of JAI.</b>
075 *
076 * @see TIFFField
077 */
078public class TIFFDirectory extends Object implements Serializable {
079
080    private static final long serialVersionUID = -168636766193675380L;
081
082        /** A boolean storing the endianness of the stream. */
083    boolean isBigEndian;
084
085    /** The number of entries in the IFD. */
086    int numEntries;
087
088    /** An array of TIFFFields. */
089    TIFFField[] fields;
090
091    /** A Hashtable indexing the fields by tag number. */
092    Hashtable<Integer, Integer> fieldIndex = new Hashtable<Integer, Integer>();
093
094    /** The offset of this IFD. */
095    long IFDOffset = 8;
096
097    /** The offset of the next IFD. */
098    long nextIFDOffset = 0;
099
100    /** The default constructor. */
101    TIFFDirectory() {}
102
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     */
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     */
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
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(Integer.valueOf(tag), Integer.valueOf(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<String> v = new ArrayList<String>();
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] = 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. */
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     */
397    public TIFFField getField(int tag) {
398        Integer i = fieldIndex.get(Integer.valueOf(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     */
409    public boolean isTagPresent(int tag) {
410        return fieldIndex.containsKey(Integer.valueOf(tag));
411    }
412
413    /**
414     * Returns an ordered array of ints indicating the tag
415     * values.
416     */
417    public int[] getTags() {
418        int[] tags = new int[fieldIndex.size()];
419        Enumeration<Integer> e = fieldIndex.keys();
420        int i = 0;
421
422        while (e.hasMoreElements()) {
423            tags[i++] = 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     */
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     */
443    public byte getFieldAsByte(int tag, int index) {
444        Integer i = fieldIndex.get(Integer.valueOf(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     */
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     */
465    public long getFieldAsLong(int tag, int index) {
466        Integer i = fieldIndex.get(Integer.valueOf(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     */
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     */
486    public float getFieldAsFloat(int tag, int index) {
487        Integer i = fieldIndex.get(Integer.valueOf(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     */
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     */
506    public double getFieldAsDouble(int tag, int index) {
507        Integer i = fieldIndex.get(Integer.valueOf(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     */
516    public double getFieldAsDouble(int tag) {
517        return getFieldAsDouble(tag, 0);
518    }
519
520    // Methods to read primitive data types from the stream
521
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
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
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
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
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
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
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
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
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     */
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     */
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     */
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     */
670    public long getNextIFDOffset() {
671        return nextIFDOffset;
672    }
673}