<?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  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: PelDataWindow.php 13759 2012-07-04 04:31:41Z giangnd $ */


/**
 * A container for bytes with a limited window of accessible bytes.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @version $Revision: 387 $
 * @date $Date: 2005-10-05 13:02:52 +0200 (Wed, 05 Oct 2005) $
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL)
 * @package PEL
 */

/**#@+ Required class definitions. */
require_once('PelException.php');
require_once('PelConvert.php');
/**#@-*/


/**
 * An exception thrown when an invalid offset is encountered.
 *
 * @package PEL
 * @subpackage Exception
 */
class PelDataWindowOffsetException extends PelException {}

/**
 * An exception thrown when an invalid window is encountered.
 *
 * @package PEL
 * @subpackage Exception
 */
class PelDataWindowWindowException extends PelException {}

/**
 * The window.
 *
 * @package PEL
 */
class PelDataWindow {

	/**
	 * The data held by this window.
	 *
	 * The string can contain any kind of data, including binary data.
	 *
	 * @var string
	 */
	private $data = '';

	/**
	 * The byte order currently in use.
	 *
	 * This will be the byte order used when data is read using the for
	 * example the {@link getShort} function.  It must be one of {@link
	 * PelConvert::LITTLE_ENDIAN} and {@link PelConvert::BIG_ENDIAN}.
	 *
	 * @var PelByteOrder
	 * @see setByteOrder, getByteOrder
	 */
	private $order;

	/**
	 * The start of the current window.
	 *
	 * All offsets used for access into the data will count from this
	 * offset, effectively limiting access to a window starting at this
	 * byte.
	 *
	 * @var int
	 * @see setWindowStart
	 */
	private $start  = 0;

	/**
	 * The size of the current window.
	 *
	 * All offsets used for access into the data will be limited by this
	 * variable.  A valid offset must be strictly less than this
	 * variable.
	 *
	 * @var int
	 * @see setWindowSize
	 */
	private $size   = 0;


	/**
	 * Construct a new data window with the data supplied.
	 *
	 * @param string the data that this window will contain.  The data
	 * will be copied into the new data window.
	 *
	 * @param boolean the initial byte order of the window.  This must
	 * be either {@link PelConvert::LITTLE_ENDIAN} or {@link
	 * PelConvert::BIG_ENDIAN}.  This will be used when integers are
	 * read from the data, and it can be changed later with {@link
	 * setByteOrder()}.
	 */
	function __construct($d = '', $e = PelConvert::LITTLE_ENDIAN) {
		$this->data  = $d;
		$this->order = $e;
		$this->size  = strlen($d);
	}


	/**
	 * Get the size of the data window.
	 *
	 * @return int the number of bytes covered by the window.  The
	 * allowed offsets go from 0 up to this number minus one.
	 *
	 * @see getBytes()
	 */
	function getSize() {
		return $this->size;
	}


	/**
	 * Change the byte order of the data.
	 *
	 * @param PelByteOrder the new byte order.  This must be either
	 * {@link PelConvert::LITTLE_ENDIAN} or {@link
	 * PelConvert::BIG_ENDIAN}.
	 */
	function setByteOrder($o) {
		$this->order = $o;
	}


	/**
	 * Get the currently used byte order.
	 *
	 * @return PelByteOrder this will be either {@link
	 * PelConvert::LITTLE_ENDIAN} or {@link PelConvert::BIG_ENDIAN}.
	 */
	function getByteOrder() {
		return $this->order;
	}


	/* Move the start of the window forward.
	 *
	 * @param int the new start of the window.  All new offsets will be
	 * calculated from this new start offset, and the size of the window
	 * will shrink to keep the end of the window in place.
	 */
	function setWindowStart($start) {
		if ($start < 0 || $start > $this->size)
		throw new PelDataWindowWindowException('Window [%d, %d] does ' .
                                             'not fit in window [0, %d]',
		$start, $this->size, $this->size);

		$this->start += $start;
		$this->size  -= $start;
	}


	/**
	 * Adjust the size of the window.
	 *
	 * The size can only be made smaller.
	 *
	 * @param int the desired size of the window.  If the argument is
	 * negative, the window will be shrunk by the argument.
	 */
	function setWindowSize($size) {
		if ($size < 0)
		$size += $this->size;

		if ($size < 0 || $size > $this->size)
		throw new PelDataWindowWindowException('Window [0, %d] ' .
                                             'does not fit in window [0, %d]',
		$size, $this->size);
		$this->size = $size;
	}


