sass-embedded 1.77.5 → 1.100.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +18 -17
  3. data/README.md +1 -1
  4. data/exe/sass +2 -10
  5. data/ext/sass/Rakefile +162 -385
  6. data/ext/sass/file_utils.rb +75 -0
  7. data/ext/sass/package.json +1 -1
  8. data/ext/sass/platform.rb +44 -0
  9. data/ext/sass/sass-1.100.0.tgz +0 -0
  10. data/ext/sass/sass_config.rb +183 -0
  11. data/ext/sass/utils.rb +39 -0
  12. data/lib/sass/compiler/channel.rb +6 -4
  13. data/lib/sass/compiler/connection.rb +17 -21
  14. data/lib/sass/compiler/dispatcher.rb +27 -1
  15. data/lib/sass/compiler/host/function_registry.rb +6 -6
  16. data/lib/sass/compiler/host/importer_registry.rb +17 -11
  17. data/lib/sass/compiler/host/logger_registry.rb +17 -20
  18. data/lib/sass/compiler/host/protofier.rb +59 -73
  19. data/lib/sass/compiler/host/struct.rb +36 -0
  20. data/lib/sass/compiler/host.rb +2 -2
  21. data/lib/sass/compiler.rb +4 -3
  22. data/lib/sass/elf.rb +282 -172
  23. data/lib/sass/embedded/version.rb +1 -1
  24. data/lib/sass/embedded.rb +2 -41
  25. data/lib/sass/embedded_protocol.rb +1 -1
  26. data/{ext → lib}/sass/embedded_sass_pb.rb +3 -5
  27. data/lib/sass/exception.rb +7 -2
  28. data/lib/sass/fork_tracker.rb +36 -28
  29. data/lib/sass/gem_package_importer.rb +19 -0
  30. data/lib/sass/serializer.rb +5 -11
  31. data/lib/sass/value/argument_list.rb +0 -8
  32. data/lib/sass/value/color/channel.rb +79 -0
  33. data/lib/sass/value/color/conversions.rb +473 -0
  34. data/lib/sass/value/color/gamut_map_method/clip.rb +45 -0
  35. data/lib/sass/value/color/gamut_map_method/local_minde.rb +94 -0
  36. data/lib/sass/value/color/gamut_map_method.rb +45 -0
  37. data/lib/sass/value/color/interpolation_method.rb +51 -0
  38. data/lib/sass/value/color/space/a98_rgb.rb +57 -0
  39. data/lib/sass/value/color/space/display_p3.rb +72 -0
  40. data/lib/sass/value/color/space/display_p3_linear.rb +72 -0
  41. data/lib/sass/value/color/space/hsl.rb +65 -0
  42. data/lib/sass/value/color/space/hwb.rb +70 -0
  43. data/lib/sass/value/color/space/lab.rb +77 -0
  44. data/lib/sass/value/color/space/lch.rb +53 -0
  45. data/lib/sass/value/color/space/lms.rb +129 -0
  46. data/lib/sass/value/color/space/oklab.rb +66 -0
  47. data/lib/sass/value/color/space/oklch.rb +54 -0
  48. data/lib/sass/value/color/space/prophoto_rgb.rb +59 -0
  49. data/lib/sass/value/color/space/rec2020.rb +69 -0
  50. data/lib/sass/value/color/space/rgb.rb +52 -0
  51. data/lib/sass/value/color/space/srgb.rb +140 -0
  52. data/lib/sass/value/color/space/srgb_linear.rb +72 -0
  53. data/lib/sass/value/color/space/utils.rb +86 -0
  54. data/lib/sass/value/color/space/xyz_d50.rb +100 -0
  55. data/lib/sass/value/color/space/xyz_d65.rb +57 -0
  56. data/lib/sass/value/color/space.rb +201 -0
  57. data/lib/sass/value/color.rb +539 -162
  58. data/lib/sass/value/function.rb +9 -6
  59. data/lib/sass/value/fuzzy_math.rb +34 -30
  60. data/lib/sass/value/mixin.rb +5 -2
  61. data/lib/sass/value/null.rb +1 -1
  62. data/lib/sass/value/number/unit.rb +5 -4
  63. data/lib/sass/value/number.rb +15 -17
  64. data/lib/sass/value/string.rb +1 -1
  65. data/lib/sass/value.rb +1 -1
  66. metadata +42 -21
  67. data/ext/sass/expand-archive.ps1 +0 -1
  68. data/lib/sass/compiler/host/structifier.rb +0 -37
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'color/channel'
4
+ require_relative 'color/conversions'
5
+ require_relative 'color/gamut_map_method'
6
+ require_relative 'color/interpolation_method'
7
+ require_relative 'color/space'
8
+
3
9
  module Sass
