You are here

class SassNumber in Sassy 7.3

Same name and namespace in other branches
  1. 7 phamlp/sass/script/literals/SassNumber.php \SassNumber

SassNumber class. Provides operations and type testing for Sass numbers. Units are of the passed value are converted the those of the class value if it has units. e.g. 2cm + 20mm = 4cm while 2 + 20mm = 22mm. @package PHamlP @subpackage Sass.script.literals

Hierarchy

Expanded class hierarchy of SassNumber

12 string references to 'SassNumber'
SassScriptFunctions::abs in phpsass/script/SassScriptFunctions.php
Finds the absolute value of a number. For example: abs(10px) => 10px abs(-10px) => 10px
SassScriptFunctions::adjust in phpsass/script/SassScriptFunctions.php
Adjusts the colour
SassScriptFunctions::adjust_hue in phpsass/script/SassScriptFunctions.php
Changes the hue of a colour while retaining the lightness and saturation.
SassScriptFunctions::ceil in phpsass/script/SassScriptFunctions.php
Rounds a number up to the nearest whole number. For example: ceil(10.4px) => 11px ceil(10.6px) => 11px
SassScriptFunctions::comparable in phpsass/script/SassScriptFunctions.php
Returns true if two numbers are similar enough to be added, subtracted, or compared.

... See full list

File

phpsass/script/literals/SassNumber.php, line 22

View source
class SassNumber extends SassLiteral {

  /**
   * Regx for matching and extracting numbers
   */
  const MATCH = '/^((?:-)?(?:\\d*\\.)?\\d+)(([a-z%]+)(\\s*[\\*\\/]\\s*[a-z%]+)*)?/i';
  const VALUE = 1;
  const UNITS = 2;

  /**
   * The number of decimal digits to round to.
   * If the units are pixels the result is always
   * rounded down to the nearest integer.
   */
  const PRECISION = 4;

  /**
   * @var array Conversion factors for units using inches as the base unit
   * (only because pt and pc are expressed as fraction of an inch, so makes the
   * numbers easy to understand).
   * Conversions are based on the following
   * in: inches — 1 inch = 2.54 centimeters
   * cm: centimeters
   * mm: millimeters
   * pc: picas — 1 pica = 12 points
   * pt: points — 1 point = 1/72nd of an inch
   */
  private static $unitConversion = array(
    'in' => 1,
    'cm' => 2.54,
    'mm' => 25.4,
    'pc' => 6,
    'pt' => 72,
  );

  /**
   * @var array numerator units of this number
   */
  private $numeratorUnits = array();

  /**
   * @var array denominator units of this number
   */
  private $denominatorUnits = array();

  /**
   * @var boolean whether this number is in an expression or a literal number
   * Used to determine whether division should take place
   */
  public $inExpression = true;

  /**
   * class constructor.
   * Sets the value and units of the number.
   * @param string number
   * @return SassNumber
   */
  public function __construct($value) {
    preg_match(self::MATCH, $value, $matches);
    $this->value = $matches[self::VALUE];
    if (!empty($matches[self::UNITS])) {
      $units = explode('/', $matches[self::UNITS]);
      $numeratorUnits = $denominatorUnits = array();
      foreach (explode('*', $units[0]) as $unit) {
        $numeratorUnits[] = trim($unit);
      }
      if (isset($units[1])) {
        foreach (explode('*', $units[1]) as $unit) {
          $denominatorUnits[] = trim($unit);
        }
      }
      $units = $this
        ->removeCommonUnits($numeratorUnits, $denominatorUnits);
      $this->numeratorUnits = $units[0];
      $this->denominatorUnits = $units[1];
    }
  }

