sass4 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/AGENTS.md +534 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/CONTRIBUTING.md +148 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +242 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/sass-spec-ref.sh +40 -0
  14. data/extra/update_watch.rb +13 -0
  15. data/init.rb +18 -0
  16. data/lib/sass/cache_stores/base.rb +88 -0
  17. data/lib/sass/cache_stores/chain.rb +34 -0
  18. data/lib/sass/cache_stores/filesystem.rb +60 -0
  19. data/lib/sass/cache_stores/memory.rb +46 -0
  20. data/lib/sass/cache_stores/null.rb +25 -0
  21. data/lib/sass/cache_stores.rb +15 -0
  22. data/lib/sass/callbacks.rb +67 -0
  23. data/lib/sass/css.rb +407 -0
  24. data/lib/sass/deprecation.rb +55 -0
  25. data/lib/sass/engine.rb +1236 -0
  26. data/lib/sass/environment.rb +236 -0
  27. data/lib/sass/error.rb +198 -0
  28. data/lib/sass/exec/base.rb +188 -0
  29. data/lib/sass/exec/sass_convert.rb +283 -0
  30. data/lib/sass/exec/sass_scss.rb +436 -0
  31. data/lib/sass/exec.rb +9 -0
  32. data/lib/sass/features.rb +48 -0
  33. data/lib/sass/importers/base.rb +182 -0
  34. data/lib/sass/importers/deprecated_path.rb +51 -0
  35. data/lib/sass/importers/filesystem.rb +221 -0
  36. data/lib/sass/importers.rb +23 -0
  37. data/lib/sass/logger/base.rb +47 -0
  38. data/lib/sass/logger/delayed.rb +50 -0
  39. data/lib/sass/logger/log_level.rb +45 -0
  40. data/lib/sass/logger.rb +17 -0
  41. data/lib/sass/media.rb +210 -0
  42. data/lib/sass/plugin/compiler.rb +552 -0
  43. data/lib/sass/plugin/configuration.rb +134 -0
  44. data/lib/sass/plugin/generic.rb +15 -0
  45. data/lib/sass/plugin/merb.rb +48 -0
  46. data/lib/sass/plugin/rack.rb +60 -0
  47. data/lib/sass/plugin/rails.rb +47 -0
  48. data/lib/sass/plugin/staleness_checker.rb +199 -0
  49. data/lib/sass/plugin.rb +134 -0
  50. data/lib/sass/railtie.rb +10 -0
  51. data/lib/sass/repl.rb +57 -0
  52. data/lib/sass/root.rb +7 -0
  53. data/lib/sass/script/css_lexer.rb +33 -0
  54. data/lib/sass/script/css_parser.rb +36 -0
  55. data/lib/sass/script/functions.rb +3103 -0
  56. data/lib/sass/script/lexer.rb +518 -0
  57. data/lib/sass/script/parser.rb +1164 -0
  58. data/lib/sass/script/tree/funcall.rb +314 -0
  59. data/lib/sass/script/tree/interpolation.rb +220 -0
  60. data/lib/sass/script/tree/list_literal.rb +119 -0
  61. data/lib/sass/script/tree/literal.rb +49 -0
  62. data/lib/sass/script/tree/map_literal.rb +64 -0
  63. data/lib/sass/script/tree/node.rb +119 -0
  64. data/lib/sass/script/tree/operation.rb +149 -0
  65. data/lib/sass/script/tree/selector.rb +26 -0
  66. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  67. data/lib/sass/script/tree/unary_operation.rb +69 -0
  68. data/lib/sass/script/tree/variable.rb +57 -0
  69. data/lib/sass/script/tree.rb +16 -0
  70. data/lib/sass/script/value/arg_list.rb +36 -0
  71. data/lib/sass/script/value/base.rb +258 -0
  72. data/lib/sass/script/value/bool.rb +35 -0
  73. data/lib/sass/script/value/callable.rb +25 -0
  74. data/lib/sass/script/value/color.rb +704 -0
  75. data/lib/sass/script/value/function.rb +19 -0
  76. data/lib/sass/script/value/helpers.rb +298 -0
  77. data/lib/sass/script/value/list.rb +135 -0
  78. data/lib/sass/script/value/map.rb +70 -0
  79. data/lib/sass/script/value/null.rb +44 -0
  80. data/lib/sass/script/value/number.rb +564 -0
  81. data/lib/sass/script/value/string.rb +138 -0
  82. data/lib/sass/script/value.rb +13 -0
  83. data/lib/sass/script.rb +66 -0
  84. data/lib/sass/scss/css_parser.rb +61 -0
  85. data/lib/sass/scss/parser.rb +1343 -0
  86. data/lib/sass/scss/rx.rb +134 -0
  87. data/lib/sass/scss/static_parser.rb +351 -0
  88. data/lib/sass/scss.rb +14 -0
  89. data/lib/sass/selector/abstract_sequence.rb +112 -0
  90. data/lib/sass/selector/comma_sequence.rb +195 -0
  91. data/lib/sass/selector/pseudo.rb +291 -0
  92. data/lib/sass/selector/sequence.rb +661 -0
  93. data/lib/sass/selector/simple.rb +124 -0
  94. data/lib/sass/selector/simple_sequence.rb +348 -0
  95. data/lib/sass/selector.rb +327 -0
  96. data/lib/sass/shared.rb +76 -0
  97. data/lib/sass/source/map.rb +209 -0
  98. data/lib/sass/source/position.rb +39 -0
  99. data/lib/sass/source/range.rb +41 -0
  100. data/lib/sass/stack.rb +140 -0
  101. data/lib/sass/supports.rb +225 -0
  102. data/lib/sass/tree/at_root_node.rb +83 -0
  103. data/lib/sass/tree/charset_node.rb +22 -0
  104. data/lib/sass/tree/comment_node.rb +82 -0
  105. data/lib/sass/tree/content_node.rb +9 -0
  106. data/lib/sass/tree/css_import_node.rb +68 -0
  107. data/lib/sass/tree/debug_node.rb +18 -0
  108. data/lib/sass/tree/directive_node.rb +59 -0
  109. data/lib/sass/tree/each_node.rb +24 -0
  110. data/lib/sass/tree/error_node.rb +18 -0
  111. data/lib/sass/tree/extend_node.rb +43 -0
  112. data/lib/sass/tree/for_node.rb +36 -0
  113. data/lib/sass/tree/function_node.rb +44 -0
  114. data/lib/sass/tree/if_node.rb +52 -0
  115. data/lib/sass/tree/import_node.rb +75 -0
  116. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  117. data/lib/sass/tree/media_node.rb +48 -0
  118. data/lib/sass/tree/mixin_def_node.rb +38 -0
  119. data/lib/sass/tree/mixin_node.rb +52 -0
  120. data/lib/sass/tree/node.rb +240 -0
  121. data/lib/sass/tree/prop_node.rb +162 -0
  122. data/lib/sass/tree/return_node.rb +19 -0
  123. data/lib/sass/tree/root_node.rb +44 -0
  124. data/lib/sass/tree/rule_node.rb +153 -0
  125. data/lib/sass/tree/supports_node.rb +38 -0
  126. data/lib/sass/tree/trace_node.rb +33 -0
  127. data/lib/sass/tree/variable_node.rb +36 -0
  128. data/lib/sass/tree/visitors/base.rb +72 -0
  129. data/lib/sass/tree/visitors/check_nesting.rb +173 -0
  130. data/lib/sass/tree/visitors/convert.rb +350 -0
  131. data/lib/sass/tree/visitors/cssize.rb +362 -0
  132. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  133. data/lib/sass/tree/visitors/extend.rb +64 -0
  134. data/lib/sass/tree/visitors/perform.rb +572 -0
  135. data/lib/sass/tree/visitors/set_options.rb +139 -0
  136. data/lib/sass/tree/visitors/to_css.rb +440 -0
  137. data/lib/sass/tree/warn_node.rb +18 -0
  138. data/lib/sass/tree/while_node.rb +18 -0
  139. data/lib/sass/util/multibyte_string_scanner.rb +151 -0
  140. data/lib/sass/util/normalized_map.rb +122 -0
  141. data/lib/sass/util/subset_map.rb +109 -0
  142. data/lib/sass/util/test.rb +9 -0
  143. data/lib/sass/util.rb +1137 -0
  144. data/lib/sass/version.rb +120 -0
  145. data/lib/sass.rb +102 -0
  146. data/rails/init.rb +1 -0
  147. metadata +283 -0