4
10
  module Value
5
11
  # Sass's color type.
@@ -18,90 +24,150 @@ module Sass
18
24
  # @param lightness [Numeric]
19
25
  # @param whiteness [Numeric]
20
26
  # @param blackness [Numeric]
27
+ # @param a [Numeric]
28
+ # @param b [Numeric]
29
+ # @param chroma [Numeric]
30
+ # @param x [Numeric]
31
+ # @param y [Numeric]
32
+ # @param z [Numeric]
21
33
  # @param alpha [Numeric]
22
- def initialize(red: nil,
23
- green: nil,
24
- blue: nil,
25
- hue: nil,
26
- saturation: nil,
27
- lightness: nil,
28
- whiteness: nil,
29
- blackness: nil,
30
- alpha: 1)
31
- @alpha = alpha.nil? ? 1 : FuzzyMath.assert_between(alpha, 0, 1, 'alpha')
32
- if red && green && blue
33
- @red = FuzzyMath.assert_between(FuzzyMath.round(red), 0, 255, 'red')
34
- @green = FuzzyMath.assert_between(FuzzyMath.round(green), 0, 255, 'green')
35
- @blue = FuzzyMath.assert_between(FuzzyMath.round(blue), 0, 255, 'blue')
36
- elsif hue && saturation && lightness
37
- @hue = hue % 360
38
- @saturation = FuzzyMath.assert_between(saturation, 0, 100, 'saturation')
39
- @lightness = FuzzyMath.assert_between(lightness, 0, 100, 'lightness')
40
- elsif hue && whiteness && blackness
41
- @hue = hue % 360
42
- @whiteness = FuzzyMath.assert_between(whiteness, 0, 100, 'whiteness')
43
- @blackness = FuzzyMath.assert_between(blackness, 0, 100, 'blackness')
44
- hwb_to_rgb
45
- @whiteness = @blackness = nil
46
- else
47
- raise Sass::ScriptError, 'Invalid Color'
34
+ # @param space [::String]
35
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'rgb')
36
+ # @overload initialize(hue: nil, saturation: nil, lightness: nil, alpha: nil, space: 'hsl')
37
+ # @overload initialize(hue: nil, whiteness: nil, blackness: nil, alpha: nil, space: 'hwb')
38
+ # @overload initialize(lightness: nil, a: nil, b: nil, alpha: nil, space: 'lab')
39
+ # @overload initialize(lightness: nil, a: nil, b: nil, alpha: nil, space: 'oklab')
40
+ # @overload initialize(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'lch')
41
+ # @overload initialize(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'oklch')
42
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'a98-rgb')
43
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'display-p3')
44
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'display-p3-linear')
45
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'prophoto-rgb')
46
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'rec2020')
47
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb')
48
+ # @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb-linear')
49
+ # @overload initialize(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz')
50
+ # @overload initialize(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d50')
51
+ # @overload initialize(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d65')
52
+ def initialize(**options)
53
+ unless options.key?(:space)
54
+ options[:space] = case options
55
+ in { red: _, green: _, blue: _ }
56
+ 'rgb'
57
+ in { hue: _, saturation: _, lightness: _ }
58
+ 'hsl'
59
+ in { hue: _, whiteness: _, blackness: _ }
60
+ 'hwb'
61
+ else
62
+ raise Sass::ScriptError.new('No color space found', 'space')
63
+ end
48
64
  end
49
- end
50
65
 
51
- # @return [Integer]
52
- def red
53
- hsl_to_rgb unless defined?(@red)
66
+ space = Space.from_name(options[:space])
54
67
 
55
- @red
68
+ keys = _assert_options(space, options)
69
+
70
+ _initialize_for_space_internal(space,
71
+ options[keys[0]],
72
+ options[keys[1]],
73
+ options[keys[2]],
74
+ options.fetch(:alpha, 1))
56
75
  end
57
76
 
58
- # @return [Integer]
59
- def green
60
- hsl_to_rgb unless defined?(@green)
77
+ # @return [::String]
78
+ def space
79
+ _space.name
80
+ end
61
81
 
62
- @green
82
+ # @param space [::String]
83
+ # @return [Color]
84
+ def to_space(space)
85
+ _to_space(Space.from_name(space))
63
86
  end
