<?php /**
* SassScript functions class file. * * Methods in this module are accessible from the SassScript context. * For example, you can write: * * $colour = hsl(120, 100%, 50%) * and it will call SassFunctions::hsl(). * * There are a few things to keep in mind when modifying this module. * First of all, the arguments passed are SassLiteral objects. * Literal objects are also expected to be returned. * * Most Literal objects support the SassLiteral->value accessor * for getting their values. Colour objects, though, must be accessed using * SassColour::rgb(). * * Second, making functions accessible from Sass introduces the temptation * to do things like database access within stylesheets. * This temptation must be resisted. * Keep in mind that Sass stylesheets are only compiled once and then left as * static CSS files. Any dynamic CSS should be left in <style> tags in the * HTML. * * @author Chris Yates <chris.l.yates@gmail.com> * @copyright Copyright (c) 2010 PBM Web Development * @license http://phamlp.googlecode.com/files/license.txt * @package PHamlP * @subpackage Sass.script */
/**
* SassScript functions class. * A collection of functions for use in SassSCript. * @package PHamlP * @subpackage Sass.script */
class SassScriptFunctions {
const DECREASE = false; const INCREASE = true; /* * Colour Creation */ /** * Creates a SassColour object from red, green, and blue values. * @param SassNumber the red component. * A number between 0 and 255 inclusive, or between 0% and 100% inclusive * @param SassNumber the green component. * A number between 0 and 255 inclusive, or between 0% and 100% inclusive * @param SassNumber the blue component. * A number between 0 and 255 inclusive, or between 0% and 100% inclusive * @return new SassColour SassColour object * @throws SassScriptFunctionException if red, green, or blue are out of bounds */ public static function rgb($red, $green, $blue) { return self::rgba($red, $green, $blue, new SassNumber(1)); } /** * Creates a SassColour object from red, green, and blue values and alpha * channel (opacity). * There are two overloads: * * rgba(red, green, blue, alpha) * @param SassNumber the red component. * A number between 0 and 255 inclusive, or between 0% and 100% inclusive * @param SassNumber the green component. * A number between 0 and 255 inclusive, or between 0% and 100% inclusive * @param SassNumber the blue component. * A number between 0 and 255 inclusive, or between 0% and 100% inclusive * @param SassNumber The alpha channel. A number between 0 and 1. * * * rgba(colour, alpha) * @param SassColour a SassColour object * @param SassNumber The alpha channel. A number between 0 and 1. * * @return new SassColour SassColour object * @throws SassScriptFunctionException if any of the red, green, or blue * colour components are out of bounds, or or the colour is not a colour, or * alpha is out of bounds */ public static function rgba() { switch (func_num_args()) { case 2: $colour = func_get_arg(0); $alpha = func_get_arg(1); SassLiteral::assertType($colour, 'SassColour'); SassLiteral::assertType($alpha, 'SassNumber'); SassLiteral::assertInRange($alpha, 0, 1); return $colour->with(array('alpha' => $alpha->value)); break; case 4: $rgba = array(); $components = func_get_args(); $alpha = array_pop($components); foreach($components as $component) { SassLiteral::assertType($component, 'SassNumber'); if ($component->units == '%') { SassLiteral::assertInRange($component, 0, 100, '%'); $rgba[] = $component->value * 2.55; } else { SassLiteral::assertInRange($component, 0, 255); $rgba[] = $component->value; } } SassLiteral::assertType($alpha, 'SassNumber'); SassLiteral::assertInRange($alpha, 0, 1); $rgba[] = $alpha->value; return new SassColour($rgba); break; default: throw new SassScriptFunctionException('Incorrect argument count for {method}; expected {expected}, received {received}', array('{method}' => __METHOD__, '{expected}' => '2 or 4', '{received}' => func_num_args()), SassScriptParser::$context->node); } } /** * Creates a SassColour object from hue, saturation, and lightness. * Uses the algorithm from the * {@link http://www.w3.org/TR/css3-colour/#hsl-colour CSS3 spec}. * @param float The hue of the colour in degrees. * Should be between 0 and 360 inclusive * @param mixed The saturation of the colour as a percentage. * Must be between '0%' and 100%, inclusive * @param mixed The lightness of the colour as a percentage. * Must be between 0% and 100%, inclusive * @return new SassColour The resulting colour * @throws SassScriptFunctionException if saturation or lightness are out of bounds */ public static function hsl($h, $s, $l) { return self::hsla($h, $s, $l, new SassNumber(1)); } /** * Creates a SassColour object from hue, saturation, lightness and alpha * channel (opacity). * @param SassNumber The hue of the colour in degrees. * Should be between 0 and 360 inclusive * @param SassNumber The saturation of the colour as a percentage. * Must be between 0% and 100% inclusive * @param SassNumber The lightness of the colour as a percentage. * Must be between 0% and 100% inclusive * @param float The alpha channel. A number between 0 and 1. * @return new SassColour The resulting colour * @throws SassScriptFunctionException if saturation, lightness or alpha are * out of bounds */ public static function hsla($h, $s, $l, $a) { SassLiteral::assertType($h, 'SassNumber'); SassLiteral::assertType($s, 'SassNumber'); SassLiteral::assertType($l, 'SassNumber'); SassLiteral::assertType($a, 'SassNumber'); SassLiteral::assertInRange($s, 0, 100, '%'); SassLiteral::assertInRange($l, 0, 100, '%'); SassLiteral::assertInRange($a, 0, 1); return new SassColour(array('hue'=>$h, 'saturation'=>$s, 'lightness'=>$l, 'alpha'=>$a)); } /* * Colour Information */ /** * Returns the red component of a colour. * @param SassColour The colour * @return new SassNumber The red component of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function red($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->red); } /** * Returns the green component of a colour. * @param SassColour The colour * @return new SassNumber The green component of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function green($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->green); } /** * Returns the blue component of a colour. * @param SassColour The colour * @return new SassNumber The blue component of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function blue($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->blue); } /** * Returns the hue component of a colour. * @param SassColour The colour * @return new SassNumber The hue component of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function hue($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->hue); } /** * Returns the saturation component of a colour. * @param SassColour The colour * @return new SassNumber The saturation component of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function saturation($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->saturation); } /** * Returns the lightness component of a colour. * @param SassColour The colour * @return new SassNumber The lightness component of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function lightness($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->lightness); } /** * Returns the alpha component (opacity) of a colour. * @param SassColour The colour * @return new SassNumber The alpha component (opacity) of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function alpha($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->alpha); } /** * Returns the alpha component (opacity) of a colour. * @param SassColour The colour * @return new SassNumber The alpha component (opacity) of colour * @throws SassScriptFunctionException If $colour is not a colour */ public static function opacity($colour) { SassLiteral::assertType($colour, 'SassColour'); return new SassNumber($colour->alpha); } /* * Colour Adjustments */ /** * Changes the hue of a colour while retaining the lightness and saturation. * @param SassColour The colour to adjust * @param SassNumber The amount to adjust the colour by * @return new SassColour The adjusted colour * @throws SassScriptFunctionException If $colour is not a colour or * $degrees is not a number */ public static function adjust_hue($colour, $degrees) { SassLiteral::assertType($colour, 'SassColour'); SassLiteral::assertType($degrees, 'SassNumber'); return $colour->with(array('hue' => $colour->hue + $degrees->value)); } /** * Makes a colour lighter. * @param SassColour The colour to lighten * @param SassNumber The amount to lighten the colour by * @param SassBoolean Whether the amount is a proportion of the current value * (true) or the total range (false). * The default is false - the amount is a proportion of the total range. * If the colour lightness value is 40% and the amount is 50%, * the resulting colour lightness value is 90% if the amount is a proportion * of the total range, whereas it is 60% if the amount is a proportion of the * current value. * @return new SassColour The lightened colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number * @see lighten_rel */ public static function lighten($colour, $amount, $ofCurrent = false) { return self::adjust($colour, $amount, $ofCurrent, 'lightness', self::INCREASE, 0, 100, '%'); } /** * Makes a colour darker. * @param SassColour The colour to darken * @param SassNumber The amount to darken the colour by * @param SassBoolean Whether the amount is a proportion of the current value * (true) or the total range (false). * The default is false - the amount is a proportion of the total range. * If the colour lightness value is 80% and the amount is 50%, * the resulting colour lightness value is 30% if the amount is a proportion * of the total range, whereas it is 40% if the amount is a proportion of the * current value. * @return new SassColour The darkened colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number * @see adjust */ public static function darken($colour, $amount, $ofCurrent = false) { return self::adjust($colour, $amount, $ofCurrent, 'lightness', self::DECREASE, 0, 100, '%'); } /** * Makes a colour more saturated. * @param SassColour The colour to saturate * @param SassNumber The amount to saturate the colour by * @param SassBoolean Whether the amount is a proportion of the current value * (true) or the total range (false). * The default is false - the amount is a proportion of the total range. * If the colour saturation value is 40% and the amount is 50%, * the resulting colour saturation value is 90% if the amount is a proportion * of the total range, whereas it is 60% if the amount is a proportion of the * current value. * @return new SassColour The saturated colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number * @see adjust */ public static function saturate($colour, $amount, $ofCurrent = false) { return self::adjust($colour, $amount, $ofCurrent, 'saturation', self::INCREASE, 0, 100, '%'); } /** * Makes a colour less saturated. * @param SassColour The colour to desaturate * @param SassNumber The amount to desaturate the colour by * @param SassBoolean Whether the amount is a proportion of the current value * (true) or the total range (false). * The default is false - the amount is a proportion of the total range. * If the colour saturation value is 80% and the amount is 50%, * the resulting colour saturation value is 30% if the amount is a proportion * of the total range, whereas it is 40% if the amount is a proportion of the * current value. * @return new SassColour The desaturateed colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number * @see adjust */ public static function desaturate($colour, $amount, $ofCurrent = false) { return self::adjust($colour, $amount, $ofCurrent, 'saturation', self::DECREASE, 0, 100, '%'); } /** * Makes a colour more opaque. * @param SassColour The colour to opacify * @param SassNumber The amount to opacify the colour by * If this is a unitless number between 0 and 1 the adjustment is absolute, * if it is a percentage the adjustment is relative. * If the colour alpha value is 0.4 * if the amount is 0.5 the resulting colour alpha value is 0.9, * whereas if the amount is 50% the resulting colour alpha value is 0.6. * @return new SassColour The opacified colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number * @see opacify_rel */ public static function opacify($colour, $amount, $ofCurrent = false) { $units = self::units($amount); return self::adjust($colour, $amount, $ofCurrent, 'alpha', self::INCREASE, 0, ($units === '%' ? 100 : 1), $units); } /** * Makes a colour more transparent. * @param SassColour The colour to transparentize * @param SassNumber The amount to transparentize the colour by. * If this is a unitless number between 0 and 1 the adjustment is absolute, * if it is a percentage the adjustment is relative. * If the colour alpha value is 0.8 * if the amount is 0.5 the resulting colour alpha value is 0.3, * whereas if the amount is 50% the resulting colour alpha value is 0.4. * @return new SassColour The transparentized colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number */ public static function transparentize($colour, $amount, $ofCurrent = false) { $units = self::units($amount); return self::adjust($colour, $amount, $ofCurrent, 'alpha', self::DECREASE, 0, ($units === '%' ? 100 : 1), $units); } /** * Makes a colour more opaque. * Alias for {@link opacify}. * @param SassColour The colour to opacify * @param SassNumber The amount to opacify the colour by * @param SassBoolean Whether the amount is a proportion of the current value * (true) or the total range (false). * @return new SassColour The opacified colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number * @see opacify */ public static function fade_in($colour, $amount, $ofCurrent = false) { return self::opacify($colour, $amount, $ofCurrent); } /** * Makes a colour more transparent. * Alias for {@link transparentize}. * @param SassColour The colour to transparentize * @param SassNumber The amount to transparentize the colour by * @param SassBoolean Whether the amount is a proportion of the current value * (true) or the total range (false). * @return new SassColour The transparentized colour * @throws SassScriptFunctionException If $colour is not a colour or * $amount is not a number * @see transparentize */ public static function fade_out($colour, $amount, $ofCurrent = false) { return self::transparentize($colour, $amount, $ofCurrent); } /** * Returns the complement of a colour. * Rotates the hue by 180 degrees. * @param SassColour The colour * @return new SassColour The comlemented colour * @uses adjust_hue() */ public static function complement($colour) { return self::adjust_hue($colour, new SassNumber('180deg')); } /** * Greyscale for non-english speakers. * @param SassColour The colour * @return new SassColour The greyscale colour * @see desaturate */ public static function grayscale($colour) { return self::desaturate($colour, new SassNumber(100)); } /** * Converts a colour to greyscale. * Reduces the saturation to zero. * @param SassColour The colour * @return new SassColour The greyscale colour * @see desaturate */ public static function greyscale($colour) { return self::desaturate($colour, new SassNumber(100)); } /** * Mixes two colours together. * Takes the average of each of the RGB components, optionally weighted by the * given percentage. The opacity of the colours is also considered when * weighting the components. * The weight specifies the amount of the first colour that should be included * in the returned colour. The default, 50%, means that half the first colour * and half the second colour should be used. 25% means that a quarter of the * first colour and three quarters of the second colour should be used. * For example: * mix(#f00, #00f) => #7f007f * mix(#f00, #00f, 25%) => #3f00bf * mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75) * * @param SassColour The first colour * @param SassColour The second colour * @param float Percentage of the first colour to use * @return new SassColour The mixed colour * @throws SassScriptFunctionException If $colour1 or $colour2 is * not a colour */ public static function mix($colour1, $colour2, $weight = null) { if (is_null($weight)) $weight = new SassNumber('50%'); SassLiteral::assertType($colour1, 'SassColour'); SassLiteral::assertType($colour2, 'SassColour'); SassLiteral::assertType($weight, 'SassNumber'); SassLiteral::assertInRange($weight, 0, 100, '%'); /* * This algorithm factors in both the user-provided weight * and the difference between the alpha values of the two colours * to decide how to perform the weighted average of the two RGB values. * * It works by first normalizing both parameters to be within [-1, 1], * where 1 indicates "only use colour1", -1 indicates "only use colour 0", * and all values in between indicated a proportionately weighted average. * * Once we have the normalized variables w and a, * we apply the formula (w + a)/(1 + w*a) * to get the combined weight (in [-1, 1]) of colour1. * This formula has two especially nice properties: * * * When either w or a are -1 or 1, the combined weight is also that number * (cases where w * a == -1 are undefined, and handled as a special case). * * * When a is 0, the combined weight is w, and vice versa * * Finally, the weight of colour1 is renormalized to be within [0, 1] * and the weight of colour2 is given by 1 minus the weight of colour1. */ $p = $weight->value/100; $w = $p * 2 - 1; $a = $colour1->alpha - $colour2->alpha; $w1 = ((($w * $a == -1) ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2; $w2 = 1 - $w1; $rgb1 = $colour1->rgb(); $rgb2 = $colour2->rgb(); $rgba = array(); foreach ($rgb1 as $key=>$value) { $rgba[$key] = $value * $w1 + $rgb2[$key] * $w2; } // foreach $rgba[] = $colour1->alpha * $p + $colour2->alpha * (1 - $p); return new SassColour($rgba); } /** * Adjusts the colour * @param SassColour the colour to adjust * @param SassNumber the amount to adust by * @param boolean whether the amount is a proportion of the current value or * the total range * @param string the attribute to adjust * @param boolean whether to decrease (false) or increase (true) the value of the attribute * @param float minimum value the amount can be * @param float maximum value the amount can bemixed * @param string amount units */ private static function adjust($colour, $amount, $ofCurrent, $attribute, $op, $min, $max, $units='') { SassLiteral::assertType($colour, 'SassColour'); SassLiteral::assertType($amount, 'SassNumber'); SassLiteral::assertInRange($amount, $min, $max, $units); if (!is_bool($ofCurrent)) { SassLiteral::assertType($ofCurrent, 'SassBoolean'); $ofCurrent = $ofCurrent->value; } $amount = $amount->value * (($attribute === 'alpha' && $ofCurrent && $units === '') ? 100 : 1); return $colour->with(array( $attribute => self::inRange(( $ofCurrent ? $colour->$attribute * (1 + ($amount * ($op === self::INCREASE ? 1 : -1))/100) : $colour->$attribute + ($amount * ($op === self::INCREASE ? 1 : -1)) ), $min, $max) )); } /* * Number Functions */ /** * Finds the absolute value of a number. * For example: * abs(10px) => 10px * abs(-10px) => 10px * * @param SassNumber The number to round * @return SassNumber The absolute value of the number * @throws SassScriptFunctionException If $number is not a number */ public static function abs($number) { SassLiteral::assertType($number, 'SassNumber'); return new SassNumber(abs($number->value).$number->units); } /** * Rounds a number up to the nearest whole number. * For example: * ceil(10.4px) => 11px * ceil(10.6px) => 11px * * @param SassNumber The number to round * @return new SassNumber The rounded number * @throws SassScriptFunctionException If $number is not a number */ public static function ceil($number) { SassLiteral::assertType($number, 'SassNumber'); return new SassNumber(ceil($number->value).$number->units); } /** * Rounds down to the nearest whole number. * For example: * floor(10.4px) => 10px * floor(10.6px) => 10px * * @param SassNumber The number to round * @return new SassNumber The rounded number * @throws SassScriptFunctionException If $value is not a number */ public static function floor($number) { SassLiteral::assertType($number, 'SassNumber'); return new SassNumber(floor($number->value).$number->units); } /** * Rounds a number to the nearest whole number. * For example: * round(10.4px) => 10px * round(10.6px) => 11px * * @param SassNumber The number to round * @return new SassNumber The rounded number * @throws SassScriptFunctionException If $number is not a number */ public static function round($number) { SassLiteral::assertType($number, 'SassNumber'); return new SassNumber(round($number->value).$number->units); } /** * Returns true if two numbers are similar enough to be added, subtracted, * or compared. * @param SassNumber The first number to test * @param SassNumber The second number to test * @return new SassBoolean True if the numbers are similar * @throws SassScriptFunctionException If $number1 or $number2 is not * a number */ public static function comparable($number1, $number2) { SassLiteral::assertType($number1, 'SassNumber'); SassLiteral::assertType($number2, 'SassNumber'); return new SassBoolean($number1->isComparableTo($number2)); } /** * Converts a decimal number to a percentage. * For example: * percentage(100px / 50px) => 200% * * @param SassNumber The decimal number to convert to a percentage * @return new SassNumber The number as a percentage * @throws SassScriptFunctionException If $number isn't a unitless number */ public static function percentage($number) { if (!$number instanceof SassNumber || $number->hasUnits()) { throw new SassScriptFunctionException('{what} must be a {type}', array('{what}'=>'number', '{type}'=>'unitless SassNumber'), SassScriptParser::$context->node); } $number->value *= 100; $number->units = '%'; return $number; } /** * Inspects the unit of the number, returning it as a quoted string. * Alias for units. * @param SassNumber The number to inspect * @return new SassString The units of the number * @throws SassScriptFunctionException If $number is not a number * @see units */ public static function unit($number) { return self::units($number); } /** * Inspects the units of the number, returning it as a quoted string. * @param SassNumber The number to inspect * @return new SassString The units of the number * @throws SassScriptFunctionException If $number is not a number */ public static function units($number) { SassLiteral::assertType($number, 'SassNumber'); return new SassString($number->units); } /** * Inspects the unit of the number, returning a boolean indicating if it is * unitless. * @param SassNumber The number to inspect * @return new SassBoolean True if the number is unitless, false if it has units. * @throws SassScriptFunctionException If $number is not a number */ public static function unitless() { SassLiteral::assertType($number, 'SassNumber'); return new SassBoolean(!$number->hasUnits()); } /* * String Functions */ /** * Add quotes to a string if the string isn't quoted, * or returns the same string if it is. * @param string String to quote * @return new SassString Quoted string * @throws SassScriptFunctionException If $string is not a string * @see unquote */ public static function quote($string) { SassLiteral::assertType($string, 'SassString'); return new SassString('"'.$string->value.'"'); } /** * Removes quotes from a string if the string is quoted, or returns the same * string if it's not. * @param string String to unquote * @return new SassString Unuoted string * @throws SassScriptFunctionException If $string is not a string * @see quote */ public static function unquote($string) { SassLiteral::assertType($string, 'SassString'); return new SassString($string->value); } /** * Returns the variable whose name is the string. * @param string String to unquote * @return * @throws SassScriptFunctionException If $string is not a string */ public static function get_var($string) { SassLiteral::assertType($string, 'SassString'); return new SassString($string->toVar()); } /* * Misc. Functions */ /** * Inspects the type of the argument, returning it as an unquoted string. * @param SassLiteral The object to inspect * @return new SassString The type of object * @throws SassScriptFunctionException If $obj is not an instance of a * SassLiteral */ public static function type_of($obj) { SassLiteral::assertType($obj, SassLiteral); return new SassString($obj->typeOf); } /** * Ensures the value is within the given range, clipping it if needed. * @param float the value to test * @param float the minimum value * @param float the maximum value * @return the value clipped to the range */ private static function inRange($value, $min, $max) { return ($value < $min ? $min : ($value > $max ? $max : $value)); }
}