2008-10-12 19:03:06 -07:00
|
|
|
module Sass::Script
|
2009-04-24 18:46:28 -07:00
|
|
|
# Methods in this module are accessible from the SassScript context.
|
2008-06-05 15:06:48 -07:00
|
|
|
# For example, you can write
|
|
|
|
#
|
2009-04-24 18:46:28 -07:00
|
|
|
# !color = hsl(120, 100%, 50%)
|
2008-06-05 15:06:48 -07:00
|
|
|
#
|
2009-04-24 18:46:28 -07:00
|
|
|
# and it will call {Sass::Script::Functions#hsl}.
|
|
|
|
#
|
|
|
|
# The following functions are provided:
|
|
|
|
#
|
|
|
|
# \{#hsl}
|
|
|
|
# : Converts an `hsl(hue, saturation, lightness)` triplet into a color.
|
|
|
|
#
|
2009-11-11 20:20:45 -08:00
|
|
|
# \{#rgb}
|
|
|
|
# : Converts an `rgb(red, green, blue)` triplet into a color.
|
|
|
|
#
|
2009-04-24 18:46:28 -07:00
|
|
|
# \{#percentage}
|
|
|
|
# : Converts a unitless number to a percentage.
|
|
|
|
#
|
2009-11-11 20:17:15 -08:00
|
|
|
# \{#red}
|
|
|
|
# : Gets the red component of a color.
|
|
|
|
#
|
|
|
|
# \{#green}
|
|
|
|
# : Gets the green component of a color.
|
|
|
|
#
|
|
|
|
# \{#blue}
|
|
|
|
# : Gets the blue component of a color.
|
|
|
|
#
|
2009-04-24 18:46:28 -07:00
|
|
|
# \{#round}
|
|
|
|
# : Rounds a number to the nearest whole number.
|
|
|
|
#
|
|
|
|
# \{#ceil}
|
|
|
|
# : Rounds a number up to the nearest whole number.
|
|
|
|
#
|
|
|
|
# \{#floor}
|
|
|
|
# : Rounds a number down to the nearest whole number.
|
|
|
|
#
|
|
|
|
# \{#abs}
|
|
|
|
# : Returns the absolute value of a number.
|
2008-06-05 15:06:48 -07:00
|
|
|
#
|
2009-11-11 18:18:16 -08:00
|
|
|
# These functions are described in more detail below.
|
|
|
|
#
|
|
|
|
# ## Adding Custom Functions
|
|
|
|
#
|
|
|
|
# New Sass functions can be added by adding Ruby methods to this module.
|
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# module Sass::Script::Functions
|
|
|
|
# def reverse(string)
|
|
|
|
# assert_type string, :String
|
|
|
|
# Sass::Script::String.new(string.value.reverse)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# There are a few things to keep in mind when modifying this module.
|
2009-04-24 18:46:28 -07:00
|
|
|
# First of all, the arguments passed are {Sass::Script::Literal} objects.
|
|
|
|
# Literal objects are also expected to be returned.
|
2009-11-11 18:18:16 -08:00
|
|
|
# This means that Ruby values must be unwrapped and wrapped.
|
|
|
|
#
|
|
|
|
# Most Literal objects support the {Sass::Script::Literal#value value} accessor
|
|
|
|
# for getting their Ruby values.
|
|
|
|
# Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
|
|
|
|
# {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::Color#blue blue}.
|
2008-06-05 15:06:48 -07:00
|
|
|
#
|
|
|
|
# Second, making Ruby 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
|
|
|
|
# at a somewhat indeterminate time
|
|
|
|
# and then left as static CSS files.
|
2009-04-24 18:46:28 -07:00
|
|
|
# Any dynamic CSS should be left in `<style>` tags in the HTML.
|
2009-01-24 22:28:10 -08:00
|
|
|
#
|
2009-04-24 18:46:28 -07:00
|
|
|
# Within one of the functions in this module,
|
|
|
|
# methods of {EvaluationContext} can be used.
|
2008-06-05 15:06:48 -07:00
|
|
|
module Functions
|
2009-04-24 18:46:28 -07:00
|
|
|
# The context in which methods in {Script::Functions} are evaluated.
|
|
|
|
# That means that all instance methods of {EvaluationContext}
|
|
|
|
# are available to use in functions.
|
|
|
|
class EvaluationContext
|
2009-04-24 18:09:01 -07:00
|
|
|
include Sass::Script::Functions
|
|
|
|
|
2009-04-24 18:46:28 -07:00
|
|
|
# The options hash for the {Sass::Engine} that is processing the function call
|
|
|
|
#
|
|
|
|
# @return [Hash<Symbol, Object>]
|
2009-04-24 18:09:01 -07:00
|
|
|
attr_reader :options
|
|
|
|
|
2009-04-24 18:46:28 -07:00
|
|
|
# @param options [Hash<Symbol, Object>] See \{#options}
|
2009-04-24 18:09:01 -07:00
|
|
|
def initialize(options)
|
|
|
|
@options = options
|
|
|
|
end
|
2009-11-11 18:15:03 -08:00
|
|
|
|
|
|
|
# Asserts that the type of a given SassScript value
|
|
|
|
# is the expected type (designated by a symbol).
|
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# assert_type value, :String
|
|
|
|
# assert_type value, :Number
|
|
|
|
#
|
|
|
|
# Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
|
|
|
|
#
|
|
|
|
# @param value [Sass::Script::Literal] A SassScript value
|
|
|
|
# @param type [Symbol] The name of the type the value is expected to be
|
|
|
|
def assert_type(value, type)
|
|
|
|
return if value.is_a?(Sass::Script.const_get(type))
|
|
|
|
raise ArgumentError.new("#{value.inspect} is not a #{type.to_s.downcase}")
|
|
|
|
end
|
2009-04-24 18:09:01 -07:00
|
|
|
end
|
|
|
|
|
2009-01-22 16:41:36 -08:00
|
|
|
instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
|
2008-06-05 15:06:48 -07:00
|
|
|
|
2009-06-28 22:44:03 -07:00
|
|
|
|
|
|
|
# Creates a {Color} object from red, green, and blue values.
|
|
|
|
# @param red
|
|
|
|
# A number between 0 and 255 inclusive
|
|
|
|
# @param green
|
|
|
|
# A number between 0 and 255 inclusive
|
|
|
|
# @param blue
|
|
|
|
# A number between 0 and 255 inclusive
|
|
|
|
def rgb(red, green, blue)
|
2009-11-10 17:11:25 -08:00
|
|
|
rgba(red, green, blue, Number.new(1))
|
|
|
|
end
|
|
|
|
|
|
|
|
# Creates a {Color} object from red, green, and blue values,
|
|
|
|
# as well as an alpha channel indicating opacity.
|
|
|
|
#
|
|
|
|
# @param red
|
|
|
|
# A number between 0 and 255 inclusive
|
|
|
|
# @param green
|
|
|
|
# A number between 0 and 255 inclusive
|
|
|
|
# @param blue
|
|
|
|
# A number between 0 and 255 inclusive
|
|
|
|
# @param alpha
|
|
|
|
# A number between 0 and 1
|
|
|
|
def rgba(red, green, blue, alpha)
|
2009-11-10 22:21:17 -08:00
|
|
|
assert_type red, :Number
|
|
|
|
assert_type green, :Number
|
|
|
|
assert_type blue, :Number
|
2009-11-10 22:29:51 -08:00
|
|
|
assert_type alpha, :Number
|
2009-11-10 22:21:17 -08:00
|
|
|
|
2009-06-28 22:44:03 -07:00
|
|
|
[red.value, green.value, blue.value].each do |v|
|
2009-11-10 17:11:25 -08:00
|
|
|
next if (0..255).include?(v)
|
2009-07-19 13:25:53 -07:00
|
|
|
raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
|
2009-06-28 22:44:03 -07:00
|
|
|
end
|
2009-11-10 17:11:25 -08:00
|
|
|
|
|
|
|
unless (0..1).include?(alpha.value)
|
|
|
|
raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
|
|
|
|
end
|
|
|
|
|
|
|
|
Color.new([red.value, green.value, blue.value, alpha.value])
|
2009-06-28 22:44:03 -07:00
|
|
|
end
|
|
|
|
|
2009-04-24 18:46:28 -07:00
|
|
|
# Creates a {Color} object from hue, saturation, and lightness
|
2009-11-11 18:18:16 -08:00
|
|
|
# as per the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
|
2009-04-24 18:46:28 -07:00
|
|
|
#
|
|
|
|
# @param hue [Number] The hue of the color.
|
|
|
|
# Should be between 0 and 360 degrees, inclusive
|
|
|
|
# @param saturation [Number] The saturation of the color.
|
|
|
|
# Must be between `0%` and `100%`, inclusive
|
|
|
|
# @param lightness [Number] The lightness of the color.
|
|
|
|
# Must be between `0%` and `100%`, inclusive
|
|
|
|
# @return [Color] The resulting color
|
|
|
|
# @raise [ArgumentError] if `saturation` or `lightness` are out of bounds
|
|
|
|
def hsl(hue, saturation, lightness)
|
2009-11-10 18:07:38 -08:00
|
|
|
hsla(hue, saturation, lightness, Number.new(1))
|
|
|
|
end
|
|
|
|
|
|
|
|
# Creates a {Color} object from hue, saturation, and lightness,
|
|
|
|
# as well as an alpha channel indicating opacity,
|
|
|
|
# as per the CSS3 spec (http://www.w3.org/TR/css3-color/#hsla-color).
|
|
|
|
#
|
|
|
|
# @param hue [Number] The hue of the color.
|
|
|
|
# Should be between 0 and 360 degrees, inclusive
|
|
|
|
# @param saturation [Number] The saturation of the color.
|
|
|
|
# Must be between `0%` and `100%`, inclusive
|
|
|
|
# @param lightness [Number] The lightness of the color.
|
|
|
|
# Must be between `0%` and `100%`, inclusive
|
|
|
|
# @param alpha [Number] The opacity of the color.
|
|
|
|
# Must be between 0 and 1, inclusive
|
|
|
|
# @return [Color] The resulting color
|
|
|
|
# @raise [ArgumentError] if `saturation`, `lightness`, or `alpha` are out of bounds
|
|
|
|
def hsla(hue, saturation, lightness, alpha)
|
2009-11-10 22:21:17 -08:00
|
|
|
assert_type hue, :Number
|
|
|
|
assert_type saturation, :Number
|
|
|
|
assert_type lightness, :Number
|
2009-11-10 22:29:51 -08:00
|
|
|
assert_type alpha, :Number
|
|
|
|
|
2009-11-10 18:07:38 -08:00
|
|
|
unless (0..1).include?(alpha.value)
|
|
|
|
raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
|
|
|
|
end
|
2009-11-10 22:21:17 -08:00
|
|
|
|
2009-04-24 18:46:28 -07:00
|
|
|
original_s = saturation
|
|
|
|
original_l = lightness
|
2008-06-05 15:06:48 -07:00
|
|
|
# This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
|
2009-04-24 18:46:28 -07:00
|
|
|
h, s, l = [hue, saturation, lightness].map { |a| a.value }
|
2009-11-10 18:07:38 -08:00
|
|
|
raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
|
|
|
|
raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
|
2008-10-25 19:02:54 -07:00
|
|
|
|
2008-06-05 15:06:48 -07:00
|
|
|
h = (h % 360) / 360.0
|
|
|
|
s /= 100.0
|
|
|
|
l /= 100.0
|
|
|
|
|
|
|
|
m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
|
|
|
|
m1 = l * 2 - m2
|
2009-11-10 18:07:38 -08:00
|
|
|
Color.new(
|
|
|
|
[hue_to_rgb(m1, m2, h + 1.0/3),
|
|
|
|
hue_to_rgb(m1, m2, h),
|
|
|
|
hue_to_rgb(m1, m2, h - 1.0/3)].map { |c| (c * 0xff).round } +
|
|
|
|
[alpha.value])
|
2008-06-05 15:06:48 -07:00
|
|
|
end
|
|
|
|
|
2009-11-10 21:19:49 -08:00
|
|
|
# Returns the red component of a color.
|
|
|
|
#
|
|
|
|
# @param color [Color]
|
|
|
|
# @return [Number]
|
|
|
|
# @raise [ArgumentError] If `color` isn't a color
|
|
|
|
def red(color)
|
2009-11-10 22:22:40 -08:00
|
|
|
assert_type color, :Color
|
2009-11-10 21:19:49 -08:00
|
|
|
Sass::Script::Number.new(color.red)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the green component of a color.
|
|
|
|
#
|
|
|
|
# @param color [Color]
|
|
|
|
# @return [Number]
|
|
|
|
# @raise [ArgumentError] If `color` isn't a color
|
|
|
|
def green(color)
|
2009-11-10 22:22:40 -08:00
|
|
|
assert_type color, :Color
|
2009-11-10 21:19:49 -08:00
|
|
|
Sass::Script::Number.new(color.green)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the blue component of a color.
|
|
|
|
#
|
|
|
|
# @param color [Color]
|
|
|
|
# @return [Number]
|
|
|
|
# @raise [ArgumentError] If `color` isn't a color
|
|
|
|
def blue(color)
|
2009-11-10 22:22:40 -08:00
|
|
|
assert_type color, :Color
|
2009-11-10 21:19:49 -08:00
|
|
|
Sass::Script::Number.new(color.blue)
|
|
|
|
end
|
|
|
|
|
2009-11-10 22:07:36 -08:00
|
|
|
# Returns the alpha component (opacity) of a color.
|
|
|
|
# This is 1 unless otherwise specified.
|
|
|
|
#
|
|
|
|
# @param color [Color]
|
|
|
|
# @return [Number]
|
|
|
|
# @raise [ArgumentError] If `color` isn't a color
|
|
|
|
def alpha(color)
|
2009-11-10 22:29:51 -08:00
|
|
|
assert_type color, :Color
|
2009-11-10 22:07:36 -08:00
|
|
|
Sass::Script::Number.new(color.alpha)
|
|
|
|
end
|
|
|
|
alias_method :opacity, :alpha
|
|
|
|
|
2009-11-10 22:48:51 -08:00
|
|
|
# Makes a color more opaque.
|
|
|
|
# Takes a color and an amount between `0%` and `100%`
|
|
|
|
# and returns a color that's that much closer to opaque.
|
|
|
|
#
|
|
|
|
# For example, `50%` will make the color twice as opaque:
|
|
|
|
#
|
|
|
|
# opacify(rgba(0, 0, 0, 0.5), 50%) => rgba(0, 0, 0, 0.75)
|
|
|
|
# opacify(rgba(0, 0, 0, 0.8), 50%) => rgba(0, 0, 0, 0.9)
|
|
|
|
# opacify(rgba(0, 0, 0, 0.2), 50%) => rgba(0, 0, 0, 0.8)
|
|
|
|
#
|
|
|
|
# Specifically, `opacify(color, n%)` will make the color
|
|
|
|
# `n%` closer to fully opaque.
|
|
|
|
def opacify(color, amount)
|
|
|
|
assert_type color, :Color
|
|
|
|
assert_type amount, :Number
|
|
|
|
unless (0..100).include?(amount.value)
|
|
|
|
raise ArgumentError.new("Amount #{amount} must be between 0% and 100%")
|
|
|
|
end
|
|
|
|
|
|
|
|
color = color.dup
|
|
|
|
color.alpha += (1 - color.alpha) * (amount.value / 100.0)
|
|
|
|
color
|
|
|
|
end
|
2009-11-10 23:01:55 -08:00
|
|
|
alias_method :fade_in, :opacify
|
2009-11-10 22:48:51 -08:00
|
|
|
|
2009-11-10 22:53:08 -08:00
|
|
|
# Makes a color more transparent.
|
|
|
|
# Takes a color and an amount between `0%` and `100%`
|
|
|
|
# and returns a color that's that much closer to transparent.
|
|
|
|
#
|
|
|
|
# For example, `50%` will make the color twice as transparent:
|
|
|
|
#
|
|
|
|
# opacify(rgba(0, 0, 0, 0.5), 50%) => rgba(0, 0, 0, 0.25)
|
|
|
|
# opacify(rgba(0, 0, 0, 0.8), 50%) => rgba(0, 0, 0, 0.4)
|
|
|
|
# opacify(rgba(0, 0, 0, 0.2), 50%) => rgba(0, 0, 0, 0.1)
|
|
|
|
#
|
|
|
|
# Specifically, `transparentize(color, n%)` will make the color
|
|
|
|
# `n%` closer to fully transparent.
|
|
|
|
def transparentize(color, amount)
|
|
|
|
assert_type color, :Color
|
|
|
|
assert_type amount, :Number
|
|
|
|
unless (0..100).include?(amount.value)
|
|
|
|
raise ArgumentError.new("Amount #{amount} must be between 0% and 100%")
|
|
|
|
end
|
|
|
|
|
|
|
|
color = color.dup
|
|
|
|
color.alpha *= 1 - (amount.value / 100.0)
|
|
|
|
color
|
|
|
|
end
|
2009-11-10 23:01:55 -08:00
|
|
|
alias_method :fade_out, :transparentize
|
2009-11-10 22:53:08 -08:00
|
|
|
|
2009-04-24 18:46:28 -07:00
|
|
|
# Converts a decimal number to a percentage.
|
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# percentage(100px / 50px) => 200%
|
|
|
|
#
|
|
|
|
# @param value [Number] The decimal number to convert to a percentage
|
|
|
|
# @return [Number] The percentage
|
|
|
|
# @raise [ArgumentError] If `value` isn't a unitless number
|
2008-09-21 00:29:21 -07:00
|
|
|
def percentage(value)
|
2008-10-12 19:03:06 -07:00
|
|
|
unless value.is_a?(Sass::Script::Number) && value.unitless?
|
2009-11-10 22:21:17 -08:00
|
|
|
raise ArgumentError.new("#{value.inspect} is not a unitless number")
|
2008-09-21 00:29:21 -07:00
|
|
|
end
|
2009-01-22 17:47:10 -08:00
|
|
|
Sass::Script::Number.new(value.value * 100, ['%'])
|
2008-09-21 00:29:21 -07:00
|
|
|
end
|
|
|
|
|
2008-10-13 11:47:44 -07:00
|
|
|
# Rounds a number to the nearest whole number.
|
2009-04-24 18:46:28 -07:00
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# round(10.4px) => 10px
|
|
|
|
# round(10.6px) => 11px
|
|
|
|
#
|
|
|
|
# @param value [Number] The number
|
|
|
|
# @return [Number] The rounded number
|
|
|
|
# @raise [Sass::SyntaxError] if `value` isn't a number
|
2008-10-13 11:47:44 -07:00
|
|
|
def round(value)
|
|
|
|
numeric_transformation(value) {|n| n.round}
|
|
|
|
end
|
|
|
|
|
2009-04-24 18:46:28 -07:00
|
|
|
# Rounds a number up to the nearest whole number.
|
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# ciel(10.4px) => 11px
|
|
|
|
# ciel(10.6px) => 11px
|
|
|
|
#
|
|
|
|
# @param value [Number] The number
|
|
|
|
# @return [Number] The rounded number
|
|
|
|
# @raise [Sass::SyntaxError] if `value` isn't a number
|
2008-10-13 11:47:44 -07:00
|
|
|
def ceil(value)
|
|
|
|
numeric_transformation(value) {|n| n.ceil}
|
|
|
|
end
|
|
|
|
|
|
|
|
# Rounds down to the nearest whole number.
|
2009-04-24 18:46:28 -07:00
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# floor(10.4px) => 10px
|
|
|
|
# floor(10.6px) => 10px
|
|
|
|
#
|
|
|
|
# @param value [Number] The number
|
|
|
|
# @return [Number] The rounded number
|
|
|
|
# @raise [Sass::SyntaxError] if `value` isn't a number
|
2008-10-13 11:47:44 -07:00
|
|
|
def floor(value)
|
|
|
|
numeric_transformation(value) {|n| n.floor}
|
|
|
|
end
|
|
|
|
|
2009-04-24 18:46:28 -07:00
|
|
|
# Finds the absolute value of a number.
|
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# abs(10px) => 10px
|
|
|
|
# abs(-10px) => 10px
|
|
|
|
#
|
|
|
|
# @param value [Number] The number
|
|
|
|
# @return [Number] The absolute value
|
|
|
|
# @raise [Sass::SyntaxError] if `value` isn't a number
|
2008-10-13 11:47:44 -07:00
|
|
|
def abs(value)
|
|
|
|
numeric_transformation(value) {|n| n.abs}
|
|
|
|
end
|
|
|
|
|
2008-06-05 15:06:48 -07:00
|
|
|
private
|
|
|
|
|
2008-10-13 11:47:44 -07:00
|
|
|
# This method implements the pattern of transforming a numeric value into
|
|
|
|
# another numeric value with the same units.
|
|
|
|
# It yields a number to a block to perform the operation and return a number
|
|
|
|
def numeric_transformation(value)
|
2009-11-10 22:21:17 -08:00
|
|
|
assert_type value, :Number
|
2008-10-13 11:47:44 -07:00
|
|
|
Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
|
|
|
|
end
|
|
|
|
|
2008-06-05 15:06:48 -07:00
|
|
|
def hue_to_rgb(m1, m2, h)
|
|
|
|
h += 1 if h < 0
|
|
|
|
h -= 1 if h > 1
|
|
|
|
return m1 + (m2 - m1) * h * 6 if h * 6 < 1
|
|
|
|
return m2 if h * 2 < 1
|
|
|
|
return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
|
|
|
|
return m1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|