64
87
 
65
- # @return [Integer]
66
- def blue
67
- hsl_to_rgb unless defined?(@blue)
88
+ # @param space [::String]
89
+ # @return [::Boolean]
90
+ def in_gamut?(space = nil)
91
+ return to_space(space)._in_gamut? unless space.nil?
68
92
 
69
- @blue
93
+ _in_gamut?
70
94
  end
71
95
 
72
- # @return [Numeric]
73
- def hue
74
- rgb_to_hsl unless defined?(@hue)
96
+ # @param method [::String]
97
+ # @param space [::String]
98
+ # @return [Color]
99
+ def to_gamut(method:, space: nil)
100
+ return to_space(space).to_gamut(method:)._to_space(_space) unless space.nil?
75
101
 
76
- @hue
102
+ _to_gamut(GamutMapMethod.from_name(method, 'method'))
77
103
  end
78
104
 
79
- # @return [Numeric]
80
- def saturation
81
- rgb_to_hsl unless defined?(@saturation)
105
+ # @return [Array<Numeric, nil>]
106
+ def channels_or_nil
107
+ [channel0_or_nil, channel1_or_nil, channel2_or_nil].freeze
108
+ end
82
109
 
83
- @saturation
110
+ # @return [Array<Numeric>]
111
+ def channels
112
+ [channel0, channel1, channel2].freeze
84
113
  end
85
114
 
115
+ # @param channel [::String]
116
+ # @param space [::String]
86
117
  # @return [Numeric]
87
- def lightness
88
- rgb_to_hsl unless defined?(@lightness)
118
+ def channel(channel, space: nil)
119
+ return to_space(space).channel(channel) unless space.nil?
120
+
121
+ channels = _space.channels
122
+ return channel0 if channel == channels[0].name
123
+ return channel1 if channel == channels[1].name
124
+ return channel2 if channel == channels[2].name
125
+ return alpha if channel == 'alpha'
89
126
 
90
- @lightness
127
+ raise Sass::ScriptError.new("Color #{self} doesn't have a channel named \"#{channel}\".", channel)
91
128
  end
92
129
 
93
- # @return [Numeric]
94
- def whiteness
95
- @whiteness ||= Rational([red, green, blue].min, 255) * 100
130
+ # @param channel [::String]
131
+ # @return [::Boolean]
132
+ def channel_missing?(channel)
133
+ channels = _space.channels
134
+ return channel0_missing? if channel == channels[0].name
135
+ return channel1_missing? if channel == channels[1].name
136
+ return channel2_missing? if channel == channels[2].name
137
+ return alpha_missing? if channel == 'alpha'
138
+
139
+ raise Sass::ScriptError.new("Color #{self} doesn't have a channel named \"#{channel}\".", channel)
96
140
  end
97
141
 
98
- # @return [Numeric]
99
- def blackness
100
- @blackness ||= 100 - (Rational([red, green, blue].max, 255) * 100)
142
+ # @param channel [::String]
143
+ # @param space [::String]
144
+ # @return [::Boolean]
145
+ def channel_powerless?(channel, space: nil)
146
+ return to_space(space).channel_powerless?(channel) unless space.nil?
147
+
148
+ channels = _space.channels
149
+ return channel0_powerless? if channel == channels[0].name
150
+ return channel1_powerless? if channel == channels[1].name
151
+ return channel2_powerless? if channel == channels[2].name
152
+ return false if channel == 'alpha'
153
+
154
+ raise Sass::ScriptError.new("Color #{self} doesn't have a channel named \"#{channel}\".", channel)
101
155
  end
102
156
 
103
- # @return [Numeric]
104
- attr_reader :alpha
157
+ # @param other [Color]
158
+ # @param method [::String]
159
+ # @param weight [Numeric]
160
+ # @return [Color]
161
+ def interpolate(other, method: nil, weight: nil)
162
+ interpolation_method = if !method.nil?
163
+ InterpolationMethod.new(_space, HueInterpolationMethod.from_name(method))
164
+ elsif !_space.polar?
165
+ InterpolationMethod.new(_space)
166
+ else
167
+ InterpolationMethod.new(_space, :shorter)
168
+ end
169
+ _interpolate(other, interpolation_method, weight:)
170
+ end
105
171
 
106
172
  # @param red [Numeric]
107
173
  # @param green [Numeric]