@@ -0,0 +1,704 @@
1
+ module Sass::Script::Value
2
+ # A SassScript object representing a CSS color.
3
+ #
4
+ # A color may be represented internally as RGBA, HSLA, or both.
5
+ # It's originally represented as whatever its input is;
6
+ # if it's created with RGB values, it's represented as RGBA,
7
+ # and if it's created with HSL values, it's represented as HSLA.
8
+ # Once a property is accessed that requires the other representation --
9
+ # for example, \{#red} for an HSL color --
10
+ # that component is calculated and cached.
11
+ #
12
+ # The alpha channel of a color is independent of its RGB or HSL representation.
13
+ # It's always stored, as 1 if nothing else is specified.
14
+ # If only the alpha channel is modified using \{#with},
15
+ # the cached RGB and HSL values are retained.
16
+ class Color < Base
17
+ # @private
18
+ #
19
+ # Convert a ruby integer to a rgba components
20
+ # @param color [Integer]
21
+ # @return [Array<Integer>] Array of 4 numbers representing r,g,b and alpha
22
+ def self.int_to_rgba(color)
23
+ rgba = (0..3).map {|n| color >> (n << 3) & 0xff}.reverse
24
+ rgba[-1] = rgba[-1] / 255.0
25
+ rgba
26
+ end
27
+
28
+ ALTERNATE_COLOR_NAMES = Sass::Util.map_vals(
29
+ {
30
+ 'aqua' => 0x00FFFFFF,
31
+ 'darkgrey' => 0xA9A9A9FF,
32
+ 'darkslategrey' => 0x2F4F4FFF,
33
+ 'dimgrey' => 0x696969FF,
34
+ 'fuchsia' => 0xFF00FFFF,
35
+ 'grey' => 0x808080FF,
36
+ 'lightgrey' => 0xD3D3D3FF,
37
+ 'lightslategrey' => 0x778899FF,
38
+ 'slategrey' => 0x708090FF,
39
+ }, &method(:int_to_rgba))
40
+
41
+ # A hash from color names to `[red, green, blue]` value arrays.
42
+ COLOR_NAMES = Sass::Util.map_vals(
43
+ {
44
+ 'aliceblue' => 0xF0F8FFFF,
45
+ 'antiquewhite' => 0xFAEBD7FF,
46
+ 'aquamarine' => 0x7FFFD4FF,
47
+ 'azure' => 0xF0FFFFFF,
48
+ 'beige' => 0xF5F5DCFF,
49
+ 'bisque' => 0xFFE4C4FF,
50
+ 'black' => 0x000000FF,
51
+ 'blanchedalmond' => 0xFFEBCDFF,
52
+ 'blue' => 0x0000FFFF,
53
+ 'blueviolet' => 0x8A2BE2FF,
54
+ 'brown' => 0xA52A2AFF,
55
+ 'burlywood' => 0xDEB887FF,
56
+ 'cadetblue' => 0x5F9EA0FF,
57
+ 'chartreuse' => 0x7FFF00FF,
58
+ 'chocolate' => 0xD2691EFF,
59
+ 'coral' => 0xFF7F50FF,
60
+ 'cornflowerblue' => 0x6495EDFF,
61
+ 'cornsilk' => 0xFFF8DCFF,
62
+ 'crimson' => 0xDC143CFF,
63
+ 'cyan' => 0x00FFFFFF,
64
+ 'darkblue' => 0x00008BFF,
65
+ 'darkcyan' => 0x008B8BFF,
66
+ 'darkgoldenrod' => 0xB8860BFF,
67
+ 'darkgray' => 0xA9A9A9FF,
68
+ 'darkgreen' => 0x006400FF,
69
+ 'darkkhaki' => 0xBDB76BFF,
70
+ 'darkmagenta' => 0x8B008BFF,
71
+ 'darkolivegreen' => 0x556B2FFF,
72
+ 'darkorange' => 0xFF8C00FF,
73
+ 'darkorchid' => 0x9932CCFF,
74
+ 'darkred' => 0x8B0000FF,
75
+ 'darksalmon' => 0xE9967AFF,
76
+ 'darkseagreen' => 0x8FBC8FFF,
77
+ 'darkslateblue' => 0x483D8BFF,
78
+ 'darkslategray' => 0x2F4F4FFF,
79
+ 'darkturquoise' => 0x00CED1FF,
80
+ 'darkviolet' => 0x9400D3FF,
81
+ 'deeppink' => 0xFF1493FF,
82
+ 'deepskyblue' => 0x00BFFFFF,
83
+ 'dimgray' => 0x696969FF,
84
+ 'dodgerblue' => 0x1E90FFFF,
85
+ 'firebrick' => 0xB22222FF,
86
+ 'floralwhite' => 0xFFFAF0FF,
87
+ 'forestgreen' => 0x228B22FF,
88
+ 'gainsboro' => 0xDCDCDCFF,
89
+ 'ghostwhite' => 0xF8F8FFFF,
90
+ 'gold' => 0xFFD700FF,
91
+ 'goldenrod' => 0xDAA520FF,
92
+ 'gray' => 0x808080FF,
93
+ 'green' => 0x008000FF,
94
+ 'greenyellow' => 0xADFF2FFF,
95
+ 'honeydew' => 0xF0FFF0FF,
96
+ 'hotpink' => 0xFF69B4FF,
97
+ 'indianred' => 0xCD5C5CFF,
98
+ 'indigo' => 0x4B0082FF,
99
+ 'ivory' => 0xFFFFF0FF,
100
+ 'khaki' => 0xF0E68CFF,
101
+ 'lavender' => 0xE6E6FAFF,
102
+ 'lavenderblush' => 0xFFF0F5FF,
103
+ 'lawngreen' => 0x7CFC00FF,
104
+ 'lemonchiffon' => 0xFFFACDFF,
105
+ 'lightblue' => 0xADD8E6FF,
106
+ 'lightcoral' => 0xF08080FF,
107
+ 'lightcyan' => 0xE0FFFFFF,
108
+ 'lightgoldenrodyellow' => 0xFAFAD2FF,
109
+ 'lightgreen' => 0x90EE90FF,
110
+ 'lightgray' => 0xD3D3D3FF,
111
+ 'lightpink' => 0xFFB6C1FF,
112
+ 'lightsalmon' => 0xFFA07AFF,
113
+ 'lightseagreen' => 0x20B2AAFF,
114
+ 'lightskyblue' => 0x87CEFAFF,
115
+ 'lightslategray' => 0x778899FF,
116
+ 'lightsteelblue' => 0xB0C4DEFF,
117
+ 'lightyellow' => 0xFFFFE0FF,
118
+ 'lime' => 0x00FF00FF,
119
+ 'limegreen' => 0x32CD32FF,
120
+ 'linen' => 0xFAF0E6FF,
121
+ 'magenta' => 0xFF00FFFF,
122
+ 'maroon' => 0x800000FF,
123
+ 'mediumaquamarine' => 0x66CDAAFF,
124
+ 'mediumblue' => 0x0000CDFF,
125
+ 'mediumorchid' => 0xBA55D3FF,
126
+ 'mediumpurple' => 0x9370DBFF,
127
+ 'mediumseagreen' => 0x3CB371FF,
128
+ 'mediumslateblue' => 0x7B68EEFF,
129
+ 'mediumspringgreen' => 0x00FA9AFF,
130
+ 'mediumturquoise' => 0x48D1CCFF,
131
+ 'mediumvioletred' => 0xC71585FF,
132
+ 'midnightblue' => 0x191970FF,
133
+ 'mintcream' => 0xF5FFFAFF,
134
+ 'mistyrose' => 0xFFE4E1FF,
135
+ 'moccasin' => 0xFFE4B5FF,
136
+ 'navajowhite' => 0xFFDEADFF,
137
+ 'navy' => 0x000080FF,
138
+ 'oldlace' => 0xFDF5E6FF,
139
+ 'olive' => 0x808000FF,
140
+ 'olivedrab' => 0x6B8E23FF,
141
+ 'orange' => 0xFFA500FF,
142
+ 'orangered' => 0xFF4500FF,
143
+ 'orchid' => 0xDA70D6FF,
144
+ 'palegoldenrod' => 0xEEE8AAFF,
145
+ 'palegreen' => 0x98FB98FF,
146
+ 'paleturquoise' => 0xAFEEEEFF,
147
+ 'palevioletred' => 0xDB7093FF,
148
+ 'papayawhip' => 0xFFEFD5FF,
149
+ 'peachpuff' => 0xFFDAB9FF,
150
+ 'peru' => 0xCD853FFF,
151
+ 'pink' => 0xFFC0CBFF,
152
+ 'plum' => 0xDDA0DDFF,
153
+ 'powderblue' => 0xB0E0E6FF,
154
+ 'purple' => 0x800080FF,
155
+ 'red' => 0xFF0000FF,
156
+ 'rebeccapurple' => 0x663399FF,
157
+ 'rosybrown' => 0xBC8F8FFF,
158
+ 'royalblue' => 0x4169E1FF,
159
+ 'saddlebrown' => 0x8B4513FF,
160
+ 'salmon' => 0xFA8072FF,
161
+ 'sandybrown' => 0xF4A460FF,
162
+ 'seagreen' => 0x2E8B57FF,
163
+ 'seashell' => 0xFFF5EEFF,
164
+ 'sienna' => 0xA0522DFF,
165
+ 'silver' => 0xC0C0C0FF,
166
+ 'skyblue' => 0x87CEEBFF,
167
+ 'slateblue' => 0x6A5ACDFF,
168
+ 'slategray' => 0x708090FF,
169
+ 'snow' => 0xFFFAFAFF,
170
+ 'springgreen' => 0x00FF7FFF,
171
+ 'steelblue' => 0x4682B4FF,
172
+ 'tan' => 0xD2B48CFF,
173
+ 'teal' => 0x008080FF,
174
+ 'thistle' => 0xD8BFD8FF,
175
+ 'tomato' => 0xFF6347FF,
176
+ 'transparent' => 0x00000000,
177
+ 'turquoise' => 0x40E0D0FF,
178
+ 'violet' => 0xEE82EEFF,
179
+ 'wheat' => 0xF5DEB3FF,
180
+ 'white' => 0xFFFFFFFF,
181
+ 'whitesmoke' => 0xF5F5F5FF,
182
+ 'yellow' => 0xFFFF00FF,
183
+ 'yellowgreen' => 0x9ACD32FF
184
+ }, &method(:int_to_rgba))
185
+
186
+ # A hash from `[red, green, blue, alpha]` value arrays to color names.
187
+ COLOR_NAMES_REVERSE = COLOR_NAMES.invert.freeze
188
+
189
+ # We add the alternate color names after inverting because
190
+ # different ruby implementations and versions vary on the ordering of the result of invert.
191
+ COLOR_NAMES.update(ALTERNATE_COLOR_NAMES).freeze
192
+
193
+ # The user's original representation of the color.
194
+ #
195
+ # @return [String]
196
+ attr_reader :representation
197
+
198
+ # Constructs an RGB or HSL color object,
199
+ # optionally with an alpha channel.
200
+ #
201
+ # RGB values are clipped within 0 and 255.
202
+ # Saturation and lightness values are clipped within 0 and 100.
203
+ # The alpha value is clipped within 0 and 1.
204
+ #
205
+ # @raise [Sass::SyntaxError] if any color value isn't in the specified range
206
+ #
207
+ # @overload initialize(attrs)
208
+ # The attributes are specified as a hash. This hash must contain either
209
+ # `:hue`, `:saturation`, and `:lightness` keys, or `:red`, `:green`, and
210
+ # `:blue` keys. It cannot contain both HSL and RGB keys. It may also
211
+ # optionally contain an `:alpha` key, and a `:representation` key
212
+ # indicating the original representation of the color that the user wrote
213
+ # in their stylesheet.
214
+ #
215
+ # @param attrs [{Symbol => Numeric}] A hash of color attributes to values
216
+ # @raise [ArgumentError] if not enough attributes are specified,
217
+ # or both RGB and HSL attributes are specified
218
+ #
219
+ # @overload initialize(rgba, [representation])
220
+ # The attributes are specified as an array.
221
+ # This overload only supports RGB or RGBA colors.
222
+ #
223
+ # @param rgba [Array<Numeric>] A three- or four-element array
224
+ # of the red, green, blue, and optionally alpha values (respectively)
225
+ # of the color
226
+ # @param representation [String] The original representation of the color
227
+ # that the user wrote in their stylesheet.
228
+ # @raise [ArgumentError] if not enough attributes are specified
229
+ def initialize(attrs, representation = nil, allow_both_rgb_and_hsl = false)
230
+ super(nil)
231
+
232
+ if attrs.is_a?(Array)
233
+ unless (3..4).include?(attrs.size)
234
+ raise ArgumentError.new("Color.new(array) expects a three- or four-element array")
235
+ end
236
+
237
+ red, green, blue = attrs[0...3].map {|c| Sass::Util.round(c)}
238
+ @attrs = {:red => red, :green => green, :blue => blue}
239
+ @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
240
+ @representation = representation
241
+ else
242
+ attrs = attrs.reject {|_k, v| v.nil?}
243
+ hsl = [:hue, :saturation, :lightness] & attrs.keys
244
+ rgb = [:red, :green, :blue] & attrs.keys
245
+ if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty?
246
+ raise ArgumentError.new("Color.new(hash) may not have both HSL and RGB keys specified")
247
+ elsif hsl.empty? && rgb.empty?
248
+ raise ArgumentError.new("Color.new(hash) must have either HSL or RGB keys specified")
249
+ elsif !hsl.empty? && hsl.size != 3
250
+ raise ArgumentError.new("Color.new(hash) must have all three HSL values specified")
251
+ elsif !rgb.empty? && rgb.size != 3
252
+ raise ArgumentError.new("Color.new(hash) must have all three RGB values specified")
253
+ end
254
+
255
+ @attrs = attrs
256
+ @attrs[:hue] %= 360 if @attrs[:hue]
257
+ @attrs[:alpha] ||= 1
258
+ @representation = @attrs.delete(:representation)
259
+ end
260
+
261
+ [:red, :green, :blue].each do |k|
262
+ next if @attrs[k].nil?
263
+ @attrs[k] = Sass::Util.restrict(Sass::Util.round(@attrs[k]), 0..255)
264
+ end
265
+
266
+ [:saturation, :lightness].each do |k|
267
+ next if @attrs[k].nil?
268
+ @attrs[k] = Sass::Util.restrict(@attrs[k], 0..100)
269
+ end
270
+
271
+ @attrs[:alpha] = Sass::Util.restrict(@attrs[:alpha], 0..1)
272
+ end
273
+
274
+ # Create a new color from a valid CSS hex string.
275
+ #
276
+ # The leading hash is optional.
277
+ #
278
+ # @return [Color]
279
+ def self.from_hex(hex_string, alpha = nil)
280
+ unless hex_string =~ /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i ||
281
+ hex_string =~ /^#?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/i
282
+ raise ArgumentError.new("#{hex_string.inspect} is not a valid hex color.")
283
+ end
284
+ red = $1.ljust(2, $1).to_i(16)
285
+ green = $2.ljust(2, $2).to_i(16)
286
+ blue = $3.ljust(2, $3).to_i(16)
287
+ alpha = $4.ljust(2, $4).to_i(16).to_f / 0xff if $4
288
+
289
+ hex_string = "##{hex_string}" unless hex_string[0] == ?#
290
+ attrs = {:red => red, :green => green, :blue => blue, :representation => hex_string}
291
+ attrs[:alpha] = alpha if alpha
292
+ new(attrs)
293
+ end
294
+
295
+ # The red component of the color.
296
+ #
297
+ # @return [Integer]
298
+ def red
299
+ hsl_to_rgb!
300
+ @attrs[:red]
301
+ end
302
+
303
+ # The green component of the color.
304
+ #
305
+ # @return [Integer]
306
+ def green
307
+ hsl_to_rgb!
308
+ @attrs[:green]
309
+ end
310
+
311
+ # The blue component of the color.
312
+ #
313
+ # @return [Integer]
314
+ def blue
315
+ hsl_to_rgb!
316
+ @attrs[:blue]
317
+ end
318
+
319
+ # The hue component of the color.
320
+ #
321
+ # @return [Numeric]
322
+ def hue
323
+ rgb_to_hsl!
324
+ @attrs[:hue]
325
+ end
326
+
327
+ # The saturation component of the color.
328
+ #
329
+ # @return [Numeric]
330
+ def saturation
331
+ rgb_to_hsl!
332
+ @attrs[:saturation]
333
+ end
334
+
335
+ # The lightness component of the color.
336
+ #
337
+ # @return [Numeric]
338
+ def lightness
339
+ rgb_to_hsl!
340
+ @attrs[:lightness]
341
+ end
342
+
343
+ # The alpha channel (opacity) of the color.
344
+ # This is 1 unless otherwise defined.
345
+ #
346
+ # @return [Integer]
347
+ def alpha
348
+ @attrs[:alpha].to_f
349
+ end
350
+
351
+ # Returns whether this color object is translucent;
352
+ # that is, whether the alpha channel is non-1.
353
+ #
354
+ # @return [Boolean]
355
+ def alpha?
356
+ alpha < 1
357
+ end
358
+
359
+ # Returns the red, green, and blue components of the color.
360
+ #
361
+ # @return [Array<Integer>] A frozen three-element array of the red, green, and blue
362
+ # values (respectively) of the color
363
+ def rgb
364
+ [red, green, blue].freeze
365
+ end
366
+
367
+ # Returns the red, green, blue, and alpha components of the color.
368
+ #
369
+ # @return [Array<Integer>] A frozen four-element array of the red, green,
370
+ # blue, and alpha values (respectively) of the color
371
+ def rgba
372
+ [red, green, blue, alpha].freeze
373
+ end
374
+
375
+ # Returns the hue, saturation, and lightness components of the color.
376
+ #
377
+ # @return [Array<Integer>] A frozen three-element array of the
378
+ # hue, saturation, and lightness values (respectively) of the color
379
+ def hsl
380
+ [hue, saturation, lightness].freeze
381
+ end
382
+
383
+ # Returns the hue, saturation, lightness, and alpha components of the color.
384
+ #
385
+ # @return [Array<Integer>] A frozen four-element array of the hue,
386
+ # saturation, lightness, and alpha values (respectively) of the color
387
+ def hsla
388
+ [hue, saturation, lightness, alpha].freeze
389
+ end
390
+
391
+ # The SassScript `==` operation.
392
+ # **Note that this returns a {Sass::Script::Value::Bool} object,
393
+ # not a Ruby boolean**.
394
+ #
395
+ # @param other [Value] The right-hand side of the operator
396
+ # @return [Bool] True if this value is the same as the other,
397
+ # false otherwise
398
+ def eq(other)
399
+ Sass::Script::Value::Bool.new(
400
+ other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha)
401
+ end
402
+
403
+ def hash
404
+ [rgb, alpha].hash
405
+ end
406
+
407
+ # Returns a copy of this color with one or more channels changed.
408
+ # RGB or HSL colors may be changed, but not both at once.
409
+ #
410
+ # For example:
411
+ #
412
+ # Color.new([10, 20, 30]).with(:blue => 40)
413
+ # #=> rgb(10, 40, 30)
414
+ # Color.new([126, 126, 126]).with(:red => 0, :green => 255)
415
+ # #=> rgb(0, 255, 126)
416
+ # Color.new([255, 0, 127]).with(:saturation => 60)
417
+ # #=> rgb(204, 51, 127)
418
+ # Color.new([1, 2, 3]).with(:alpha => 0.4)
419
+ # #=> rgba(1, 2, 3, 0.4)
420
+ #
421
+ # @param attrs [{Symbol => Numeric}]
422
+ # A map of channel names (`:red`, `:green`, `:blue`,
423
+ # `:hue`, `:saturation`, `:lightness`, or `:alpha`) to values
424
+ # @return [Color] The new Color object
425
+ # @raise [ArgumentError] if both RGB and HSL keys are specified
426
+ def with(attrs)
427
+ attrs = attrs.reject {|_k, v| v.nil?}
428
+ hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty?
429
+ rgb = !([:red, :green, :blue] & attrs.keys).empty?
430
+ if hsl && rgb
431
+ raise ArgumentError.new("Cannot specify HSL and RGB values for a color at the same time")
432
+ end
433
+
434
+ if hsl
435
+ [:hue, :saturation, :lightness].each {|k| attrs[k] ||= send(k)}
436
+ elsif rgb
437
+ [:red, :green, :blue].each {|k| attrs[k] ||= send(k)}
438
+ else
439
+ # If we're just changing the alpha channel,
440
+ # keep all the HSL/RGB stuff we've calculated
441
+ attrs = @attrs.merge(attrs)
442
+ end
443
+ attrs[:alpha] ||= alpha
444
+
445
+ Color.new(attrs, nil, :allow_both_rgb_and_hsl)
446
+ end
447
+
448
+ # The SassScript `+` operation.
449
+ # Its functionality depends on the type of its argument:
450
+ #
451
+ # {Number}
452
+ # : Adds the number to each of the RGB color channels.
453
+ #
454
+ # {Color}
455
+ # : Adds each of the RGB color channels together.
456
+ #
457
+ # {Value}
458
+ # : See {Value::Base#plus}.
459
+ #
460
+ # @param other [Value] The right-hand side of the operator
461
+ # @return [Color] The resulting color
462
+ # @raise [Sass::SyntaxError] if `other` is a number with units
463
+ def plus(other)
464
+ if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
465
+ piecewise(other, :+)
466
+ else
467
+ super
468
+ end
469
+ end
470
+
471
+ # The SassScript `-` operation.
472
+ # Its functionality depends on the type of its argument:
473
+ #
474
+ # {Number}
475
+ # : Subtracts the number from each of the RGB color channels.
476
+ #
477
+ # {Color}
478
+ # : Subtracts each of the other color's RGB color channels from this color's.
479
+ #
480
+ # {Value}
481
+ # : See {Value::Base#minus}.
482
+ #
483
+ # @param other [Value] The right-hand side of the operator
484
+ # @return [Color] The resulting color
485
+ # @raise [Sass::SyntaxError] if `other` is a number with units
486
+ def minus(other)
487
+ if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
488
+ piecewise(other, :-)
489
+ else
490
+ super
491
+ end
492
+ end
493
+
494
+ # The SassScript `*` operation.
495
+ # Its functionality depends on the type of its argument:
496
+ #
497
+ # {Number}
498
+ # : Multiplies the number by each of the RGB color channels.
499
+ #
500
+ # {Color}
501
+ # : Multiplies each of the RGB color channels together.
502
+ #
503
+ # @param other [Number, Color] The right-hand side of the operator
504
+ # @return [Color] The resulting color
505
+ # @raise [Sass::SyntaxError] if `other` is a number with units
506
+ def times(other)
507
+ if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
508
+ piecewise(other, :*)
509
+ else
510
+ raise NoMethodError.new(nil, :times)
511
+ end
512
+ end
513
+
514
+ # The SassScript `/` operation.
515
+ # Its functionality depends on the type of its argument:
516
+ #
517
+ # {Number}
518
+ # : Divides each of the RGB color channels by the number.
519
+ #
520
+ # {Color}
521
+ # : Divides each of this color's RGB color channels by the other color's.
522
+ #
523
+ # {Value}
524
+ # : See {Value::Base#div}.
525
+ #
526
+ # @param other [Value] The right-hand side of the operator
527
+ # @return [Color] The resulting color
528
+ # @raise [Sass::SyntaxError] if `other` is a number with units
529
+ def div(other)
530
+ if other.is_a?(Sass::Script::Value::Number) ||
531
+ other.is_a?(Sass::Script::Value::Color)
532
+ piecewise(other, :/)
533
+ else
534
+ super
535
+ end
536
+ end
537
+
538
+ # The SassScript `%` operation.
539
+ # Its functionality depends on the type of its argument:
540
+ #
541
+ # {Number}
542
+ # : Takes each of the RGB color channels module the number.
543
+ #
544
+ # {Color}
545
+ # : Takes each of this color's RGB color channels modulo the other color's.
546
+ #
547
+ # @param other [Number, Color] The right-hand side of the operator
548
+ # @return [Color] The resulting color
549
+ # @raise [Sass::SyntaxError] if `other` is a number with units
550
+ def mod(other)
551
+ if other.is_a?(Sass::Script::Value::Number) ||
552
+ other.is_a?(Sass::Script::Value::Color)
553
+ piecewise(other, :%)
554
+ else
555
+ raise NoMethodError.new(nil, :mod)
556
+ end
557
+ end
558
+
559
+ # Returns a string representation of the color.
560
+ # This is usually the color's hex value,
561
+ # but if the color has a name that's used instead.
562
+ #
563
+ # @return [String] The string representation
564
+ def to_s(opts = {})
565
+ return smallest if options[:style] == :compressed
566
+ return representation if representation
567
+
568
+ # IE10 doesn't properly support the color name "transparent", so we emit
569
+ # generated transparent colors as rgba(0, 0, 0, 0) in favor of that. See
570
+ # #1782.
571
+ return rgba_str if Number.basically_equal?(alpha, 0)
572
+ return name if name
573
+ alpha? ? rgba_str : hex_str
574
+ end
575
+ alias_method :to_sass, :to_s
576
+
577
+ # Returns a string representation of the color.
578
+ #
579
+ # @return [String] The hex value
580
+ def inspect
581
+ alpha? ? rgba_str : hex_str
582
+ end
583
+
584
+ # Returns the color's name, if it has one.
585
+ #
586
+ # @return [String, nil]
587
+ def name
588
+ COLOR_NAMES_REVERSE[rgba]
589
+ end
590
+
591
+ private
592
+
593
+ def smallest
594
+ small_explicit_str = alpha? ? rgba_str : hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
595
+ [representation, COLOR_NAMES_REVERSE[rgba], small_explicit_str].
596
+ compact.min_by {|str| str.size}
597
+ end
598
+
599
+ def rgba_str
600
+ split = options[:style] == :compressed ? ',' : ', '
601
+ "rgba(#{rgb.join(split)}#{split}#{Number.round(alpha)})"
602
+ end
603
+
604
+ def hex_str
605
+ red, green, blue = rgb.map {|num| num.to_s(16).rjust(2, '0')}
606
+ "##{red}#{green}#{blue}"
607
+ end
608
+
609
+ def operation_name(operation)
610
+ case operation
611
+ when :+
612
+ "add"
613
+ when :-
614
+ "subtract"
615
+ when :*
616
+ "multiply"
617
+ when :/
618
+ "divide"
619
+ when :%
620
+ "modulo"
621
+ end
622
+ end
623
+
624
+ def piecewise(other, operation)
625
+ other_num = other.is_a? Number
626
+ if other_num && !other.unitless?
627
+ raise Sass::SyntaxError.new(
628
+ "Cannot #{operation_name(operation)} a number with units (#{other}) to a color (#{self})."
629
+ )
630
+ end
631
+
632
+ result = []
633
+ (0...3).each do |i|
634
+ res = rgb[i].to_f.send(operation, other_num ? other.value : other.rgb[i])
635
+ result[i] = [[res, 255].min, 0].max
636
+ end
637
+
638
+ if !other_num && other.alpha != alpha
639
+ raise Sass::SyntaxError.new("Alpha channels must be equal: #{self} #{operation} #{other}")
640
+ end
641
+
642
+ with(:red => result[0], :green => result[1], :blue => result[2])
643
+ end
644
+
645
+ def hsl_to_rgb!
646
+ return if @attrs[:red] && @attrs[:blue] && @attrs[:green]
647
+
648
+ h = @attrs[:hue] / 360.0
649
+ s = @attrs[:saturation] / 100.0
650
+ l = @attrs[:lightness] / 100.0
651
+
652
+ # Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.
653
+ m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
654
+ m1 = l * 2 - m2
655
+ @attrs[:red], @attrs[:green], @attrs[:blue] = [
656
+ hue_to_rgb(m1, m2, h + 1.0 / 3),
657
+ hue_to_rgb(m1, m2, h),
658
+ hue_to_rgb(m1, m2, h - 1.0 / 3)
659
+ ].map {|c| Sass::Util.round(c * 0xff)}
660
+ end
661
+
662
+ def hue_to_rgb(m1, m2, h)
663
+ h += 1 if h < 0
664
+ h -= 1 if h > 1
665
+ return m1 + (m2 - m1) * h * 6 if h * 6 < 1
666
+ return m2 if h * 2 < 1
667
+ return m1 + (m2 - m1) * (2.0 / 3 - h) * 6 if h * 3 < 2
668
+ m1
669
+ end
670
+
671
+ def rgb_to_hsl!
672
+ return if @attrs[:hue] && @attrs[:saturation] && @attrs[:lightness]
673
+ r, g, b = [:red, :green, :blue].map {|k| @attrs[k] / 255.0}
674
+
675
+ # Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
676
+ max = [r, g, b].max
677
+ min = [r, g, b].min
678
+ d = max - min
679
+
680
+ h =
681
+ case max
682
+ when min; 0
683
+ when r; 60 * (g - b) / d
684
+ when g; 60 * (b - r) / d + 120
685
+ when b; 60 * (r - g) / d + 240
686
+ end
687
+
688
+ l = (max + min) / 2.0
689
+
690
+ s =
691
+ if max == min
692
+ 0
693
+ elsif l < 0.5
694
+ d / (2 * l)
695
+ else
696
+ d / (2 - 2 * l)
697
+ end
698
+
699
+ @attrs[:hue] = h % 360
700
+ @attrs[:saturation] = s * 100
701
+ @attrs[:lightness] = l * 100
702
+ end
703
+ end
704
+ end