Source for file UnitConverter.php

Documentation is available at UnitConverter.php

  1. <?php
  2.  
  3. /**
  4.  * Class for converting between different unit-lengths as specified by
  5.  * CSS.
  6.  */
  7. {
  8.     
  9.     const ENGLISH 1;
  10.     const METRIC 2;
  11.     const DIGITAL 3;
  12.     
  13.     /**
  14.      * Units information array. Units are grouped into measuring systems
  15.      * (English, Metric), and are assigned an integer representing
  16.      * the conversion factor between that unit and the smallest unit in
  17.      * the system. Numeric indexes are actually magical constants that
  18.      * encode conversion data from one system to the next, with a O(n^2)
  19.      * constraint on memory (this is generally not a problem, since
  20.      * the number of measuring systems is small.)
  21.      */
  22.     protected static $units array(
  23.         self::ENGLISH => array(
  24.             'px' => 3// This is as per CSS 2.1 and Firefox. Your mileage may vary
  25.             'pt' => 4,
  26.             'pc' => 48,
  27.             'in' => 288,
  28.             self::METRIC => array('pt''0.352777778''mm'),
  29.         ),
  30.         self::METRIC => array(
  31.             'mm' => 1,
  32.             'cm' => 10,
  33.             self::ENGLISH => array('mm''2.83464567''pt'),
  34.         ),
  35.     );
  36.     
  37.     /**
  38.      * Minimum bcmath precision for output.
  39.      */
  40.     protected $outputPrecision;
  41.     
  42.     /**
  43.      * Bcmath precision for internal calculations.
  44.      */
  45.     protected $internalPrecision;
  46.     
  47.     /**
  48.      * Whether or not BCMath is available
  49.      */
  50.     private $bcmath;
  51.     
  52.     public function __construct($output_precision 4$internal_precision 10$force_no_bcmath false{
  53.         $this->outputPrecision = $output_precision;
  54.         $this->internalPrecision = $internal_precision;
  55.         $this->bcmath !$force_no_bcmath && function_exists('bcmul');
  56.     }
  57.     
  58.     /**
  59.      * Converts a length object of one unit into another unit.
  60.      * @param HTMLPurifier_Length $length 
  61.      *       Instance of HTMLPurifier_Length to convert. You must validate()
  62.      *       it before passing it here!
  63.      * @param string $to_unit 
  64.      *       Unit to convert to.
  65.      * @note
  66.      *       About precision: This conversion function pays very special
  67.      *       attention to the incoming precision of values and attempts
  68.      *       to maintain a number of significant figure. Results are
  69.      *       fairly accurate up to nine digits. Some caveats:
  70.      *           - If a number is zero-padded as a result of this significant
  71.      *             figure tracking, the zeroes will be eliminated.
  72.      *           - If a number contains less than four sigfigs ($outputPrecision)
  73.      *             and this causes some decimals to be excluded, those
  74.      *             decimals will be added on.
  75.      */
  76.     public function convert($length$to_unit{
  77.         
  78.         if (!$length->isValid()) return false;
  79.         
  80.         $n    $length->getN();
  81.         $unit $length->getUnit();
  82.         
  83.         if ($n === '0' || $unit === false{
  84.             return new HTMLPurifier_Length('0'false);
  85.         }
  86.         
  87.         $state $dest_state false;
  88.         foreach (self::$units as $k => $x{
  89.             if (isset($x[$unit])) $state $k;
  90.             if (isset($x[$to_unit])) $dest_state $k;
  91.         }
  92.         if (!$state || !$dest_statereturn false;
  93.         
  94.         // Some calculations about the initial precision of the number;
  95.         // this will be useful when we need to do final rounding.
  96.         $sigfigs $this->getSigFigs($n);
  97.         if ($sigfigs $this->outputPrecision$sigfigs $this->outputPrecision;
  98.         
  99.         // BCMath's internal precision deals only with decimals. Use
  100.         // our default if the initial number has no decimals, or increase
  101.         // it by how ever many decimals, thus, the number of guard digits
  102.         // will always be greater than or equal to internalPrecision.
  103.         $log = (int) floor(log(abs($n)10));
  104.         $cp ($log 0$this->internalPrecision - $log $this->internalPrecision// internal precision
  105.         
  106.         for ($i 0$i 2$i++{
  107.             
  108.             // Determine what unit IN THIS SYSTEM we need to convert to
  109.             if ($dest_state === $state{
  110.                 // Simple conversion
  111.                 $dest_unit $to_unit;
  112.             else {
  113.                 // Convert to the smallest unit, pending a system shift
  114.                 $dest_unit self::$units[$state][$dest_state][0];
  115.             }
  116.             
  117.             // Do the conversion if necessary
  118.             if ($dest_unit !== $unit{
  119.                 $factor $this->div(self::$units[$state][$unit]self::$units[$state][$dest_unit]$cp);
  120.                 $n $this->mul($n$factor$cp);
  121.                 $unit $dest_unit;
  122.             }
  123.             
  124.             // Output was zero, so bail out early. Shouldn't ever happen.
  125.             if ($n === ''{
  126.                 $n '0';
  127.                 $unit $to_unit;
  128.                 break;
  129.             }
  130.             
  131.             // It was a simple conversion, so bail out
  132.             if ($dest_state === $state{
  133.                 break;
  134.             }
  135.             
  136.             if ($i !== 0{
  137.                 // Conversion failed! Apparently, the system we forwarded
  138.                 // to didn't have this unit. This should never happen!
  139.                 return false;
  140.             }
  141.             
  142.             // Pre-condition: $i == 0
  143.             
  144.             // Perform conversion to next system of units
  145.             $n $this->mul($nself::$units[$state][$dest_state][1]$cp);
  146.             $unit self::$units[$state][$dest_state][2];
  147.             $state $dest_state;
  148.             
  149.             // One more loop around to convert the unit in the new system.
  150.             
  151.         }
  152.         
  153.         // Post-condition: $unit == $to_unit
  154.         if ($unit !== $to_unitreturn false;
  155.         
  156.         // Useful for debugging:
  157.         //echo "<pre>n";
  158.         //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";
  159.         
  160.         $n $this->round($n$sigfigs);
  161.         if (strpos($n'.'!== false$n rtrim($n'0');
  162.         $n rtrim($n'.');
  163.         
  164.         return new HTMLPurifier_Length($n$unit);
  165.     }
  166.     
  167.     /**
  168.      * Returns the number of significant figures in a string number.
  169.      * @param string $n Decimal number
  170.      * @return int number of sigfigs
  171.      */
  172.     public function getSigFigs($n{
  173.         $n ltrim($n'0+-');
  174.         $dp strpos($n'.')// decimal position
  175.         if ($dp === false{
  176.             $sigfigs strlen(rtrim($n'0'));
  177.         else {
  178.             $sigfigs strlen(ltrim($n'0.'))// eliminate extra decimal character
  179.             if ($dp !== 0$sigfigs--;
  180.         }
  181.         return $sigfigs;
  182.     }
  183.     
  184.     /**
  185.      * Adds two numbers, using arbitrary precision when available.
  186.      */
  187.     private function add($s1$s2$scale{
  188.         if ($this->bcmathreturn bcadd($s1$s2$scale);
  189.         else return $this->scale($s1 $s2$scale);
  190.     }
  191.     
  192.     /**
  193.      * Multiples two numbers, using arbitrary precision when available.
  194.      */
  195.     private function mul($s1$s2$scale{
  196.         if ($this->bcmathreturn bcmul($s1$s2$scale);
  197.         else return $this->scale($s1 $s2$scale);
  198.     }
  199.     
  200.     /**
  201.      * Divides two numbers, using arbitrary precision when available.
  202.      */
  203.     private function div($s1$s2$scale{
  204.         if ($this->bcmathreturn bcdiv($s1$s2$scale);
  205.         else return $this->scale($s1 $s2$scale);
  206.     }
  207.     
  208.     /**
  209.      * Rounds a number according to the number of sigfigs it should have,
  210.      * using arbitrary precision when available.
  211.      */
  212.     private function round($n$sigfigs{
  213.         $new_log = (int) floor(log(abs($n)10))// Number of digits left of decimal - 1
  214.         $rp $sigfigs $new_log 1// Number of decimal places needed
  215.         $neg $n '-' ''// Negative sign
  216.         if ($this->bcmath{
  217.             if ($rp >= 0{
  218.                 $n bcadd($n$neg '0.' .  str_repeat('0'$rp'5'$rp 1);
  219.                 $n bcdiv($n'1'$rp);
  220.             else {
  221.                 // This algorithm partially depends on the standardized
  222.                 // form of numbers that comes out of bcmath.
  223.                 $n bcadd($n$neg '5' str_repeat('0'$new_log $sigfigs)0);
  224.                 $n substr($n0$sigfigs strlen($neg)) str_repeat('0'$new_log $sigfigs 1);
  225.             }
  226.             return $n;
  227.         else {
  228.             return $this->scale(round($n$sigfigs $new_log 1)$rp 1);
  229.         }
  230.     }
  231.     
  232.     /**
  233.      * Scales a float to $scale digits right of decimal point, like BCMath.
  234.      */
  235.     private function scale($r$scale{
  236.         return sprintf('%.' $scale 'f'(float) $r);
  237.     }
  238.     
  239. }

Documentation generated on Thu, 19 Jun 2008 18:50:27 -0400 by phpDocumentor 1.4.2