<?php

/*  PEL: PHP Exif Library.  A library with support for reading and
 *  writing all Exif headers in JPEG and TIFF images using PHP.
 *
 *  Copyright (C) 2004, 2005, 2006  Martin Geisler.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program in the file COPYING; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 *  Boston, MA 02110-1301 USA
 */

/* $Id: PelTiff.php 13759 2012-07-04 04:31:41Z giangnd $ */


/**
 * Classes for dealing with TIFF data.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @version $Revision: 458 $
 * @date $Date: 2006-11-18 22:22:58 +0100 (Sat, 18 Nov 2006) $
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public
 * License (GPL)
 * @package PEL
 */

/**#@+ Required class definitions. */
require_once('PelDataWindow.php');
require_once('PelIfd.php');
require_once('Pel.php');
/**#@-*/


/**
 * Class for handling TIFF data.
 *
 * Exif data is actually an extension of the TIFF file format.  TIFF
 * images consist of a number of {@link PelIfd Image File Directories}
 * (IFDs), each containing a number of {@link PelEntry entries}.  The
 * IFDs are linked to each other --- one can get hold of the first one
 * with the {@link getIfd()} method.
 *
 * To parse a TIFF image for Exif data one would do:
 *
 * <code>
 * $tiff = new PelTiff($data);
 * $ifd0 = $tiff->getIfd();
 * $exif = $ifd0->getSubIfd(PelIfd::EXIF);
 * $ifd1 = $ifd0->getNextIfd();
 * </code>
 *
 * Should one have some image data of an unknown type, then the {@link
 * PelTiff::isValid()} function is handy: it will quickly test if the
 * data could be valid TIFF data.  The {@link PelJpeg::isValid()}
 * function does the same for JPEG images.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @package PEL
 */
class PelTiff {

	/**
	 * TIFF header.
	 *
	 * This must follow after the two bytes indicating the byte order.
	 */
	const TIFF_HEADER = 0x002A;

	/**
	 * The first Image File Directory, if any.
	 *
	 * If set, then the type of the IFD must be {@link PelIfd::IFD0}.
	 *
	 * @var PelIfd
	 */
	private $ifd = null;


	/**
	 * Construct a new object for holding TIFF data.
	 *
	 * The new object will be empty (with no {@link PelIfd}) unless an
	 * argument is given from which it can initialize itself. This can
	 * either be the filename of a TIFF image or a {@link PelDataWindow}
	 * object.
	 *
	 * Use {@link setIfd()} to explicitly set the IFD.
	 */
	function __construct($data = false) {
		if ($data === false)
		return;

		if (is_string($data)) {
			Pel::debug('Initializing PelTiff object from %s', $data);
			$this->loadFile($data);
		} elseif ($data instanceof PelDataWindow) {
			Pel::debug('Initializing PelTiff object from PelDataWindow.');
			$this->load($data);
		} else {
			throw new PelInvalidArgumentException('Bad type for $data: %s',
			gettype($data));
		}
	}


	/**
	 * Load TIFF data.
	 *
	 * The data given will be parsed and an internal tree representation
	 * will be built.  If the data cannot be parsed correctly, a {@link
	 * PelInvalidDataException} is thrown, explaining the problem.
	 *
	 * @param PelDataWindow the data from which the object will be
	 * constructed.  This should be valid TIFF data, coming either
	 * directly from a TIFF image or from the Exif data in a JPEG image.
	 */
	function load(PelDataWindow $d) {
		Pel::debug('Parsing %d bytes of TIFF data...', $d->getSize());

		/* There must be at least 8 bytes available: 2 bytes for the byte
		 * order, 2 bytes for the TIFF header, and 4 bytes for the offset
		 * to the first IFD. */
		if ($d->getSize() < 8)
		throw new PelInvalidDataException('Expected at least 8 bytes of TIFF ' .
                                        'data, found just %d bytes.',
		$d->getSize());

		/* Byte order */
		if ($d->strcmp(0, 'II')) {
			Pel::debug('Found Intel byte order');
			$d->setByteOrder(PelConvert::LITTLE_ENDIAN);
		} elseif ($d->strcmp(0, 'MM')) {
			Pel::debug('Found Motorola byte order');
			$d->setByteOrder(PelConvert::BIG_ENDIAN);
		} else {
			throw new PelInvalidDataException('Unknown byte order found in TIFF ' .
                                        'data: 0x%2X%2X',
			$d->getByte(0), $d->getByte(1));
		}

		/* Verify the TIFF header */
		if ($d->getShort(2) != self::TIFF_HEADER)
		throw new PelInvalidDataException('Missing TIFF magic value.');

		/* IFD 0 offset */
		$offset = $d->getLong(4);
		Pel::debug('First IFD at offset %d.', $offset);

		if ($offset > 0) {
			/* Parse the first IFD, this will automatically parse the
			 * following IFDs and any sub IFDs. */
			$this->ifd = new PelIfd(PelIfd::IFD0);
			$this->ifd->load($d, $offset);
		}
	}