@@ -111,52 +177,176 @@ module Sass
111
177
  # @param lightness [Numeric]
112
178
  # @param whiteness [Numeric]
113
179
  # @param blackness [Numeric]
180
+ # @param a [Numeric]
181
+ # @param b [Numeric]
182
+ # @param chroma [Numeric]
183
+ # @param x [Numeric]
184
+ # @param y [Numeric]
185
+ # @param z [Numeric]
114
186
  # @param alpha [Numeric]
187
+ # @param space [::String]
188
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'rgb')
189
+ # @overload change(hue: nil, saturation: nil, lightness: nil, alpha: nil, space: 'hsl')
190
+ # @overload change(hue: nil, whiteness: nil, blackness: nil, alpha: nil, space: 'hwb')
191
+ # @overload change(lightness: nil, a: nil, b: nil, alpha: nil, space: 'lab')
192
+ # @overload change(lightness: nil, a: nil, b: nil, alpha: nil, space: 'oklab')
193
+ # @overload change(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'lch')
194
+ # @overload change(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'oklch')
195
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'a98-rgb')
196
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'display-p3')
197
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'display-p3-linear')
198
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'prophoto-rgb')
199
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'rec2020')
200
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb')
201
+ # @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb-linear')
202
+ # @overload change(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz')
203
+ # @overload change(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d50')
204
+ # @overload change(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d65')
115
205
  # @return [Color]
116
- def change(red: nil,
117
- green: nil,
118
- blue: nil,
119
- hue: nil,
120
- saturation: nil,
121
- lightness: nil,
122
- whiteness: nil,
123
- blackness: nil,
124
- alpha: nil)
125
- if whiteness || blackness
126
- Sass::Value::Color.new(hue: hue || self.hue,
127
- whiteness: whiteness || self.whiteness,
128
- blackness: blackness || self.blackness,
129
- alpha: alpha || self.alpha)
130
- elsif hue || saturation || lightness
131
- Sass::Value::Color.new(hue: hue || self.hue,
132
- saturation: saturation || self.saturation,
133
- lightness: lightness || self.lightness,
134
- alpha: alpha || self.alpha)
135
- elsif red || green || blue
136
- Sass::Value::Color.new(red: red ? FuzzyMath.round(red) : self.red,
137
- green: green ? FuzzyMath.round(green) : self.green,
138
- blue: blue ? FuzzyMath.round(blue) : self.blue,
139
- alpha: alpha || self.alpha)
140
- else
141
- dup.instance_eval do
142
- @alpha = FuzzyMath.assert_between(alpha, 0, 1, 'alpha')
143
- self
206
+ def change(**options)
207
+ space_set_explictly = !options[:space].nil?
208
+ space = space_set_explictly ? Space.from_name(options[:space]) : _space
209
+
210
+ if legacy? && !space_set_explictly
211
+ case options
212
+ in { whiteness: _ } | { blackness: _ }
213
+ space = Space::HWB
214
+ in { saturation: _ } | { lightness: _ }
215
+ space = Space::HSL
216
+ in { hue: _ }
217
+ space = if _space == Space::HWB
218
+ Space::HWB
219
+ else
220
+ Space::HSL
221
+ end
222
+ in { red: _ } | { blue: _ } | { green: _ }
223
+ space = Space::RGB
224
+ else
225
+ end
226
+
227
+ if space != _space
228
+ # deprecated
144
229
  end
145
230
  end