	/**
	 * Make a new data window with the same data as the this window.
	 *
	 * @param mixed if an integer is supplied, then it will be the start
	 * of the window in the clone.  If left unspecified, then the clone
	 * will inherit the start from this object.
	 *
	 * @param mixed if an integer is supplied, then it will be the size
	 * of the window in the clone.  If left unspecified, then the clone
	 * will inherit the size from this object.
	 *
	 * @return PelDataWindow a new window that operates on the same data
	 * as this window, but (optionally) with a smaller window size.
	 */
	function getClone($start = false, $size = false) {
		$c = clone $this;

		if (is_int($start))
		$c->setWindowStart($start);

		if (is_int($size))
		$c->setWindowSize($size);

		return $c;
	}


	/**
	 * Validate an offset against the current window.
	 *
	 * @param int the offset to be validated.  If the offset is negative
	 * or if it is greater than or equal to the current window size,
	 * then a {@link PelDataWindowOffsetException} is thrown.
	 *
	 * @return void if the offset is valid nothing is returned, if it is
	 * invalid a new {@link PelDataWindowOffsetException} is thrown.
	 */
	private function validateOffset($o) {
		if ($o < 0 || $o >= $this->size)
		throw new PelDataWindowOffsetException('Offset %d not within [%d, %d]',
		$o, 0, $this->size-1);
	}


	/**
	 * Return some or all bytes visible in the window.
	 *
	 * This method works just like the standard {@link substr()}
	 * function in PHP with the exception that it works within the
	 * window of accessible bytes and does strict range checking.
	 *
	 * @param int the offset to the first byte returned.  If a negative
	 * number is given, then the counting will be from the end of the
	 * window.  Invalid offsets will result in a {@link
	 * PelDataWindowOffsetException} being thrown.
	 *
	 * @param int the size of the sub-window.  If a negative number is
	 * given, then that many bytes will be omitted from the result.
	 *
	 * @return string a subset of the bytes in the window.  This will
	 * always return no more than {@link getSize()} bytes.
	 */
	function getBytes($start = false, $size = false) {
		if (is_int($start)) {
			if ($start < 0)
			$start += $this->size;

			$this->validateOffset($start);
		} else {
			$start = 0;
		}

		if (is_int($size)) {
			if ($size <= 0)
			$size += $this->size - $start;

			$this->validateOffset($start+$size);
		} else {
			$size = $this->size - $start;
		}

		return substr($this->data, $this->start + $start, $size);
	}


	/**
	 * Return an unsigned byte from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first byte in the current allowed window.  The last
	 * valid offset is equal to {@link getSize()}-1.  Invalid offsets
	 * will result in a {@link PelDataWindowOffsetException} being
	 * thrown.
	 *
	 * @return  int  the unsigned byte found at offset.
	 */
	function getByte($o = 0) {
		/* Validate the offset --- this throws an exception if offset is
		 * out of range. */
		$this->validateOffset($o);

		/* Translate the offset into an offset into the data. */
		$o += $this->start;

		/* Return an unsigned byte. */
		return PelConvert::bytesToByte($this->data, $o);
	}


	/**
	 * Return a signed byte from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first byte in the current allowed window.  The last
	 * valid offset is equal to {@link getSize()}-1.  Invalid offsets
	 * will result in a {@link PelDataWindowOffsetException} being
	 * thrown.
	 *
	 * @return  int  the signed byte found at offset.
	 */
	function getSByte($o = 0) {
		/* Validate the offset --- this throws an exception if offset is
		 * out of range. */
		$this->validateOffset($o);

		/* Translate the offset into an offset into the data. */
		$o += $this->start;

		/* Return a signed byte. */
		return PelConvert::bytesToSByte($this->data, $o);
	}


	/**
	 * Return an unsigned short read from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first short available in the current allowed window.
	 * The last valid offset is equal to {@link getSize()}-2.  Invalid
	 * offsets will result in a {@link PelDataWindowOffsetException}
	 * being thrown.
	 *
	 * @return  int  the unsigned short found at offset.
	 */
	function getShort($o = 0) {
		/* Validate the offset+1 to see if we can safely get two bytes ---
		 * this throws an exception if offset is out of range. */
		$this->validateOffset($o);
		$this->validateOffset($o+1);

		/* Translate the offset into an offset into the data. */
		$o += $this->start;

		/* Return an unsigned short. */
		return PelConvert::bytesToShort($this->data, $o, $this->order);
	}