	/**
	 * Load data from a file into a TIFF object.
	 *
	 * @param string the filename.  This must be a readable file.
	 */
	function loadFile($filename) {
		$this->load(new PelDataWindow(file_get_contents($filename)));
	}


	/**
	 * Set the first IFD.
	 *
	 * @param PelIfd the new first IFD, which must be of type {@link
	 * PelIfd::IFD0}.
	 */
	function setIfd(PelIfd $ifd) {
		if ($ifd->getType() != PelIfd::IFD0)
		throw new PelInvalidDataException('Invalid type of IFD: %d, expected %d.',
		$ifd->getType(), PelIfd::IFD0);

		$this->ifd = $ifd;
	}


	/**
	 * Return the first IFD.
	 *
	 * @return PelIfd the first IFD contained in the TIFF data, if any.
	 * If there is no IFD null will be returned.
	 */
	function getIfd() {
		return $this->ifd;
	}


	/**
	 * Turn this object into bytes.
	 *
	 * TIFF images can have {@link PelConvert::LITTLE_ENDIAN
	 * little-endian} or {@link PelConvert::BIG_ENDIAN big-endian} byte
	 * order, and so this method takes an argument specifying that.
	 *
	 * @param PelByteOrder the desired byte order of the TIFF data.
	 * This should be one of {@link PelConvert::LITTLE_ENDIAN} or {@link
	 * PelConvert::BIG_ENDIAN}.
	 *
	 * @return string the bytes representing this object.
	 */
	function getBytes($order = PelConvert::LITTLE_ENDIAN) {
		if ($order == PelConvert::LITTLE_ENDIAN)
		$bytes = 'II';
		else
		$bytes = 'MM';

		/* TIFF magic number --- fixed value. */
		$bytes .= PelConvert::shortToBytes(self::TIFF_HEADER, $order);

		if ($this->ifd != null) {
			/* IFD 0 offset.  We will always start IDF 0 at an offset of 8
			 * bytes (2 bytes for byte order, another 2 bytes for the TIFF
			 * header, and 4 bytes for the IFD 0 offset make 8 bytes
			 * together).
			 */
			$bytes .= PelConvert::longToBytes(8, $order);

			/* The argument specifies the offset of this IFD.  The IFD will
			 * use this to calculate offsets from the entries to their data,
			 * all those offsets are absolute offsets counted from the
			 * beginning of the data. */
			$bytes .= $this->ifd->getBytes(8, $order);
		} else {
			$bytes .= PelConvert::longToBytes(0, $order);
		}

		return $bytes;
	}


	/**
	 * Return a string representation of this object.
	 *
	 * @return string a string describing this object.  This is mostly useful
	 * for debugging.
	 */
	function __toString() {
		$str = Pel::fmt("Dumping TIFF data...\n");
		if ($this->ifd != null)
		$str .= $this->ifd->__toString();

		return $str;
	}


	/**
	 * Check if data is valid TIFF data.
	 *
	 * This will read just enough data from the data window to determine
	 * if the data could be a valid TIFF data.  This means that the
	 * check is more like a heuristic than a rigorous check.
	 *
	 * @param PelDataWindow the bytes that will be examined.
	 *
	 * @return boolean true if the data looks like valid TIFF data,
	 * false otherwise.
	 *
	 * @see PelJpeg::isValid()
	 */
	static function isValid(PelDataWindow $d) {
		/* First check that we have enough data. */
		if ($d->getSize() < 8)
		return false;

		/* Byte order */
		if ($d->strcmp(0, 'II')) {
			$d->setByteOrder(PelConvert::LITTLE_ENDIAN);
		} elseif ($d->strcmp(0, 'MM')) {
			Pel::debug('Found Motorola byte order');
			$d->setByteOrder(PelConvert::BIG_ENDIAN);
		} else {
			return false;
		}

		/* Verify the TIFF header */
		return $d->getShort(2) == self::TIFF_HEADER;
	}

}