231
+
232
+ keys = _assert_options(space, options)
233
+
234
+ color = _to_space(space)
235
+
236
+ changed_color = if space_set_explictly
237
+ Color.send(:for_space_internal,
238
+ space,
239
+ options.fetch(keys[0], color.channel0_or_nil),
240
+ options.fetch(keys[1], color.channel1_or_nil),
241
+ options.fetch(keys[2], color.channel2_or_nil),
242
+ options.fetch(:alpha, color.alpha_or_nil))
243
+ else
244
+ changed_channel0_or_nil = options[keys[0]]
245
+ changed_channel1_or_nil = options[keys[1]]
246
+ changed_channel2_or_nil = options[keys[2]]
247
+ changed_alpha_or_nil = options[:alpha]
248
+ Color.send(:for_space_internal,
249
+ space,
250
+ changed_channel0_or_nil.nil? ? color.channel0_or_nil : changed_channel0_or_nil,
251
+ changed_channel1_or_nil.nil? ? color.channel1_or_nil : changed_channel1_or_nil,
252
+ changed_channel2_or_nil.nil? ? color.channel2_or_nil : changed_channel2_or_nil,
253
+ changed_alpha_or_nil.nil? ? color.alpha_or_nil : changed_alpha_or_nil)
254
+ end
255
+
256
+ changed_color._to_space(_space)
257
+ end
258
+
259
+ # @return [Numeric]
260
+ def alpha
261
+ @alpha_or_nil.nil? ? 0 : @alpha_or_nil
262
+ end
263
+
264
+ # @return [::Boolean]
265
+ def legacy?
266
+ _space.legacy?
267
+ end
268
+
269
+ # @deprecated
270
+ # @return [Numeric]
271
+ def red
272
+ _to_space(Space::RGB).channel('red').round
273
+ end
274
+
275
+ # @deprecated
276
+ # @return [Numeric]
277
+ def green
278
+ _to_space(Space::RGB).channel('green').round
279
+ end
280
+
281
+ # @deprecated
282
+ # @return [Numeric]
283
+ def blue
284
+ _to_space(Space::RGB).channel('blue').round
285
+ end
286
+
287
+ # @deprecated
288
+ # @return [Numeric]
289
+ def hue
290
+ _to_space(Space::HSL).channel('hue')
291
+ end
292
+
293
+ # @deprecated
294
+ # @return [Numeric]
295
+ def saturation
296
+ _to_space(Space::HSL).channel('saturation')
297
+ end
298
+
299
+ # @deprecated
300
+ # @return [Numeric]
301
+ def lightness
302
+ _to_space(Space::HSL).channel('lightness')
303
+ end
304
+
305
+ # @deprecated
306
+ # @return [Numeric]
307
+ def whiteness
308
+ _to_space(Space::HWB).channel('whiteness')
309
+ end
310
+
311
+ # @deprecated
312
+ # @return [Numeric]
313
+ def blackness
314
+ _to_space(Space::HWB).channel('blackness')
146
315
  end
147
316
 
148
317
  # @return [::Boolean]
149
318
  def ==(other)
150
- other.is_a?(Sass::Value::Color) &&
151
- other.red == red &&
152
- other.green == green &&
153
- other.blue == blue &&
154
- other.alpha == alpha
319
+ return false unless other.is_a?(Sass::Value::Color)
320
+
321
+ if legacy?
322
+ return false unless other.legacy?
323
+ return false unless FuzzyMath.equals_nilable?(other.alpha_or_nil, alpha_or_nil)
324
+
325
+ if _space == other._space
326
+ FuzzyMath.equals_nilable?(other.channel0_or_nil, channel0_or_nil) &&
327
+ FuzzyMath.equals_nilable?(other.channel1_or_nil, channel1_or_nil) &&
328
+ FuzzyMath.equals_nilable?(other.channel2_or_nil, channel2_or_nil)
329
+ else
330
+ _to_space(Space::RGB) == other._to_space(Space::RGB)
331
+ end
332
+ else
333
+ other._space == _space &&
334
+ FuzzyMath.equals_nilable?(other.channel0_or_nil, channel0_or_nil) &&
335
+ FuzzyMath.equals_nilable?(other.channel1_or_nil, channel1_or_nil) &&
336
+ FuzzyMath.equals_nilable?(other.channel2_or_nil, channel2_or_nil) &&
337
+ FuzzyMath.equals_nilable?(other.alpha_or_nil, alpha_or_nil)
338
+ end
155
339
  end
156
340
 
157
341
  # @return [Integer]
158
342
  def hash
159
- @hash ||= [red, green, blue, alpha].hash
343
+ @hash ||= [
344
+ _space.name,
345
+ FuzzyMath._hash(channel0_or_nil),
346
+ FuzzyMath._hash(channel1_or_nil),
347
+ FuzzyMath._hash(channel2_or_nil),
348
+ FuzzyMath._hash(alpha_or_nil)
349
+ ].hash
160
350
  end
161
351
 
162
352
  # @return [Color]
@@ -164,89 +354,276 @@ module Sass
164
354
  self
165
355
  end
166
356
 
