<?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$ */


/**
 * Classes used to manipulate rational numbers.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @version $Revision$
 * @date $Date$
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public
 * License (GPL)
 * @package PEL
 */

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


/**
 * Class for holding unsigned rational numbers.
 *
 * This class can hold rational numbers, consisting of a numerator and
 * denominator both of which are of type unsigned long.  Each rational
 * is represented by an array with just two entries: the numerator and
 * the denominator, in that order.
 *
 * The class can hold either just a single rational or an array of
 * rationals.  The class will be used to manipulate any of the Exif
 * tags which can have format {@link PelFormat::RATIONAL} like in this
 * example:
 *
 * <code>
 * $resolution = $ifd->getEntry(PelTag::X_RESOLUTION);
 * $resolution->setValue(array(1, 300));
 * </code>
 *
 * Here the x-resolution is adjusted to 1/300, which will be 300 DPI,
 * unless the {@link PelTag::RESOLUTION_UNIT resolution unit} is set
 * to something different than 2 which means inches.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @package PEL
 */
class PelEntryRational extends PelEntryLong {

  /**
   * Make a new entry that can hold an unsigned rational.
   *
   * @param PelTag the tag which this entry represents.  This should
   * be one of the constants defined in {@link PelTag}, e.g., {@link
   * PelTag::X_RESOLUTION}, or any other tag which can have format
   * {@link PelFormat::RATIONAL}.
   *
   * @param array $value... the rational(s) that this entry will
   * represent.  The arguments passed must obey the same rules as the
   * argument to {@link setValue}, namely that each argument should be
   * an array with two entries, both of which must be within range of
   * an unsigned long (32 bit), that is between 0 and 4294967295
   * (inclusive).  If not, then a {@link PelOverflowException} will be
   * thrown.
   */
  function __construct($tag /* $value... */) {
    $this->tag       = $tag;
    $this->format    = PelFormat::RATIONAL;
    $this->dimension = 2;
    $this->min       = 0;
    $this->max       = 4294967295;

    $value = func_get_args();
    array_shift($value);
    $this->setValueArray($value);
  }


  /**
   * Format a rational number.
   *
   * The rational will be returned as a string with a slash '/'
   * between the numerator and denominator.
   *
   * @param array the rational which will be formatted.
   *
   * @param boolean not used.
   *
   * @return string the rational formatted as a string suitable for
   * display.
   */
  function formatNumber($number, $brief = false) {
    return $number[0] . '/' . $number[1];
  }


  /**
   * Get the value of an entry as text.
   *
   * The value will be returned in a format suitable for presentation,
   * e.g., rationals will be returned as 'x/y', ASCII strings will be
   * returned as themselves etc.
   *
   * @param boolean some values can be returned in a long or more
   * brief form, and this parameter controls that.
   *
   * @return string the value as text.
   */
  function getText($brief = false) {
    if (isset($this->value[0]))
      $v = $this->value[0];

    switch ($this->tag) {
    case PelTag::FNUMBER:
      //CC (e->components, 1, v);
      return Pel::fmt('f/%.01f', $v[0]/$v[1]);

    case PelTag::APERTURE_VALUE:
      //CC (e->components, 1, v);
      //if (!v_rat.denominator) return (NULL);
      return Pel::fmt('f/%.01f', pow(2, $v[0]/$v[1]/2));

    case PelTag::FOCAL_LENGTH:
      //CC (e->components, 1, v);
      //if (!v_rat.denominator) return (NULL);
      return Pel::fmt('%.1f mm', $v[0]/$v[1]);

    case PelTag::SUBJECT_DISTANCE:
      //CC (e->components, 1, v);
      //if (!v_rat.denominator) return (NULL);
      return Pel::fmt('%.1f m', $v[0]/$v[1]);

    case PelTag::EXPOSURE_TIME:
      //CC (e->components, 1, v);
      //if (!v_rat.denominator) return (NULL);
      if ($v[0]/$v[1] < 1)
        return Pel::fmt('1/%d sec.', $v[1]/$v[0]);
      else
        return Pel::fmt('%d sec.', $v[0]/$v[1]);

    case PelTag::GPS_LATITUDE:
    case PelTag::GPS_LONGITUDE:
      $degrees = $this->value[0][0]/$this->value[0][1];
      $minutes = $this->value[1][0]/$this->value[1][1];
      $seconds = $this->value[2][0]/$this->value[2][1];

      return sprintf('%s° %s\' %s" (%.2f°)',
                     $degrees, $minutes, $seconds,
                     $degrees + $minutes/60 + $seconds/3600);

    default:
      return parent::getText($brief);
    }
  }
}


