/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/script/functions.rb

http://github.com/scalate/scalate · Ruby · 861 lines · 206 code · 54 blank · 601 comment · 11 complexity · bf4a898d2426b6e61e9f9e2d9ec3e528 MD5 · raw file

  1. module Sass::Script
  2. # Methods in this module are accessible from the SassScript context.
  3. # For example, you can write
  4. #
  5. # $color = hsl(120deg, 100%, 50%)
  6. #
  7. # and it will call {Sass::Script::Functions#hsl}.
  8. #
  9. # The following functions are provided:
  10. #
  11. # ## RGB Functions
  12. #
  13. # \{#rgb}
  14. # : Converts an `rgb(red, green, blue)` triplet into a color.
  15. #
  16. # \{#rgba}
  17. # : Converts an `rgba(red, green, blue, alpha)` quadruplet into a color.
  18. #
  19. # \{#red}
  20. # : Gets the red component of a color.
  21. #
  22. # \{#green}
  23. # : Gets the green component of a color.
  24. #
  25. # \{#blue}
  26. # : Gets the blue component of a color.
  27. #
  28. # \{#mix}
  29. # : Mixes two colors together.
  30. #
  31. # ## HSL Functions
  32. #
  33. # \{#hsl}
  34. # : Converts an `hsl(hue, saturation, lightness)` triplet into a color.
  35. #
  36. # \{#hsla}
  37. # : Converts an `hsla(hue, saturation, lightness, alpha)` quadruplet into a color.
  38. #
  39. # \{#hue}
  40. # : Gets the hue component of a color.
  41. #
  42. # \{#saturation}
  43. # : Gets the saturation component of a color.
  44. #
  45. # \{#lightness}
  46. # : Gets the lightness component of a color.
  47. #
  48. # \{#adjust_hue #adjust-hue}
  49. # : Changes the hue of a color.
  50. #
  51. # \{#lighten}
  52. # : Makes a color lighter.
  53. #
  54. # \{#darken}
  55. # : Makes a color darker.
  56. #
  57. # \{#saturate}
  58. # : Makes a color more saturated.
  59. #
  60. # \{#desaturate}
  61. # : Makes a color less saturated.
  62. #
  63. # \{#grayscale}
  64. # : Converts a color to grayscale.
  65. #
  66. # \{#complement}
  67. # : Returns the complement of a color.
  68. #
  69. # ## Opacity Functions
  70. #
  71. # \{#alpha} / \{#opacity}
  72. # : Gets the alpha component (opacity) of a color.
  73. #
  74. # \{#rgba}
  75. # : Sets the alpha component of a color.
  76. #
  77. # \{#opacify} / \{#fade_in #fade-in}
  78. # : Makes a color more opaque.
  79. #
  80. # \{#transparentize} / \{#fade_out #fade-out}
  81. # : Makes a color more transparent.
  82. #
  83. # ## String Functions
  84. #
  85. # \{#unquote}
  86. # : Removes the quotes from a string.
  87. #
  88. # \{#quote}
  89. # : Adds quotes to a string.
  90. #
  91. # ## Number Functions
  92. #
  93. # \{#percentage}
  94. # : Converts a unitless number to a percentage.
  95. #
  96. # \{#round}
  97. # : Rounds a number to the nearest whole number.
  98. #
  99. # \{#ceil}
  100. # : Rounds a number up to the nearest whole number.
  101. #
  102. # \{#floor}
  103. # : Rounds a number down to the nearest whole number.
  104. #
  105. # \{#abs}
  106. # : Returns the absolute value of a number.
  107. #
  108. # ## Introspection Functions
  109. #
  110. # \{#type_of}
  111. # : Returns the type of a value.
  112. #
  113. # \{#unit}
  114. # : Returns the units associated with a number.
  115. #
  116. # \{#unitless}
  117. # : Returns whether a number has units or not.
  118. #
  119. # \{#comparable}
  120. # : Returns whether two numbers can be added or compared.
  121. #
  122. # These functions are described in more detail below.
  123. #
  124. # ## Adding Custom Functions
  125. #
  126. # New Sass functions can be added by adding Ruby methods to this module.
  127. # For example:
  128. #
  129. # module Sass::Script::Functions
  130. # def reverse(string)
  131. # assert_type string, :String
  132. # Sass::Script::String.new(string.value.reverse)
  133. # end
  134. # end
  135. #
  136. # There are a few things to keep in mind when modifying this module.
  137. # First of all, the arguments passed are {Sass::Script::Literal} objects.
  138. # Literal objects are also expected to be returned.
  139. # This means that Ruby values must be unwrapped and wrapped.
  140. #
  141. # Most Literal objects support the {Sass::Script::Literal#value value} accessor
  142. # for getting their Ruby values.
  143. # Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
  144. # {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::Color#blue blue}.
  145. #
  146. # Second, making Ruby functions accessible from Sass introduces the temptation
  147. # to do things like database access within stylesheets.
  148. # This is generally a bad idea;
  149. # since Sass files are by default only compiled once,
  150. # dynamic code is not a great fit.
  151. #
  152. # If you really, really need to compile Sass on each request,
  153. # first make sure you have adequate caching set up.
  154. # Then you can use {Sass::Engine} to render the code,
  155. # using the {file:SASS_REFERENCE.md#custom-option `options` parameter}
  156. # to pass in data that {EvaluationContext#options can be accessed}
  157. # from your Sass functions.
  158. #
  159. # Within one of the functions in this module,
  160. # methods of {EvaluationContext} can be used.
  161. #
  162. # ### Caveats
  163. #
  164. # When creating new {Literal} objects within functions,
  165. # be aware that it's not safe to call {Literal#to_s #to_s}
  166. # (or other methods that use the string representation)
  167. # on those objects without first setting {Node#options= the #options attribute}.
  168. module Functions
  169. # The context in which methods in {Script::Functions} are evaluated.
  170. # That means that all instance methods of {EvaluationContext}
  171. # are available to use in functions.
  172. class EvaluationContext
  173. # The options hash for the {Sass::Engine} that is processing the function call
  174. #
  175. # @return [{Symbol => Object}]
  176. attr_reader :options
  177. # @param options [{Symbol => Object}] See \{#options}
  178. def initialize(options)
  179. @options = options
  180. # We need to include this individually in each instance
  181. # because of an icky Ruby restriction
  182. class << self; include Sass::Script::Functions; end
  183. end
  184. # Asserts that the type of a given SassScript value
  185. # is the expected type (designated by a symbol).
  186. # For example:
  187. #
  188. # assert_type value, :String
  189. # assert_type value, :Number
  190. #
  191. # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
  192. # Note that `:String` will match both double-quoted strings
  193. # and unquoted identifiers.
  194. #
  195. # @param value [Sass::Script::Literal] A SassScript value
  196. # @param type [Symbol] The name of the type the value is expected to be
  197. def assert_type(value, type)
  198. return if value.is_a?(Sass::Script.const_get(type))
  199. raise ArgumentError.new("#{value.inspect} is not a #{type.to_s.downcase}")
  200. end
  201. end
  202. instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
  203. # Creates a {Color} object from red, green, and blue values.
  204. #
  205. # @param red [Number]
  206. # A number between 0 and 255 inclusive,
  207. # or between 0% and 100% inclusive
  208. # @param green [Number]
  209. # A number between 0 and 255 inclusive,
  210. # or between 0% and 100% inclusive
  211. # @param blue [Number]
  212. # A number between 0 and 255 inclusive,
  213. # or between 0% and 100% inclusive
  214. # @see #rgba
  215. # @return [Color]
  216. def rgb(red, green, blue)
  217. assert_type red, :Number
  218. assert_type green, :Number
  219. assert_type blue, :Number
  220. Color.new([red, green, blue].map do |c|
  221. v = c.value
  222. if c.numerator_units == ["%"] && c.denominator_units.empty?
  223. next v * 255 / 100.0 if (0..100).include?(v)
  224. raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
  225. else
  226. next v if (0..255).include?(v)
  227. raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
  228. end
  229. end)
  230. end
  231. # @see #rgb
  232. # @overload rgba(red, green, blue, alpha)
  233. # Creates a {Color} object from red, green, and blue values,
  234. # as well as an alpha channel indicating opacity.
  235. #
  236. # @param red [Number]
  237. # A number between 0 and 255 inclusive
  238. # @param green [Number]
  239. # A number between 0 and 255 inclusive
  240. # @param blue [Number]
  241. # A number between 0 and 255 inclusive
  242. # @param alpha [Number]
  243. # A number between 0 and 1
  244. # @return [Color]
  245. #
  246. # @overload rgba(color, alpha)
  247. # Sets the opacity of a color.
  248. #
  249. # @example
  250. # rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
  251. # rgba(blue, 0.2) => rgba(0, 0, 255, 0.2)
  252. #
  253. # @param color [Color]
  254. # @param alpha [Number]
  255. # A number between 0 and 1
  256. # @return [Color]
  257. def rgba(*args)
  258. case args.size
  259. when 2
  260. color, alpha = args
  261. assert_type color, :Color
  262. assert_type alpha, :Number
  263. unless (0..1).include?(alpha.value)
  264. raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
  265. end
  266. color.with(:alpha => alpha.value)
  267. when 4
  268. red, green, blue, alpha = args
  269. rgba(rgb(red, green, blue), alpha)
  270. else
  271. raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
  272. end
  273. end
  274. # Creates a {Color} object from hue, saturation, and lightness.
  275. # Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
  276. #
  277. # @param hue [Number] The hue of the color.
  278. # Should be between 0 and 360 degrees, inclusive
  279. # @param saturation [Number] The saturation of the color.
  280. # Must be between `0%` and `100%`, inclusive
  281. # @param lightness [Number] The lightness of the color.
  282. # Must be between `0%` and `100%`, inclusive
  283. # @return [Color] The resulting color
  284. # @see #hsla
  285. # @raise [ArgumentError] if `saturation` or `lightness` are out of bounds
  286. def hsl(hue, saturation, lightness)
  287. hsla(hue, saturation, lightness, Number.new(1))
  288. end
  289. # Creates a {Color} object from hue, saturation, and lightness,
  290. # as well as an alpha channel indicating opacity.
  291. # Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
  292. #
  293. # @param hue [Number] The hue of the color.
  294. # Should be between 0 and 360 degrees, inclusive
  295. # @param saturation [Number] The saturation of the color.
  296. # Must be between `0%` and `100%`, inclusive
  297. # @param lightness [Number] The lightness of the color.
  298. # Must be between `0%` and `100%`, inclusive
  299. # @param alpha [Number] The opacity of the color.
  300. # Must be between 0 and 1, inclusive
  301. # @return [Color] The resulting color
  302. # @see #hsl
  303. # @raise [ArgumentError] if `saturation`, `lightness`, or `alpha` are out of bounds
  304. def hsla(hue, saturation, lightness, alpha)
  305. assert_type hue, :Number
  306. assert_type saturation, :Number
  307. assert_type lightness, :Number
  308. assert_type alpha, :Number
  309. unless (0..1).include?(alpha.value)
  310. raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
  311. end
  312. original_s = saturation
  313. original_l = lightness
  314. # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
  315. h, s, l = [hue, saturation, lightness].map { |a| a.value }
  316. raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
  317. raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
  318. Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
  319. end
  320. # Returns the red component of a color.
  321. #
  322. # @param color [Color]
  323. # @return [Number]
  324. # @raise [ArgumentError] If `color` isn't a color
  325. def red(color)
  326. assert_type color, :Color
  327. Sass::Script::Number.new(color.red)
  328. end
  329. # Returns the green component of a color.
  330. #
  331. # @param color [Color]
  332. # @return [Number]
  333. # @raise [ArgumentError] If `color` isn't a color
  334. def green(color)
  335. assert_type color, :Color
  336. Sass::Script::Number.new(color.green)
  337. end
  338. # Returns the blue component of a color.
  339. #
  340. # @param color [Color]
  341. # @return [Number]
  342. # @raise [ArgumentError] If `color` isn't a color
  343. def blue(color)
  344. assert_type color, :Color
  345. Sass::Script::Number.new(color.blue)
  346. end
  347. # Returns the hue component of a color.
  348. #
  349. # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
  350. #
  351. # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
  352. #
  353. # @param color [Color]
  354. # @return [Number] between 0deg and 360deg
  355. # @see #adjust_hue
  356. # @raise [ArgumentError] if `color` isn't a color
  357. def hue(color)
  358. assert_type color, :Color
  359. Sass::Script::Number.new(color.hue, ["deg"])
  360. end
  361. # Returns the saturation component of a color.
  362. #
  363. # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
  364. #
  365. # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
  366. #
  367. # @param color [Color]
  368. # @return [Number] between 0% and 100%
  369. # @see #saturate
  370. # @see #desaturate
  371. # @raise [ArgumentError] if `color` isn't a color
  372. def saturation(color)
  373. assert_type color, :Color
  374. Sass::Script::Number.new(color.saturation, ["%"])
  375. end
  376. # Returns the hue component of a color.
  377. #
  378. # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
  379. #
  380. # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
  381. #
  382. # @param color [Color]
  383. # @return [Number] between 0% and 100%
  384. # @see #lighten
  385. # @see #darken
  386. # @raise [ArgumentError] if `color` isn't a color
  387. def lightness(color)
  388. assert_type color, :Color
  389. Sass::Script::Number.new(color.lightness, ["%"])
  390. end
  391. # Returns the alpha component (opacity) of a color.
  392. # This is 1 unless otherwise specified.
  393. #
  394. # This function also supports the proprietary Microsoft
  395. # `alpha(opacity=20)` syntax.
  396. #
  397. # @overload def alpha(color)
  398. # @param color [Color]
  399. # @return [Number]
  400. # @see #opacify
  401. # @see #transparentize
  402. # @raise [ArgumentError] If `color` isn't a color
  403. def alpha(*args)
  404. if args.all? do |a|
  405. a.is_a?(Sass::Script::String) && a.type == :identifier &&
  406. a.value =~ /^[a-zA-Z]+\s*=/
  407. end
  408. # Support the proprietary MS alpha() function
  409. return Sass::Script::String.new("alpha(#{args.map {|a| a.to_s}.join(", ")})")
  410. end
  411. opacity(*args)
  412. end
  413. # Returns the alpha component (opacity) of a color.
  414. # This is 1 unless otherwise specified.
  415. #
  416. # @param color [Color]
  417. # @return [Number]
  418. # @see #opacify
  419. # @see #transparentize
  420. # @raise [ArgumentError] If `color` isn't a color
  421. def opacity(color)
  422. assert_type color, :Color
  423. Sass::Script::Number.new(color.alpha)
  424. end
  425. # Makes a color more opaque.
  426. # Takes a color and an amount between 0 and 1,
  427. # and returns a color with the opacity increased by that value.
  428. #
  429. # For example:
  430. #
  431. # opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
  432. # opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
  433. #
  434. # @param color [Color]
  435. # @param amount [Number]
  436. # @return [Color]
  437. # @see #transparentize
  438. # @raise [ArgumentError] If `color` isn't a color,
  439. # or `number` isn't a number between 0 and 1
  440. def opacify(color, amount)
  441. adjust(color, amount, :alpha, 0..1, :+)
  442. end
  443. alias_method :fade_in, :opacify
  444. # Makes a color more transparent.
  445. # Takes a color and an amount between 0 and 1,
  446. # and returns a color with the opacity decreased by that value.
  447. #
  448. # For example:
  449. #
  450. # transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
  451. # transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
  452. #
  453. # @param color [Color]
  454. # @param amount [Number]
  455. # @return [Color]
  456. # @see #opacify
  457. # @raise [ArgumentError] If `color` isn't a color,
  458. # or `number` isn't a number between 0 and 1
  459. def transparentize(color, amount)
  460. adjust(color, amount, :alpha, 0..1, :-)
  461. end
  462. alias_method :fade_out, :transparentize
  463. # Makes a color lighter.
  464. # Takes a color and an amount between 0% and 100%,
  465. # and returns a color with the lightness increased by that value.
  466. #
  467. # For example:
  468. #
  469. # lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
  470. # lighten(#800, 20%) => #e00
  471. #
  472. # @param color [Color]
  473. # @param amount [Number]
  474. # @return [Color]
  475. # @see #darken
  476. # @raise [ArgumentError] If `color` isn't a color,
  477. # or `number` isn't a number between 0% and 100%
  478. def lighten(color, amount)
  479. adjust(color, amount, :lightness, 0..100, :+, "%")
  480. end
  481. # Makes a color darker.
  482. # Takes a color and an amount between 0% and 100%,
  483. # and returns a color with the lightness decreased by that value.
  484. #
  485. # For example:
  486. #
  487. # darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
  488. # darken(#800, 20%) => #200
  489. #
  490. # @param color [Color]
  491. # @param amount [Number]
  492. # @return [Color]
  493. # @see #lighten
  494. # @raise [ArgumentError] If `color` isn't a color,
  495. # or `number` isn't a number between 0% and 100%
  496. def darken(color, amount)
  497. adjust(color, amount, :lightness, 0..100, :-, "%")
  498. end
  499. # Makes a color more saturated.
  500. # Takes a color and an amount between 0% and 100%,
  501. # and returns a color with the saturation increased by that value.
  502. #
  503. # For example:
  504. #
  505. # saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
  506. # saturate(#855, 20%) => #9e3f3f
  507. #
  508. # @param color [Color]
  509. # @param amount [Number]
  510. # @return [Color]
  511. # @see #desaturate
  512. # @raise [ArgumentError] If `color` isn't a color,
  513. # or `number` isn't a number between 0% and 100%
  514. def saturate(color, amount)
  515. adjust(color, amount, :saturation, 0..100, :+, "%")
  516. end
  517. # Makes a color less saturated.
  518. # Takes a color and an amount between 0% and 100%,
  519. # and returns a color with the saturation decreased by that value.
  520. #
  521. # For example:
  522. #
  523. # desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
  524. # desaturate(#855, 20%) => #726b6b
  525. #
  526. # @param color [Color]
  527. # @param amount [Number]
  528. # @return [Color]
  529. # @see #saturate
  530. # @raise [ArgumentError] If `color` isn't a color,
  531. # or `number` isn't a number between 0% and 100%
  532. def desaturate(color, amount)
  533. adjust(color, amount, :saturation, 0..100, :-, "%")
  534. end
  535. # Changes the hue of a color while retaining the lightness and saturation.
  536. # Takes a color and a number of degrees (usually between -360deg and 360deg),
  537. # and returns a color with the hue rotated by that value.
  538. #
  539. # For example:
  540. #
  541. # adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
  542. # adjust-hue(hsl(120, 30%, 90%), 060deg) => hsl(60, 30%, 90%)
  543. # adjust-hue(#811, 45deg) => #886a11
  544. #
  545. # @param color [Color]
  546. # @param amount [Number]
  547. # @return [Color]
  548. # @raise [ArgumentError] If `color` isn't a color, or `number` isn't a number
  549. def adjust_hue(color, degrees)
  550. assert_type color, :Color
  551. assert_type degrees, :Number
  552. color.with(:hue => color.hue + degrees.value)
  553. end
  554. # Mixes together two colors.
  555. # Specifically, takes the average of each of the RGB components,
  556. # optionally weighted by the given percentage.
  557. # The opacity of the colors is also considered when weighting the components.
  558. #
  559. # The weight specifies the amount of the first color that should be included
  560. # in the returned color.
  561. # The default, 50%, means that half the first color
  562. # and half the second color should be used.
  563. # 25% means that a quarter of the first color
  564. # and three quarters of the second color should be used.
  565. #
  566. # For example:
  567. #
  568. # mix(#f00, #00f) => #7f007f
  569. # mix(#f00, #00f, 25%) => #3f00bf
  570. # mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
  571. #
  572. # @overload mix(color1, color2, weight = 50%)
  573. # @param color1 [Color]
  574. # @param color2 [Color]
  575. # @param weight [Number] between 0% and 100%
  576. # @return [Color]
  577. # @raise [ArgumentError] if `color1` or `color2` aren't colors,
  578. # or `weight` isn't a number between 0% and 100%
  579. def mix(color1, color2, weight = Number.new(50))
  580. assert_type color1, :Color
  581. assert_type color2, :Color
  582. assert_type weight, :Number
  583. unless (0..100).include?(weight.value)
  584. raise ArgumentError.new("Weight #{weight} must be between 0% and 100%")
  585. end
  586. # This algorithm factors in both the user-provided weight
  587. # and the difference between the alpha values of the two colors
  588. # to decide how to perform the weighted average of the two RGB values.
  589. #
  590. # It works by first normalizing both parameters to be within [-1, 1],
  591. # where 1 indicates "only use color1", -1 indicates "only use color 0",
  592. # and all values in between indicated a proportionately weighted average.
  593. #
  594. # Once we have the normalized variables w and a,
  595. # we apply the formula (w + a)/(1 + w*a)
  596. # to get the combined weight (in [-1, 1]) of color1.
  597. # This formula has two especially nice properties:
  598. #
  599. # * When either w or a are -1 or 1, the combined weight is also that number
  600. # (cases where w * a == -1 are undefined, and handled as a special case).
  601. #
  602. # * When a is 0, the combined weight is w, and vice versa
  603. #
  604. # Finally, the weight of color1 is renormalized to be within [0, 1]
  605. # and the weight of color2 is given by 1 minus the weight of color1.
  606. p = weight.value/100.0
  607. w = p*2 - 1
  608. a = color1.alpha - color2.alpha
  609. w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0
  610. w2 = 1 - w1
  611. rgb = color1.rgb.zip(color2.rgb).map {|v1, v2| v1*w1 + v2*w2}
  612. alpha = color1.alpha*p + color2.alpha*(1-p)
  613. Color.new(rgb + [alpha])
  614. end
  615. # Converts a color to grayscale.
  616. # This is identical to `desaturate(color, 100%)`.
  617. #
  618. # @param color [Color]
  619. # @return [Color]
  620. # @raise [ArgumentError] if `color` isn't a color
  621. # @see #desaturate
  622. def grayscale(color)
  623. desaturate color, Number.new(100)
  624. end
  625. # Returns the complement of a color.
  626. # This is identical to `adjust-hue(color, 180deg)`.
  627. #
  628. # @param color [Color]
  629. # @return [Color]
  630. # @raise [ArgumentError] if `color` isn't a color
  631. # @see #adjust_hue #adjust-hue
  632. def complement(color)
  633. adjust_hue color, Number.new(180)
  634. end
  635. # Removes quotes from a string if the string is quoted,
  636. # or returns the same string if it's not.
  637. #
  638. # @param str [String]
  639. # @return [String]
  640. # @raise [ArgumentError] if `str` isn't a string
  641. # @see #quote
  642. # @example
  643. # unquote("foo") => foo
  644. # unquote(foo) => foo
  645. def unquote(str)
  646. assert_type str, :String
  647. Sass::Script::String.new(str.value, :identifier)
  648. end
  649. # Add quotes to a string if the string isn't quoted,
  650. # or returns the same string if it is.
  651. #
  652. # @param str [String]
  653. # @return [String]
  654. # @raise [ArgumentError] if `str` isn't a string
  655. # @see #unquote
  656. # @example
  657. # quote("foo") => "foo"
  658. # quote(foo) => "foo"
  659. def quote(str)
  660. assert_type str, :String
  661. Sass::Script::String.new(str.value, :string)
  662. end
  663. # Inspects the type of the argument, returning it as an unquoted string.
  664. # For example:
  665. #
  666. # type-of(100px) => number
  667. # type-of(asdf) => string
  668. # type-of("asdf") => string
  669. # type-of(true) => bool
  670. # type-of(#fff) => color
  671. # type-of(blue) => color
  672. #
  673. # @param obj [Literal] The object to inspect
  674. # @return [String] The unquoted string name of the literal's type
  675. def type_of(obj)
  676. Sass::Script::String.new(obj.class.name.gsub(/Sass::Script::/,'').downcase)
  677. end
  678. # Inspects the unit of the number, returning it as a quoted string.
  679. # Complex units are sorted in alphabetical order by numerator and denominator.
  680. # For example:
  681. #
  682. # unit(100) => ""
  683. # unit(100px) => "px"
  684. # unit(3em) => "em"
  685. # unit(10px * 5em) => "em*px"
  686. # unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem"
  687. #
  688. # @param number [Literal] The number to inspect
  689. # @return [String] The unit(s) of the number
  690. # @raise [ArgumentError] if `number` isn't a number
  691. def unit(number)
  692. assert_type number, :Number
  693. Sass::Script::String.new(number.unit_str, :string)
  694. end
  695. # Inspects the unit of the number, returning a boolean indicating if it is unitless.
  696. # For example:
  697. #
  698. # unitless(100) => true
  699. # unitless(100px) => false
  700. #
  701. # @param number [Literal] The number to inspect
  702. # @return [Bool] Whether or not the number is unitless
  703. # @raise [ArgumentError] if `number` isn't a number
  704. def unitless(number)
  705. assert_type number, :Number
  706. Sass::Script::Bool.new(number.unitless?)
  707. end
  708. # Returns true if two numbers are similar enough to be added, subtracted, or compared.
  709. # For example:
  710. #
  711. # comparable(2px, 1px) => true
  712. # comparable(100px, 3em) => false
  713. # comparable(10cm, 3mm) => true
  714. #
  715. # @param number1 [Number]
  716. # @param number2 [Number]
  717. # @return [Bool] indicating if the numbers can be compared.
  718. # @raise [ArgumentError] if `number1` or `number2` aren't numbers
  719. def comparable(number1, number2)
  720. assert_type number1, :Number
  721. assert_type number2, :Number
  722. Sass::Script::Bool.new(number1.comparable_to?(number2))
  723. end
  724. # Converts a decimal number to a percentage.
  725. # For example:
  726. #
  727. # percentage(100px / 50px) => 200%
  728. #
  729. # @param value [Number] The decimal number to convert to a percentage
  730. # @return [Number] The percentage
  731. # @raise [ArgumentError] If `value` isn't a unitless number
  732. def percentage(value)
  733. unless value.is_a?(Sass::Script::Number) && value.unitless?
  734. raise ArgumentError.new("#{value.inspect} is not a unitless number")
  735. end
  736. Sass::Script::Number.new(value.value * 100, ['%'])
  737. end
  738. # Rounds a number to the nearest whole number.
  739. # For example:
  740. #
  741. # round(10.4px) => 10px
  742. # round(10.6px) => 11px
  743. #
  744. # @param value [Number] The number
  745. # @return [Number] The rounded number
  746. # @raise [Sass::SyntaxError] if `value` isn't a number
  747. def round(value)
  748. numeric_transformation(value) {|n| n.round}
  749. end
  750. # Rounds a number up to the nearest whole number.
  751. # For example:
  752. #
  753. # ciel(10.4px) => 11px
  754. # ciel(10.6px) => 11px
  755. #
  756. # @param value [Number] The number
  757. # @return [Number] The rounded number
  758. # @raise [Sass::SyntaxError] if `value` isn't a number
  759. def ceil(value)
  760. numeric_transformation(value) {|n| n.ceil}
  761. end
  762. # Rounds down to the nearest whole number.
  763. # For example:
  764. #
  765. # floor(10.4px) => 10px
  766. # floor(10.6px) => 10px
  767. #
  768. # @param value [Number] The number
  769. # @return [Number] The rounded number
  770. # @raise [Sass::SyntaxError] if `value` isn't a number
  771. def floor(value)
  772. numeric_transformation(value) {|n| n.floor}
  773. end
  774. # Finds the absolute value of a number.
  775. # For example:
  776. #
  777. # abs(10px) => 10px
  778. # abs(-10px) => 10px
  779. #
  780. # @param value [Number] The number
  781. # @return [Number] The absolute value
  782. # @raise [Sass::SyntaxError] if `value` isn't a number
  783. def abs(value)
  784. numeric_transformation(value) {|n| n.abs}
  785. end
  786. private
  787. # This method implements the pattern of transforming a numeric value into
  788. # another numeric value with the same units.
  789. # It yields a number to a block to perform the operation and return a number
  790. def numeric_transformation(value)
  791. assert_type value, :Number
  792. Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
  793. end
  794. def adjust(color, amount, attr, range, op, units = "")
  795. assert_type color, :Color
  796. assert_type amount, :Number
  797. unless range.include?(amount.value)
  798. raise ArgumentError.new("Amount #{amount} must be between #{range.first}#{units} and #{range.last}#{units}")
  799. end
  800. # TODO: is it worth restricting here,
  801. # or should we do so in the Color constructor itself,
  802. # and allow clipping in rgb() et al?
  803. color.with(attr => Haml::Util.restrict(
  804. color.send(attr).send(op, amount.value), range))
  805. end
  806. end
  807. end