357
+ protected
358
+
359
+ attr_reader :channel0_or_nil, :channel1_or_nil, :channel2_or_nil, :alpha_or_nil
360
+
361
+ def channel0
362
+ @channel0_or_nil.nil? ? 0 : @channel0_or_nil
363
+ end
364
+
365
+ def channel0_missing?
366
+ @channel0_or_nil.nil?
367
+ end
368
+
369
+ def channel0_powerless?
370
+ case _space
371
+ when Space::HSL
372
+ FuzzyMath.equals?(channel1, 0)
373
+ when Space::HWB
374
+ FuzzyMath.greater_than_or_equals?(channel1 + channel2, 100)
375
+ else
376
+ false
377
+ end
378
+ end
379
+
380
+ def channel1
381
+ @channel1_or_nil.nil? ? 0 : @channel1_or_nil
382
+ end
383
+
384
+ def channel1_missing?
385
+ @channel1_or_nil.nil?
386
+ end
387
+
388
+ def channel1_powerless?
389
+ false
390
+ end
391
+
392
+ def channel2
393
+ @channel2_or_nil.nil? ? 0 : @channel2_or_nil
394
+ end
395
+
396
+ def channel2_missing?
397
+ @channel2_or_nil.nil?
398
+ end
399
+
400
+ def channel2_powerless?
401
+ case _space
402
+ when Space::LCH, Space::OKLCH
403
+ FuzzyMath.equals?(channel1, 0)
404
+ else
405
+ false
406
+ end
407
+ end
408
+
409
+ def alpha_missing?
410
+ @alpha_or_nil.nil?
411
+ end
412
+
413
+ def _space
414
+ @space
415
+ end
416
+
417
+ def _to_space(space)
418
+ return self if _space == space
419
+
420
+ _space.convert(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
421
+ end
422
+
423
+ def _in_gamut?
424
+ return true unless _space.bounded?
425
+
426
+ _is_channel_in_gamut?(channel0, _space.channels[0]) &&
427
+ _is_channel_in_gamut?(channel1, _space.channels[1]) &&
428
+ _is_channel_in_gamut?(channel2, _space.channels[2])
429
+ end
430
+
431
+ def _to_gamut(method)
432
+ _in_gamut? ? self : method.map(self)
433
+ end
434
+
167
435
  private
168
436
 
169
- def rgb_to_hsl
170
- scaled_red = Rational(red, 255)
171
- scaled_green = Rational(green, 255)
172
- scaled_blue = Rational(blue, 255)
173
-
174
- max = [scaled_red, scaled_green, scaled_blue].max
175
- min = [scaled_red, scaled_green, scaled_blue].min
176
- delta = max - min
177
-
178
- if max == min
179
- @hue = 0
180
- elsif max == scaled_red
181
- @hue = ((scaled_green - scaled_blue) * 60 / delta) % 360
182
- elsif max == scaled_green
183
- @hue = (((scaled_blue - scaled_red) * 60 / delta) + 120) % 360
184
- elsif max == scaled_blue
185
- @hue = (((scaled_red - scaled_green) * 60 / delta) + 240) % 360
437
+ def _assert_options(space, options)
438
+ keys = space.channels.map do |channel|
439
+ channel.name.to_sym
440
+ end << :alpha << :space
441
+ options.each_key do |key|
442
+ unless keys.include?(key)
443
+ raise Sass::ScriptError.new("`#{key}` is not a valid channel in `#{space.name}`.", key)
444
+ end
445
+ end
446
+ keys
447
+ end
448
+
449
+ def _initialize_for_space_internal(space, channel0, channel1, channel2, alpha = 1)
450
+ case space
451
+ when Space::HSL
452
+ _initialize_for_space(
453
+ space,
454
+ _normalize_hue(channel0, invert: !channel1.nil? && FuzzyMath.less_than?(channel1, 0)),
455
+ channel1&.abs,
456
+ channel2,
457
+ alpha
458
+ )
459
+ when Space::HWB
460
+ _initialize_for_space(space, _normalize_hue(channel0, invert: false), channel1, channel2, alpha)
461
+ when Space::LCH, Space::OKLCH
462
+ _initialize_for_space(
463
+ space,
464
+ channel0,
465
+ channel1&.abs,
466
+ _normalize_hue(channel2, invert: !channel1.nil? && FuzzyMath.less_than?(channel1, 0)),
467
+ alpha
468
+ )
469
+ else
470
+ _initialize_for_space(space, channel0, channel1, channel2, alpha)
186
471
  end
472
+ end
473
+
474
+ def _initialize_for_space(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
475
+ @space = space
476
+ @channel0_or_nil = channel0_or_nil
477
+ @channel1_or_nil = channel1_or_nil
478
+ @channel2_or_nil = channel2_or_nil
479
+ @alpha_or_nil = alpha
480
+
481
+ FuzzyMath.assert_between(@alpha_or_nil, 0, 1, 'alpha') unless @alpha_or_nil.nil?
482
+ end
187
483
 
188
- lightness = @lightness = (max + min) * 50
189
-
190
- @saturation = if max == min
191
- 0
192
- elsif lightness < 50
193
- delta * 100 / (max + min)
194
- else
195
- delta * 100 / (2 - max - min)
196
- end
197
- end
198
-
199
- def hsl_to_rgb
200
- scaled_hue = Rational(hue, 360)
201
- scaled_saturation = Rational(saturation, 100)
202
- scaled_lightness = Rational(lightness, 100)
203
-
204
- tmp2 = if scaled_lightness <= 0.5
205
- scaled_lightness * (scaled_saturation + 1)
206
- else
207
- scaled_lightness + scaled_saturation - (scaled_lightness * scaled_saturation)
208
- end
209
- tmp1 = (scaled_lightness * 2) - tmp2
210
- @red = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue + Rational(1, 3)) * 255)
211
- @green = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue) * 255)
212
- @blue = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue - Rational(1, 3)) * 255)
213
- end
214
-
215
- def hsl_hue_to_rgb(tmp1, tmp2, hue)
216
- hue += 1 if hue.negative?
217
- hue -= 1 if hue > 1
218
-
219
- if hue < Rational(1, 6)
220
- tmp1 + ((tmp2 - tmp1) * hue * 6)
221
- elsif hue < Rational(1, 2)
222
- tmp2
223
- elsif hue < Rational(2, 3)
224
- tmp1 + ((tmp2 - tmp1) * (Rational(2, 3) - hue) * 6)
484
+ def _normalize_hue(hue, invert:)
485
+ return hue if hue.nil?
486
+
487
+ ((hue % 360) + 360 + (invert ? 180 : 0)) % 360
488
+ end
489
+
490
+ def _is_channel_in_gamut?(value, channel)
491
+ case channel
492
+ when LinearChannel
493
+ FuzzyMath.less_than_or_equals?(value, channel.max) && FuzzyMath.greater_than_or_equals?(value, channel.min)
225
494
  else