/**
 * Class for holding signed rational numbers.
 *
 * This class can hold rational numbers, consisting of a numerator and
 * denominator both of which are of type unsigned long.  Each rational
 * is represented by an array with just two entries: the numerator and
 * the denominator, in that order.
 *
 * The class can hold either just a single rational or an array of
 * rationals.  The class will be used to manipulate any of the Exif
 * tags which can have format {@link PelFormat::SRATIONAL}.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @package PEL
 */
class PelEntrySRational extends PelEntrySLong {

  /**
   * Make a new entry that can hold a signed rational.
   *
   * @param PelTag the tag which this entry represents.  This should
   * be one of the constants defined in {@link PelTag}, e.g., {@link
   * PelTag::SHUTTER_SPEED_VALUE}, or any other tag which can have
   * format {@link PelFormat::SRATIONAL}.
   *
   * @param array $value... the rational(s) that this entry will
   * represent.  The arguments passed must obey the same rules as the
   * argument to {@link setValue}, namely that each argument should be
   * an array with two entries, both of which must be within range of
   * a signed long (32 bit), that is between -2147483648 and
   * 2147483647 (inclusive).  If not, then a {@link
   * PelOverflowException} will be thrown.
   */
  function __construct($tag /* $value... */) {
    $this->tag       = $tag;
    $this->format    = PelFormat::SRATIONAL;
    $this->dimension = 2;
    $this->min       = -2147483648;
    $this->max       = 2147483647;

    $value = func_get_args();
    array_shift($value);
    $this->setValueArray($value);
  }


  /**
   * Format a rational number.
   *
   * The rational will be returned as a string with a slash '/'
   * between the numerator and denominator.  Care is taken to display
   * '-1/2' instead of the ugly but mathematically equivalent '1/-2'.
   *
   * @param array the rational which will be formatted.
   *
   * @param boolean not used.
   *
   * @return string the rational formatted as a string suitable for
   * display.
   */
  function formatNumber($number, $brief = false) {
    if ($number[1] < 0)
      /* Turn output like 1/-2 into -1/2. */
      return (-$number[0]) . '/' . (-$number[1]);
    else
      return $number[0] . '/' . $number[1];
  }


  /**
   * Get the value of an entry as text.
   *
   * The value will be returned in a format suitable for presentation,
   * e.g., rationals will be returned as 'x/y', ASCII strings will be
   * returned as themselves etc.
   *
   * @param boolean some values can be returned in a long or more
   * brief form, and this parameter controls that.
   *
   * @return string the value as text.
   */
  function getText($brief = false) {
    if (isset($this->value[0]))
      $v = $this->value[0];

    switch ($this->tag) {
    case PelTag::SHUTTER_SPEED_VALUE:
      //CC (e->components, 1, v);
      //if (!v_srat.denominator) return (NULL);
      return Pel::fmt('%.0f/%.0f sec. (APEX: %d)',
                      $v[0], $v[1], pow(sqrt(2), $v[0]/$v[1]));

    case PelTag::BRIGHTNESS_VALUE:
      //CC (e->components, 1, v);
      //
      // TODO: figure out the APEX thing, or remove this so that it is
      // handled by the default clause at the bottom.
      return sprintf('%d/%d', $v[0], $v[1]);
      //FIXME: How do I calculate the APEX value?

    case PelTag::EXPOSURE_BIAS_VALUE:
      //CC (e->components, 1, v);
      //if (!v_srat.denominator) return (NULL);
      return sprintf('%s%.01f', $v[0]*$v[1] > 0 ? '+' : '', $v[0]/$v[1]);

    default:
      return parent::getText($brief);
    }
  }

}

?>