	/**
	 * Return a signed short read from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first short available in the current allowed window.
	 * The last valid offset is equal to {@link getSize()}-2.  Invalid
	 * offsets will result in a {@link PelDataWindowOffsetException}
	 * being thrown.
	 *
	 * @return  int  the signed short found at offset.
	 */
	function getSShort($o = 0) {
		/* Validate the offset+1 to see if we can safely get two bytes ---
		 * this throws an exception if offset is out of range. */
		$this->validateOffset($o);
		$this->validateOffset($o+1);

		/* Translate the offset into an offset into the data. */
		$o += $this->start;

		/* Return a signed short. */
		return PelConvert::bytesToSShort($this->data, $o, $this->order);
	}


	/**
	 * Return an unsigned long read from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first long available in the current allowed window.
	 * The last valid offset is equal to {@link getSize()}-4.  Invalid
	 * offsets will result in a {@link PelDataWindowOffsetException}
	 * being thrown.
	 *
	 * @return  int  the unsigned long found at offset.
	 */
	function getLong($o = 0) {
		/* Validate the offset+3 to see if we can safely get four bytes
		 * --- this throws an exception if offset is out of range. */
		$this->validateOffset($o);
		$this->validateOffset($o+3);
		 
		/* Translate the offset into an offset into the data. */
		$o += $this->start;

		/* Return an unsigned long. */
		return PelConvert::bytesToLong($this->data, $o, $this->order);
	}


	/**
	 * Return a signed long read from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first long available in the current allowed window.
	 * The last valid offset is equal to {@link getSize()}-4.  Invalid
	 * offsets will result in a {@link PelDataWindowOffsetException}
	 * being thrown.
	 *
	 * @return  int  the signed long found at offset.
	 */
	function getSLong($o = 0) {
		/* Validate the offset+3 to see if we can safely get four bytes
		 * --- this throws an exception if offset is out of range. */
		$this->validateOffset($o);
		$this->validateOffset($o+3);
		 
		/* Translate the offset into an offset into the data. */
		$o += $this->start;

		/* Return a signed long. */
		return PelConvert::bytesToSLong($this->data, $o, $this->order);
	}


	/**
	 * Return an unsigned rational read from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first rational available in the current allowed
	 * window.  The last valid offset is equal to {@link getSize()}-8.
	 * Invalid offsets will result in a {@link
	 * PelDataWindowOffsetException} being thrown.
	 *
	 * @return array the unsigned rational found at offset.  A rational
	 * number is represented as an array of two numbers: the enumerator
	 * and denominator.  Both of these numbers will be unsigned longs.
	 */
	function getRational($o = 0) {
		return array($this->getLong($o), $this->getLong($o+4));
	}


	/**
	 * Return a signed rational read from the data.
	 *
	 * @param int the offset into the data.  An offset of zero will
	 * return the first rational available in the current allowed
	 * window.  The last valid offset is equal to {@link getSize()}-8.
	 * Invalid offsets will result in a {@link
	 * PelDataWindowOffsetException} being thrown.
	 *
	 * @return array the signed rational found at offset.  A rational
	 * number is represented as an array of two numbers: the enumerator
	 * and denominator.  Both of these numbers will be signed longs.
	 */
	function getSRational($o = 0) {
		return array($this->getSLong($o), $this->getSLong($o+4));
	}


	/**
	 * String comparison on substrings.
	 *
	 * @param int the offset into the data.  An offset of zero will make
	 * the comparison start with the very first byte available in the
	 * window.  The last valid offset is equal to {@link getSize()}
	 * minus the length of the string.  If the string is too long, then
	 * a {@link PelDataWindowOffsetException} will be thrown.
	 *
	 * @param string the string to compare with.
	 *
	 * @return boolean true if the string given matches the data in the
	 * window, at the specified offset, false otherwise.  The comparison
	 * will stop as soon as a mismatch if found.
	 */
	function strcmp($o, $str) {
		/* Validate the offset of the final character we might have to
		 * check. */
		$s = strlen($str);
		$this->validateOffset($o);
		$this->validateOffset($o + $s - 1);

		/* Translate the offset into an offset into the data. */
		$o += $this->start;

		/* Check each character, return as soon as the answer is known. */
		for ($i = 0; $i < $s; $i++) {
			if ($this->data{$o + $i} != $str{$i})
			return false;
		}

		/* All characters matches each other, return true. */
		return true;
	}


	/**
	 * Return a string representation of the data window.
	 *
	 * @return string a description of the window with information about
	 * the number of bytes accessible, the total number of bytes, and
	 * the window start and stop.
	 */
	function __toString() {
		return Pel::fmt('DataWindow: %d bytes in [%d, %d] of %d bytes',
		$this->size,
		$this->start, $this->start + $this->size,
		strlen($this->data));
	}

}

?>