226
- tmp1
495
+ true
496
+ end
497
+ end
498
+
499
+ def _interpolate(other, method, weight: nil)
500
+ weight = 0.5 if weight.nil?
501
+ if weight.negative? || weight > 1
502
+ raise Sass::ScriptError.new("Expected #{wieght} to be within 0 and 1.", 'weight')
227
503
  end
504
+
505
+ return other if FuzzyMath.equals?(weight, 0)
506
+ return self if FuzzyMath.equals?(weight, 1)
507
+
508
+ color1 = _to_space(method.space)
509
+ color2 = other._to_space(method.space)
510
+
511
+ c1_missing0 = _analogous_channel_missing?(self, color1, 0)
512
+ c1_missing1 = _analogous_channel_missing?(self, color1, 1)
513
+ c1_missing2 = _analogous_channel_missing?(self, color1, 2)
514
+ c2_missing0 = _analogous_channel_missing?(other, color2, 0)
515
+ c2_missing1 = _analogous_channel_missing?(other, color2, 1)
516
+ c2_missing2 = _analogous_channel_missing?(other, color2, 2)
517
+ c1_channel0 = (c1_missing0 ? color2 : color1).channel0
518
+ c1_channel1 = (c1_missing1 ? color2 : color1).channel1
519
+ c1_channel2 = (c1_missing2 ? color2 : color1).channel2
520
+ c2_channel0 = (c2_missing0 ? color1 : color2).channel0
521
+ c2_channel1 = (c2_missing1 ? color1 : color2).channel1
522
+ c2_channel2 = (c2_missing2 ? color1 : color2).channel2
523
+ c1_alpha = alpha_or_nil.nil? ? other.alpha : alpha_or_nil
524
+ c2_alpha = other.alpha_or_nil.nil? ? alpha : other.alpha_or_nil
525
+
526
+ c1_multiplier = (alpha_or_nil.nil? ? 1 : alpha_or_nil) * weight
527
+ c2_multiplier = (other.alpha_or_nil.nil? ? 1 : other.alpha_or_nil) * (1 - weight)
528
+ mixed_alpha = alpha_missing? && other.alpha_missing? ? nil : (c1_alpha * weight) + (c2_alpha * (1 - weight))
529
+ mixed0 = if c1_missing0 && c2_missing0
530
+ nil
531
+ else
532
+ ((c1_channel0 * c1_multiplier) + (c2_channel0 * c2_multiplier)) /
533
+ (mixed_alpha.nil? ? 1 : mixed_alpha)
534
+ end
535
+ mixed1 = if c1_missing1 && c2_missing1
536
+ nil
537
+ else
538
+ ((c1_channel1 * c1_multiplier) + (c2_channel1 * c2_multiplier)) /
539
+ (mixed_alpha.nil? ? 1 : mixed_alpha)
540
+ end
541
+ mixed2 = if c1_missing2 && c2_missing2
542
+ nil
543
+ else
544
+ ((c1_channel2 * c1_multiplier) + (c2_channel2 * c2_multiplier)) /
545
+ (mixed_alpha.nil? ? 1 : mixed_alpha)
546
+ end
547
+
548
+ case method.space
549
+ when Space::HSL, Space::HWB
550
+ Color.send(:for_space_internal,
551
+ method.space,
552
+ c1_missing0 && c2_missing0 ? nil : _interpolate_hues(c1_channel0, c2_channel0, method.hue, weight),
553
+ mixed1,
554
+ mixed2,
555
+ mixed_alpha)
556
+ when Space::LCH, Space::OKLCH
557
+ Color.send(:for_space_internal,
558
+ method.space,
559
+ mixed0,
560
+ mixed1,
561
+ c1_missing2 && c2_missing2 ? nil : _interpolate_hues(c1_channel2, c2_channel2, method.hue, weight),
562
+ mixed_alpha)
563
+ else
564
+ Color.send(:_for_space,
565
+ method.space, mixed0, mixed1, mixed2, mixed_alpha)
566
+ end._to_space(_space)
228
567
  end