  /**
   * Adds the value of other to the value of this
   * @param mixed SassNumber|SassColour: value to add
   * @return mixed SassNumber if other is a SassNumber or
   * SassColour if it is a SassColour
   */
  public function op_plus($other) {
    if ($other instanceof SassColour) {
      return $other
        ->op_plus($this);
    }
    elseif (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    else {
      $other = $this
        ->convert($other);
      return new SassNumber($this->value + $other->value . $this->units);
    }
  }

  /**
   * Unary + operator
   * @return SassNumber the value of this number
   */
  public function op_unary_plus() {
    return $this;
  }

  /**
   * Subtracts the value of other from this value
   * @param mixed SassNumber|SassColour: value to subtract
   * @return mixed SassNumber if other is a SassNumber or
   * SassColour if it is a SassColour
   */
  public function op_minus($other) {
    if ($other instanceof SassColour) {
      return $other
        ->op_minus($this);
    }
    elseif (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    else {
      $other = $this
        ->convert($other);
      return new SassNumber($this->value - $other->value . $this->units);
    }
  }

  /**
   * Unary - operator
   * @return SassNumber the negative value of this number
   */
  public function op_unary_minus() {
    return new SassNumber($this->value * -1 . $this->units);
  }
  public function op_unary_concat() {
    return $this;
  }

  /**
   * Multiplies this value by the value of other
   * @param mixed SassNumber|SassColour: value to multiply by
   * @return mixed SassNumber if other is a SassNumber or
   * SassColour if it is a SassColour
   */
  public function op_times($other) {
    if ($other instanceof SassColour) {
      return $other
        ->op_times($this);
    }
    elseif (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    else {
      return new SassNumber($this->value * $other->value . $this
        ->unitString(array_merge($this->numeratorUnits, $other->numeratorUnits), array_merge($this->denominatorUnits, $other->denominatorUnits)));
    }
  }

  /**
   * Divides this value by the value of other
   * @param mixed SassNumber|SassColour: value to divide by
   * @return mixed SassNumber if other is a SassNumber or
   * SassColour if it is a SassColour
   */
  public function op_div($other) {
    if ($other instanceof SassColour) {
      return $other
        ->op_div($this);
    }
    elseif (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    elseif ($this->inExpression || $other->inExpression) {
      return new SassNumber($this->value / $other->value . $this
        ->unitString(array_merge($this->numeratorUnits, $other->denominatorUnits), array_merge($this->denominatorUnits, $other->numeratorUnits)));
    }
    else {
      return parent::op_div($other);
    }
  }

  /**
   * The SassScript == operation.
   * @return SassBoolean SassBoolean object with the value true if the values
   * of this and other are equal, false if they are not
   */
  public function op_eq($other) {
    if (!$other instanceof SassNumber) {
      return new SassBoolean(false);
    }
    try {
      return new SassBoolean($this->value == $this
        ->convert($other)->value);
    } catch (Exception $e) {
      return new SassBoolean(false);
    }
  }

  /**
   * The SassScript > operation.
   * @param sassLiteral the value to compare to this
   * @return SassBoolean SassBoolean object with the value true if the values
   * of this is greater than the value of other, false if it is not
   */
  public function op_gt($other) {
    if (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    return new SassBoolean($this->value > $this
      ->convert($other)->value);
  }

  /**
   * The SassScript >= operation.
   * @param sassLiteral the value to compare to this
   * @return SassBoolean SassBoolean object with the value true if the values
   * of this is greater than or equal to the value of other, false if it is not
   */
  public function op_gte($other) {
    if (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    return new SassBoolean($this->value >= $this
      ->convert($other)->value);
  }

  /**
   * The SassScript < operation.
   * @param sassLiteral the value to compare to this
   * @return SassBoolean SassBoolean object with the value true if the values
   * of this is less than the value of other, false if it is not
   */
  public function op_lt($other) {
    if (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    return new SassBoolean($this->value < $this
      ->convert($other)->value);
  }

  /**
   * The SassScript <= operation.
   * @param sassLiteral the value to compare to this
   * @return SassBoolean SassBoolean object with the value true if the values
   * of this is less than or equal to the value of other, false if it is not
   */
  public function op_lte($other) {
    if (!$other instanceof SassNumber) {
      throw new SassNumberException('Number must be a number', SassScriptParser::$context->node);
    }
    return new SassBoolean($this->value <= $this
      ->convert($other)->value);
  }

  /**
   * Takes the modulus (remainder) of this value divided by the value of other
   * @param string value to divide by
   * @return mixed SassNumber if other is a SassNumber or
   * SassColour if it is a SassColour
   */
  public function op_modulo($other) {
    if (!$other instanceof SassNumber || !$other
      ->isUnitless()) {
      throw new SassNumberException('Number must be a unitless number', SassScriptParser::$context->node);
    }
    $this->value %= $this
      ->convert($other)->value;
    return $this;
  }

  /**
   * Converts values and units.
   * If this is a unitless numeber it will take the units of other; if not
   * other is coerced to the units of this.
   * @param SassNumber the other number
   * @return SassNumber the other number with its value and units coerced if neccessary
   * @throws SassNumberException if the units are incompatible
   */
  private function convert($other) {
    if ($this
      ->isUnitless()) {
      $this->numeratorUnits = $other->numeratorUnits;
      $this->denominatorUnits = $other->denominatorUnits;
    }
    else {
      $other = $other
        ->coerce($this->numeratorUnits, $this->denominatorUnits);
    }
    return $other;
  }

  /**
   * Returns the value of this number converted to other units.
   * The conversion takes into account the relationship between e.g. mm and cm,
   * as well as between e.g. in and cm.
   *
   * If this number is unitless, it will simply return itself with the given units.
   * @param array $numeratorUnits
   * @param array $denominatorUnits
   * @return SassNumber
   */
  public function coerce($numeratorUnits, $denominatorUnits) {
    return new SassNumber(($this
      ->isUnitless() ? $this->value : $this->value * $this
      ->coercionFactor($this->numeratorUnits, $numeratorUnits) / $this
      ->coercionFactor($this->denominatorUnits, $denominatorUnits)) . join(' * ', $numeratorUnits) . (!empty($denominatorUnits) ? ' / ' . join(' * ', $denominatorUnits) : ''));
  }

  /**
   * Calculates the corecion factor to apply to the value
   * @param array units being converted from
   * @param array units being converted to
   * @return float the coercion factor to apply
   */
  private function coercionFactor($fromUnits, $toUnits) {
    $units = $this
      ->removeCommonUnits($fromUnits, $toUnits);
    $fromUnits = $units[0];
    $toUnits = $units[1];
    if (sizeof($fromUnits) !== sizeof($toUnits) || !$this
      ->areConvertable(array_merge($fromUnits, $toUnits))) {
      throw new SassNumberException("Incompatible units: '" . join(' * ', $fromUnits) . "' and '" . join(' * ', $toUnits) . "'", SassScriptParser::$context->node);
    }
    $coercionFactor = 1;
    foreach ($fromUnits as $i => $from) {
      if (array_key_exists($from) && array_key_exists($from)) {
        $coercionFactor *= self::$unitConversion[$toUnits[$i]] / self::$unitConversion[$from];
      }
      else {
        throw new SassNumberException("Incompatible units: '" . join(' * ', $fromUnits) . "' and '" . join(' * ', $toUnits) . "'", SassScriptParser::$context->node);
      }
    }
    return $coercionFactor;
  }

  /**
   * Returns a value indicating if all the units are capable of being converted
   * @param array units to test
   * @return boolean true if all units can be converted, false if not
   */
  private function areConvertable($units) {
    $convertable = array_keys(self::$unitConversion);
    foreach ($units as $unit) {
      if (!in_array($unit, $convertable)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Removes common units from each set.
   * We don't use array_diff because we want (for eaxmple) mm*mm/mm*cm to
   * end up as mm/cm.
   * @param array first set of units
   * @param array second set of units
   * @return array both sets of units with common units removed
   */
  private function removeCommonUnits($u1, $u2) {
    $_u1 = array();
    while (!empty($u1)) {
      $u = array_shift($u1);
      $i = array_search($u, $u2);
      if ($i !== false) {
        unset($u2[$i]);
      }
      else {
        $_u1[] = $u;
      }
    }
    return array(
      $_u1,
      $u2,
    );
  }

  /**
   * Returns a value indicating if this number is unitless.
   * @return boolean true if this number is unitless, false if not
   */
  public function isUnitless() {
    return empty($this->numeratorUnits) && empty($this->denominatorUnits);
  }

  /**
   * Returns a value indicating if this number has units.
   * @return boolean true if this number has, false if not
   */
  public function hasUnits() {
    return !$this
      ->isUnitless();
  }

  /**
   * Returns a value indicating if this number has units that can be represented
   * in CSS.
   * @return boolean true if this number has units that can be represented in
   * CSS, false if not
   */
  public function hasLegalUnits() {
    return (empty($this->numeratorUnits) || count($this->numeratorUnits) === 1) && empty($this->denominatorUnits);
  }

  /**
   * Returns a string representation of the units.
   * @return string the units
   */
  public function unitString($numeratorUnits, $denominatorUnits) {
    return join(' * ', $numeratorUnits) . (!empty($denominatorUnits) ? ' / ' . join(' * ', $denominatorUnits) : '');
  }

  /**
   * Returns the units of this number.
   * @return string the units of this number
   */
  public function getUnits() {
    return $this
      ->unitString($this->numeratorUnits, $this->denominatorUnits);
  }

  /**
   * Returns the denominator units of this number.
   * @return string the denominator units of this number
   */
  public function getDenominatorUnits() {
    return join(' * ', $this->denominatorUnits);
  }

  /**
   * Returns the numerator units of this number.
   * @return string the numerator units of this number
   */
  public function getNumeratorUnits() {
    return join(' * ', $this->numeratorUnits);
  }

  /**
   * Returns a value indicating if this number can be compared to other.
   * @return boolean true if this number can be compared to other, false if not
   */
  public function isComparableTo($other) {
    try {
      $this
        ->op_plus($other);
      return true;
    } catch (Exception $e) {
      return false;
    }
  }

  /**
   * Returns a value indicating if this number is an integer.
   * @return boolean true if this number is an integer, false if not
   */
  public function isInt() {
    return $this->value % 1 === 0;
  }

  /**
   * Returns the value of this number.
   * @return float the value of this number.
   */
  public function getValue() {
    return $this->value;
  }

  /**
   * Returns the integer value.
   * @return integer the integer value.
   * @throws SassNumberException if the number is not an integer
   */
  public function toInt() {
    if (!$this
      ->isInt()) {
      throw new SassNumberException('Not an integer: ' . $this->value, SassScriptParser::$context->node);
    }
    return intval($this->value);
  }

  /**
   * Converts the number to a string with it's units if any.
   * If the units are px the result is rounded down to the nearest integer,
   * otherwise the result is rounded to the specified precision.
   * @return string number as a string with it's units if any
   */
  public function toString() {
    if (!$this
      ->hasLegalUnits()) {
      throw new SassNumberException('Invalid CSS units (' . $this->units . ')', SassScriptParser::$context->node);
    }
    return ($this->units == 'px' ? floor($this->value) : round($this->value, self::PRECISION)) . $this->units;
  }

  /**
   * Returns a value indicating if a token of this type can be matched at
   * the start of the subject string.
   * @param string the subject string
   * @return mixed match at the start of the string or false if no match
   */
  public static function isa($subject) {
    return preg_match(self::MATCH, $subject, $matches) ? $matches[0] : false;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SassLiteral::$typeOf public static property
SassLiteral::$value public property
SassLiteral::addChild public function Adds a child object to this.
SassLiteral::assertInRange public static function Asserts that the value of a literal is within the expected range
SassLiteral::assertType public static function Asserts that the literal is the expected type
SassLiteral::getTypeOf public function Returns the type of this
SassLiteral::op_and public function The SassScript and operation.
SassLiteral::op_assign public function
SassLiteral::op_bw_and public function Bitwise AND the value of other and this value 1
SassLiteral::op_bw_not public function Bitwise NOT the value of other and the value of this
SassLiteral::op_bw_or public function Bitwise OR the value of other and this value 1
SassLiteral::op_bw_xor public function Bitwise XOR the value of other and the value of this 1
SassLiteral::op_comma public function SassScript ',' operation.
SassLiteral::op_concat public function The SassScript default operation (e.g. $a $b, "foo" "bar").
SassLiteral::op_neq public function The SassScript != operation.
SassLiteral::op_not public function The SassScript not operation. 1
SassLiteral::op_or public function The SassScript or operation.
SassLiteral::op_shiftl public function Shifts the value of this left by the number of bits given in value 1
SassLiteral::op_shiftr public function Shifts the value of this right by the number of bits given in value 1
SassLiteral::op_xor public function The SassScript xor operation.
SassLiteral::toBoolean public function Returns the boolean representation of the value of this
SassLiteral::__get public function Getter.
SassLiteral::__toString public function
SassNumber::$denominatorUnits private property
SassNumber::$inExpression public property Used to determine whether division should take place
SassNumber::$numeratorUnits private property
SassNumber::$unitConversion private static property (only because pt and pc are expressed as fraction of an inch, so makes the numbers easy to understand). Conversions are based on the following in: inches — 1 inch = 2.54 centimeters cm: centimeters mm: millimeters pc: picas — 1 pica = 12…
SassNumber::areConvertable private function Returns a value indicating if all the units are capable of being converted
SassNumber::coerce public function Returns the value of this number converted to other units. The conversion takes into account the relationship between e.g. mm and cm, as well as between e.g. in and cm.
SassNumber::coercionFactor private function Calculates the corecion factor to apply to the value
SassNumber::convert private function Converts values and units. If this is a unitless numeber it will take the units of other; if not other is coerced to the units of this.
SassNumber::getDenominatorUnits public function Returns the denominator units of this number.
SassNumber::getNumeratorUnits public function Returns the numerator units of this number.
SassNumber::getUnits public function Returns the units of this number.
SassNumber::getValue public function Returns the value of this number. Overrides SassLiteral::getValue
SassNumber::hasLegalUnits public function Returns a value indicating if this number has units that can be represented in CSS.
SassNumber::hasUnits public function Returns a value indicating if this number has units.
SassNumber::isa public static function Returns a value indicating if a token of this type can be matched at the start of the subject string. Overrides SassLiteral::isa
SassNumber::isComparableTo public function Returns a value indicating if this number can be compared to other.
SassNumber::isInt public function Returns a value indicating if this number is an integer.
SassNumber::isUnitless public function Returns a value indicating if this number is unitless.
SassNumber::MATCH constant Regx for matching and extracting numbers
SassNumber::op_div public function Divides this value by the value of other Overrides SassLiteral::op_div
SassNumber::op_eq public function The SassScript == operation. Overrides SassLiteral::op_eq
SassNumber::op_gt public function The SassScript > operation. Overrides SassLiteral::op_gt
SassNumber::op_gte public function The SassScript >= operation. Overrides SassLiteral::op_gte
SassNumber::op_lt public function The SassScript < operation. Overrides SassLiteral::op_lt
SassNumber::op_lte public function The SassScript <= operation. Overrides SassLiteral::op_lte
SassNumber::op_minus public function Subtracts the value of other from this value Overrides SassLiteral::op_minus
SassNumber::op_modulo public function Takes the modulus (remainder) of this value divided by the value of other Overrides SassLiteral::op_modulo
SassNumber::op_plus public function Adds the value of other to the value of this Overrides SassLiteral::op_plus
SassNumber::op_times public function Multiplies this value by the value of other Overrides SassLiteral::op_times
SassNumber::op_unary_concat public function
SassNumber::op_unary_minus public function Unary - operator
SassNumber::op_unary_plus public function Unary + operator
SassNumber::PRECISION constant The number of decimal digits to round to. If the units are pixels the result is always rounded down to the nearest integer.
SassNumber::removeCommonUnits private function Removes common units from each set. We don't use array_diff because we want (for eaxmple) mm*mm/mm*cm to end up as mm/cm.
SassNumber::toInt public function Returns the integer value.
SassNumber::toString public function Converts the number to a string with it's units if any. If the units are px the result is rounded down to the nearest integer, otherwise the result is rounded to the specified precision. Overrides SassLiteral::toString
SassNumber::UNITS constant
SassNumber::unitString public function Returns a string representation of the units.
SassNumber::VALUE constant
SassNumber::__construct public function class constructor. Sets the value and units of the number. Overrides SassLiteral::__construct