229
568
 
230
- def hwb_to_rgb
231
- scaled_hue = Rational(hue, 360)
232
- scaled_whiteness = Rational(whiteness, 100)
233
- scaled_blackness = Rational(blackness, 100)
569
+ def _analogous_channel_missing?(original, output, output_channel_index)
570
+ return true if output.channels_or_nil[output_channel_index].nil?
571
+
572
+ return false if original.equal?(output)
573
+
574
+ output_channel = output._space.channels[output_channel_index]
575
+ original_channel = original._space.channels.find do |channel|
576
+ output_channel.analogous?(channel)
577
+ end
578
+
579
+ return false if original_channel.nil?
580
+
581
+ original.channel_missing?(original_channel.name)
582
+ end
234
583
 
235
- sum = scaled_whiteness + scaled_blackness
236
- if sum > 1
237
- scaled_whiteness /= sum
238
- scaled_blackness /= sum
584
+ def _interpolate_hues(hue1, hue2, method, weight)
585
+ case method
586
+ when :shorter
587
+ diff = hue2 - hue1
588
+ if diff > 180
589
+ hue1 += 360
590
+ elsif diff < -180
591
+ hue2 += 360
592
+ end
593
+ when :longer
594
+ diff = hue2 - hue1
595
+ if diff.positive? && diff < 180
596
+ hue2 += 360
597
+ elsif diff > -180 && diff <= 0
598
+ hue1 += 360
599
+ end
600
+ when :increasing
601
+ hue2 += 360 if hue2 < hue1
602
+ when :decreasing
603
+ hue1 += 360 if hue1 < hue2
239
604
  end
240
605
 
241
- factor = 1 - scaled_whiteness - scaled_blackness
242
- @red = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue + Rational(1, 3))
243
- @green = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue)
244
- @blue = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue - Rational(1, 3))
606
+ (hue1 * weight) + (hue2 * (1 - weight))
245
607
  end
246
608
 
247
- def hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue)
248
- channel = (hsl_hue_to_rgb(0, 1, scaled_hue) * factor) + scaled_whiteness
249
- FuzzyMath.round(channel * 255)
609
+ class << self
610
+ private
611
+
612
+ def for_space(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
613
+ _for_space(Space.from_name(space), channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
614
+ end
615
+
616
+ def for_space_internal(space, channel0, channel1, channel2, alpha)
617
+ o = allocate
618
+ o.send(:_initialize_for_space_internal, space, channel0, channel1, channel2, alpha)
619
+ o
620
+ end
621
+
622
+ def _for_space(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
623
+ o = allocate
624
+ o.send(:_initialize_for_space, space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
625
+ o
626
+ end
250
627
  end
251
628
  end
252
629
  end