abachrome 0.1.5 → 0.1.6

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/abachrome.gemspec +1 -0
  3. data/devenv.nix +1 -1
  4. data/lib/abachrome/abc_decimal.rb +38 -35
  5. data/lib/abachrome/color.rb +37 -10
  6. data/lib/abachrome/color_mixins/blend.rb +7 -5
  7. data/lib/abachrome/color_mixins/lighten.rb +8 -6
  8. data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
  9. data/lib/abachrome/color_mixins/to_colorspace.rb +10 -8
  10. data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
  11. data/lib/abachrome/color_mixins/to_lrgb.rb +14 -12
  12. data/lib/abachrome/color_mixins/to_oklab.rb +16 -14
  13. data/lib/abachrome/color_mixins/to_oklch.rb +12 -10
  14. data/lib/abachrome/color_mixins/to_srgb.rb +17 -15
  15. data/lib/abachrome/color_models/cmyk.rb +159 -0
  16. data/lib/abachrome/color_models/hsv.rb +5 -3
  17. data/lib/abachrome/color_models/lms.rb +3 -1
  18. data/lib/abachrome/color_models/oklab.rb +3 -1
  19. data/lib/abachrome/color_models/oklch.rb +6 -4
  20. data/lib/abachrome/color_models/rgb.rb +4 -2
  21. data/lib/abachrome/color_models/xyz.rb +3 -1
  22. data/lib/abachrome/color_models/yiq.rb +37 -0
  23. data/lib/abachrome/color_space.rb +28 -14
  24. data/lib/abachrome/converter.rb +10 -8
  25. data/lib/abachrome/converters/base.rb +13 -11
  26. data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
  27. data/lib/abachrome/converters/lms_to_lrgb.rb +5 -3
  28. data/lib/abachrome/converters/lms_to_srgb.rb +6 -4
  29. data/lib/abachrome/converters/lms_to_xyz.rb +5 -3
  30. data/lib/abachrome/converters/lrgb_to_lms.rb +3 -1
  31. data/lib/abachrome/converters/lrgb_to_oklab.rb +5 -3
  32. data/lib/abachrome/converters/lrgb_to_srgb.rb +6 -4
  33. data/lib/abachrome/converters/lrgb_to_xyz.rb +5 -3
  34. data/lib/abachrome/converters/oklab_to_lms.rb +9 -7
  35. data/lib/abachrome/converters/oklab_to_lrgb.rb +7 -7
  36. data/lib/abachrome/converters/oklab_to_oklch.rb +4 -2
  37. data/lib/abachrome/converters/oklab_to_srgb.rb +4 -2
  38. data/lib/abachrome/converters/oklch_to_lrgb.rb +5 -3
  39. data/lib/abachrome/converters/oklch_to_oklab.rb +5 -3
  40. data/lib/abachrome/converters/oklch_to_srgb.rb +6 -4
  41. data/lib/abachrome/converters/oklch_to_xyz.rb +6 -4
  42. data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
  43. data/lib/abachrome/converters/srgb_to_lrgb.rb +5 -3
  44. data/lib/abachrome/converters/srgb_to_oklab.rb +4 -2
  45. data/lib/abachrome/converters/srgb_to_oklch.rb +5 -3
  46. data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
  47. data/lib/abachrome/converters/xyz_to_lms.rb +5 -3
  48. data/lib/abachrome/converters/xyz_to_oklab.rb +5 -3
  49. data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
  50. data/lib/abachrome/gamut/base.rb +3 -3
  51. data/lib/abachrome/gamut/p3.rb +3 -3
  52. data/lib/abachrome/gamut/rec2020.rb +2 -2
  53. data/lib/abachrome/gamut/srgb.rb +4 -2
  54. data/lib/abachrome/illuminants/base.rb +2 -2
  55. data/lib/abachrome/illuminants/d50.rb +2 -2
  56. data/lib/abachrome/illuminants/d55.rb +2 -2
  57. data/lib/abachrome/illuminants/d65.rb +2 -2
  58. data/lib/abachrome/illuminants/d75.rb +2 -2
  59. data/lib/abachrome/named/css.rb +149 -149
  60. data/lib/abachrome/named/tailwind.rb +265 -265
  61. data/lib/abachrome/outputs/css.rb +2 -2
  62. data/lib/abachrome/palette.rb +26 -25
  63. data/lib/abachrome/palette_mixins/interpolate.rb +3 -1
  64. data/lib/abachrome/palette_mixins/resample.rb +2 -2
  65. data/lib/abachrome/palette_mixins/stretch_luminance.rb +2 -2
  66. data/lib/abachrome/parsers/css.rb +86 -71
  67. data/lib/abachrome/parsers/hex.rb +2 -2
  68. data/lib/abachrome/parsers/tailwind.rb +8 -8
  69. data/lib/abachrome/spectral.rb +277 -0
  70. data/lib/abachrome/to_abcd.rb +4 -4
  71. data/lib/abachrome/version.rb +2 -2
  72. data/lib/abachrome.rb +66 -10
  73. metadata +29 -3
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  class Palette
@@ -6,7 +6,7 @@ module Abachrome
6
6
 
7
7
  # Initializes a new color palette with the given colors.
8
8
  # Automatically converts non-Color objects to Color objects by parsing them as hex values.
9
- #
9
+ #
10
10
  # @param colors [Array] An array of colors to include in the palette. Each element can be
11
11
  # either a Color object or a string-convertible object representing a hex color code.
12
12
  # @return [Abachrome::Palette] A new palette instance containing the provided colors.
@@ -17,7 +17,7 @@ module Abachrome
17
17
  # Adds a color to the palette.
18
18
  # Accepts either an Abachrome::Color object or any object that can be
19
19
  # converted to a string and parsed as a hex color code.
20
- #
20
+ #
21
21
  # @param color [Abachrome::Color, String, #to_s] The color to add to the palette.
22
22
  # If not already an Abachrome::Color object, it will be converted using Color.from_hex
23
23
  # @return [Abachrome::Palette] self, enabling method chaining
@@ -30,7 +30,7 @@ module Abachrome
30
30
  alias << add
31
31
 
32
32
  # Removes the specified color from the palette.
33
- #
33
+ #
34
34
  # @param color [Abachrome::Color, Object] The color to be removed from the palette
35
35
  # @return [Abachrome::Palette] Returns self for method chaining
36
36
  def remove(color)
@@ -39,10 +39,10 @@ module Abachrome
39
39
  end
40
40
 
41
41
  # Clears all colors from the palette.
42
- #
42
+ #
43
43
  # This method removes all stored colors in the palette. It provides a way to
44
44
  # reset the palette to an empty state while maintaining the same palette object.
45
- #
45
+ #
46
46
  # @return [Abachrome::Palette] Returns self for method chaining
47
47
  def clear
48
48
  @colors.clear
@@ -50,21 +50,21 @@ module Abachrome
50
50
  end
51
51
 
52
52
  # Returns the number of colors in the palette.
53
- #
53
+ #
54
54
  # @return [Integer] the number of colors in the palette
55
55
  def size
56
56
  @colors.size
57
57
  end
58
58
 
59
59
  # Returns whether the palette has no colors.
60
- #
60
+ #
61
61
  # @return [Boolean] true if the palette contains no colors, false otherwise
62
62
  def empty?
63
63
  @colors.empty?
64
64
  end
65
65
 
66
66
  # Yields each color in the palette to the given block.
67
- #
67
+ #
68
68
  # @param block [Proc] The block to be executed for each color in the palette.
69
69
  # @yield [Abachrome::Color] Each color in the palette.
70
70
  # @return [Enumerator] Returns an Enumerator if no block is given.
@@ -72,11 +72,12 @@ module Abachrome
72
72
  def each(&block)
73
73
  @colors.each(&block)
74
74
  end
75
+
75
76
  # Calls the given block once for each color in the palette, passing the color and its index as parameters.
76
- #
77
+ #
77
78
  # @example
78
79
  # palette.each_with_index { |color, index| puts "Color #{index}: #{color}" }
79
- #
80
+ #
80
81
  # @param block [Proc] The block to be called for each color
81
82
  # @yield [color, index] Yields a color and its index
82
83
  # @yieldparam color [Abachrome::Color] The color at the current position
@@ -88,7 +89,7 @@ module Abachrome
88
89
  end
89
90
 
90
91
  # Maps the palette by applying a block to each color.
91
- #
92
+ #
92
93
  # @param block [Proc] A block that takes a color and returns a new color.
93
94
  # @return [Abachrome::Palette] A new palette with the mapped colors.
94
95
  # @example
@@ -99,14 +100,14 @@ module Abachrome
99
100
  end
100
101
 
101
102
  # Returns a duplicate of the internal colors array.
102
- #
103
+ #
103
104
  # @return [Array<Abachrome::Color>] A duplicate of the palette's color array
104
105
  def to_a
105
106
  @colors.dup
106
107
  end
107
108
 
108
109
  # Access a color in the palette at the specified index.
109
- #
110
+ #
110
111
  # @param index [Integer] The index of the color to retrieve from the palette
111
112
  # @return [Abachrome::Color, nil] The color at the specified index, or nil if the index is out of bounds
112
113
  def [](index)
@@ -114,7 +115,7 @@ module Abachrome
114
115
  end
115
116
 
116
117
  # Slices the palette to create a new palette with a subset of colors.
117
- #
118
+ #
118
119
  # @param start [Integer] The starting index (or range) from which to start the slice.
119
120
  # @param length [Integer, nil] The number of colors to include in the slice. If nil and start is an Integer,
120
121
  # returns a new palette containing the single color at that index. If start is a Range, length is ignored.
@@ -125,14 +126,14 @@ module Abachrome
125
126
  end
126
127
 
127
128
  # Returns the first color in the palette.
128
- #
129
+ #
129
130
  # @return [Abachrome::Color, nil] The first color in the palette, or nil if the palette is empty.
130
131
  def first
131
132
  @colors.first
132
133
  end
133
134
 
134
135
  # Returns the last color in the palette.
135
- #
136
+ #
136
137
  # @return [Abachrome::Color, nil] The last color in the palette or nil if palette is empty.
137
138
  def last
138
139
  @colors.last
@@ -141,7 +142,7 @@ module Abachrome
141
142
  # Returns a new palette with colors sorted by lightness.
142
143
  # This method creates a new palette instance containing the same colors as the current
143
144
  # palette but sorted in ascending order based on their lightness values.
144
- #
145
+ #
145
146
  # @return [Abachrome::Palette] a new palette with the same colors sorted by lightness
146
147
  def sort_by_lightness
147
148
  self.class.new(@colors.sort_by(&:lightness))
@@ -150,7 +151,7 @@ module Abachrome
150
151
  # Returns a new palette with colors sorted by saturation from lowest to highest.
151
152
  # Saturation is determined by the second coordinate (a*) in the OKLAB color space.
152
153
  # Lower values represent less saturated colors, higher values represent more saturated colors.
153
- #
154
+ #
154
155
  # @return [Abachrome::Palette] A new palette instance with the same colors sorted by saturation
155
156
  def sort_by_saturation
156
157
  self.class.new(@colors.sort_by { |c| c.to_oklab.coordinates[1] })
@@ -160,7 +161,7 @@ module Abachrome
160
161
  # This method takes each color in the palette and blends it with the accumulated result
161
162
  # of previous blends. It starts with the first color and progressively blends each subsequent
162
163
  # color at the specified blend amount.
163
- #
164
+ #
164
165
  # @param amount [Float] The blend amount to use between each color pair, between 0.0 and 1.0.
165
166
  # Defaults to 0.5 (equal blend).
166
167
  # @return [Abachrome::Color, nil] The final blended color result, or nil if the palette is empty.
@@ -178,7 +179,7 @@ module Abachrome
178
179
  # This method converts each color in the palette to OKLAB coordinates,
179
180
  # calculates the arithmetic mean of these coordinates, and creates a new
180
181
  # color from the average values. Alpha values are also averaged.
181
- #
182
+ #
182
183
  # @return [Abachrome::Color, nil] The average color of all colors in the palette,
183
184
  # or nil if the palette is empty
184
185
  def average
@@ -198,9 +199,9 @@ module Abachrome
198
199
  end
199
200
 
200
201
  # Converts the colors in the palette to CSS-formatted strings.
201
- #
202
+ #
202
203
  # The format of the output can be specified with the format parameter.
203
- #
204
+ #
204
205
  # @param format [Symbol] The format to use for the CSS color strings.
205
206
  # :hex - Outputs colors in hexadecimal format (e.g., "#RRGGBB")
206
207
  # :rgb - Outputs colors in rgb() function format
@@ -223,7 +224,7 @@ module Abachrome
223
224
  end
224
225
 
225
226
  # Returns a string representation of the palette for inspection purposes.
226
- #
227
+ #
227
228
  # @return [String] A string containing the class name and a list of colors in the palette
228
229
  def inspect
229
230
  "#<#{self.class} colors=#{@colors.map(&:to_s)}>"
@@ -240,4 +241,4 @@ module Abachrome
240
241
  end
241
242
  end
242
243
 
243
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
244
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::PaletteMixins::Interpolate - Color palette interpolation functionality
2
4
  #
3
5
  # This mixin provides methods for interpolating between adjacent colors in a palette to create
@@ -48,4 +50,4 @@ module Abachrome
48
50
  end
49
51
  end
50
52
 
51
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
53
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module PaletteMixins
@@ -58,4 +58,4 @@ module Abachrome
58
58
  end
59
59
  end
60
60
 
61
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
61
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module PaletteMixins
@@ -69,4 +69,4 @@ module Abachrome
69
69
  end
70
70
  end
71
71
 
72
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
72
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Abachrome::Parsers::CSS - CSS color format parser
3
5
  #
@@ -36,8 +38,6 @@ module Abachrome
36
38
  parse_functional_color(input)
37
39
  end
38
40
 
39
- private
40
-
41
41
  def self.parse_named_color(input)
42
42
  # Check if input matches a named color
43
43
  rgb_values = Named::CSS.method(input.to_sym)&.call
@@ -53,27 +53,25 @@ module Abachrome
53
53
  def self.parse_functional_color(input)
54
54
  case input
55
55
  when /^rgb\((.+)\)$/
56
- parse_rgb($1)
56
+ parse_rgb(::Regexp.last_match(1))
57
57
  when /^rgba\((.+)\)$/
58
- parse_rgba($1)
58
+ parse_rgba(::Regexp.last_match(1))
59
59
  when /^hsl\((.+)\)$/
60
- parse_hsl($1)
60
+ parse_hsl(::Regexp.last_match(1))
61
61
  when /^hsla\((.+)\)$/
62
- parse_hsla($1)
62
+ parse_hsla(::Regexp.last_match(1))
63
63
  when /^hwb\((.+)\)$/
64
- parse_hwb($1)
64
+ parse_hwb(::Regexp.last_match(1))
65
65
  when /^lab\((.+)\)$/
66
- parse_lab($1)
66
+ parse_lab(::Regexp.last_match(1))
67
67
  when /^lch\((.+)\)$/
68
- parse_lch($1)
68
+ parse_lch(::Regexp.last_match(1))
69
69
  when /^oklab\((.+)\)$/
70
- parse_oklab($1)
70
+ parse_oklab(::Regexp.last_match(1))
71
71
  when /^oklch\((.+)\)$/
72
- parse_oklch($1)
72
+ parse_oklch(::Regexp.last_match(1))
73
73
  when /^color\((.+)\)$/
74
- parse_color_function($1)
75
- else
76
- nil
74
+ parse_color_function(::Regexp.last_match(1))
77
75
  end
78
76
  end
79
77
 
@@ -171,39 +169,43 @@ module Abachrome
171
169
  when "srgb"
172
170
  values = parse_color_values(values_str, 3)
173
171
  return nil unless values
172
+
174
173
  r, g, b = values
175
174
  Color.from_rgb(r, g, b)
176
175
  when "srgb-linear"
177
176
  values = parse_color_values(values_str, 3)
178
177
  return nil unless values
178
+
179
179
  r, g, b = values
180
180
  Color.from_lrgb(r, g, b)
181
181
  when "display-p3"
182
182
  # For now, approximate as sRGB
183
183
  values = parse_color_values(values_str, 3)
184
184
  return nil unless values
185
+
185
186
  r, g, b = values
186
187
  Color.from_rgb(r, g, b)
187
188
  when "a98-rgb"
188
189
  # For now, approximate as sRGB
189
190
  values = parse_color_values(values_str, 3)
190
191
  return nil unless values
192
+
191
193
  r, g, b = values
192
194
  Color.from_rgb(r, g, b)
193
195
  when "prophoto-rgb"
194
196
  # For now, approximate as sRGB
195
197
  values = parse_color_values(values_str, 3)
196
198
  return nil unless values
199
+
197
200
  r, g, b = values
198
201
  Color.from_rgb(r, g, b)
199
202
  when "rec2020"
200
203
  # For now, approximate as sRGB
201
204
  values = parse_color_values(values_str, 3)
202
205
  return nil unless values
206
+
203
207
  r, g, b = values
204
208
  Color.from_rgb(r, g, b)
205
- else
206
- nil
207
209
  end
208
210
  end
209
211
 
@@ -224,15 +226,16 @@ module Abachrome
224
226
 
225
227
  parsed = []
226
228
  values.each_with_index do |v, i|
227
- if i == 0 # Hue
228
- val = parse_angle_value(v)
229
- return nil unless val
230
- parsed << val
231
- else # Saturation, Lightness, Alpha
232
- val = parse_percentage_or_number(v)
233
- return nil unless val
234
- parsed << val
235
- end
229
+ val = if i.zero? # Hue
230
+ parse_angle_value(v)
231
+
232
+ else # Saturation, Lightness, Alpha
233
+ parse_percentage_or_number(v)
234
+
235
+ end
236
+ return nil unless val
237
+
238
+ parsed << val
236
239
  end
237
240
  parsed
238
241
  end
@@ -306,66 +309,78 @@ module Abachrome
306
309
  def self.parse_numeric_value(str)
307
310
  return nil unless str
308
311
 
309
- if str.end_with?('%')
310
- (str.chomp('%').to_f / 100.0)
312
+ if str.end_with?("%")
313
+ (str.chomp("%").to_f / 100.0)
311
314
  else
312
315
  str.to_f
313
316
  end
314
- rescue
317
+ rescue StandardError
315
318
  nil
316
319
  end
317
320
 
318
321
  def self.parse_percentage_or_number(str)
319
322
  return nil unless str
320
323
 
321
- if str.end_with?('%')
322
- str.chomp('%').to_f / 100.0
324
+ if str.end_with?("%")
325
+ str.chomp("%").to_f / 100.0
323
326
  else
324
327
  str.to_f
325
328
  end
326
- rescue
329
+ rescue StandardError
327
330
  nil
328
331
  end
329
332
 
330
333
  def self.parse_angle_value(str)
331
334
  return nil unless str
332
335
 
333
- if str.end_with?('deg')
334
- str.chomp('deg').to_f
335
- elsif str.end_with?('rad')
336
- str.chomp('rad').to_f * 180.0 / Math::PI
337
- elsif str.end_with?('grad')
338
- str.chomp('grad').to_f * 0.9
339
- elsif str.end_with?('turn')
340
- str.chomp('turn').to_f * 360.0
336
+ if str.end_with?("deg")
337
+ str.chomp("deg").to_f
338
+ elsif str.end_with?("rad")
339
+ str.chomp("rad").to_f * 180.0 / Math::PI
340
+ elsif str.end_with?("grad")
341
+ str.chomp("grad").to_f * 0.9
342
+ elsif str.end_with?("turn")
343
+ str.chomp("turn").to_f * 360.0
341
344
  else
342
345
  str.to_f # Assume degrees
343
346
  end
344
- rescue
347
+ rescue StandardError
345
348
  nil
346
349
  end
347
350
 
348
351
  # Color space conversion functions
349
352
 
350
353
  def self.hsl_to_rgb(h, s, l)
351
- h = h / 360.0 # Normalize hue to 0-1
352
-
353
- c = (1 - (2 * l - 1).abs) * s
354
- x = c * (1 - ((h * 6) % 2 - 1).abs)
355
- m = l - c / 2
356
-
357
- if h < 1.0/6
358
- r, g, b = c, x, 0
359
- elsif h < 2.0/6
360
- r, g, b = x, c, 0
361
- elsif h < 3.0/6
362
- r, g, b = 0, c, x
363
- elsif h < 4.0/6
364
- r, g, b = 0, x, c
365
- elsif h < 5.0/6
366
- r, g, b = x, 0, c
354
+ h /= 360.0 # Normalize hue to 0-1
355
+
356
+ c = (1 - ((2 * l) - 1).abs) * s
357
+ x = c * (1 - (((h * 6) % 2) - 1).abs)
358
+ m = l - (c / 2)
359
+
360
+ if h < 1.0 / 6
361
+ r = c
362
+ g = x
363
+ b = 0
364
+ elsif h < 2.0 / 6
365
+ r = x
366
+ g = c
367
+ b = 0
368
+ elsif h < 3.0 / 6
369
+ r = 0
370
+ g = c
371
+ b = x
372
+ elsif h < 4.0 / 6
373
+ r = 0
374
+ g = x
375
+ b = c
376
+ elsif h < 5.0 / 6
377
+ r = x
378
+ g = 0
379
+ b = c
367
380
  else
368
- r, g, b = c, 0, x
381
+ r = c
382
+ g = 0
383
+ b = x
369
384
  end
370
385
 
371
386
  [r + m, g + m, b + m]
@@ -373,7 +388,7 @@ module Abachrome
373
388
 
374
389
  def self.hwb_to_rgb(h, w, b)
375
390
  # Normalize values
376
- h = h / 360.0
391
+ h /= 360.0
377
392
 
378
393
  # Calculate RGB from HSL equivalent
379
394
  if w + b >= 1
@@ -384,9 +399,9 @@ module Abachrome
384
399
  r, g, b_rgb = rgb
385
400
 
386
401
  # Apply whiteness and blackness
387
- r = r * (1 - w - b) + w
388
- g = g * (1 - w - b) + w
389
- b_rgb = b_rgb * (1 - w - b) + w
402
+ r = (r * (1 - w - b)) + w
403
+ g = (g * (1 - w - b)) + w
404
+ b_rgb = (b_rgb * (1 - w - b)) + w
390
405
 
391
406
  [r, g, b_rgb]
392
407
  end
@@ -395,12 +410,12 @@ module Abachrome
395
410
  def self.lab_to_xyz(l, a, b)
396
411
  # CIELAB to XYZ conversion (D65 white point)
397
412
  y = (l + 16) / 116
398
- x = a / 500 + y
399
- z = y - b / 200
413
+ x = (a / 500) + y
414
+ z = y - (b / 200)
400
415
 
401
- x = x**3 > 0.008856 ? x**3 : (x - 16/116) / 7.787
402
- y = y**3 > 0.008856 ? y**3 : (y - 16/116) / 7.787
403
- z = z**3 > 0.008856 ? z**3 : (z - 16/116) / 7.787
416
+ x = x**3 > 0.008856 ? x**3 : (x - (16 / 116)) / 7.787
417
+ y = y**3 > 0.008856 ? y**3 : (y - (16 / 116)) / 7.787
418
+ z = z**3 > 0.008856 ? z**3 : (z - (16 / 116)) / 7.787
404
419
 
405
420
  # D65 white point
406
421
  x *= 0.95047
@@ -419,14 +434,14 @@ module Abachrome
419
434
 
420
435
  def self.xyz_to_rgb(x, y, z)
421
436
  # XYZ to linear RGB
422
- r = x * 3.2406 + y * -1.5372 + z * -0.4986
423
- g = x * -0.9689 + y * 1.8758 + z * 0.0415
424
- b = x * 0.0557 + y * -0.2040 + z * 1.0570
437
+ r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986)
438
+ g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415)
439
+ b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570)
425
440
 
426
441
  # Linear RGB to sRGB
427
442
  [r, g, b].map do |v|
428
443
  if v > 0.0031308
429
- 1.055 * (v ** (1/2.4)) - 0.055
444
+ (1.055 * (v**(1 / 2.4))) - 0.055
430
445
  else
431
446
  12.92 * v
432
447
  end
@@ -434,4 +449,4 @@ module Abachrome
434
449
  end
435
450
  end
436
451
  end
437
- end
452
+ end
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Parsers
@@ -49,4 +49,4 @@ module Abachrome
49
49
  end
50
50
  end
51
51
 
52
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
52
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Parsers
@@ -7,7 +7,7 @@ module Abachrome
7
7
  # - gray-400
8
8
  # - blue-900/20 (with opacity)
9
9
  # - slate-50
10
- TAILWIND_PATTERN = /^([a-z]+)-(\d+)(?:\/(\d+(?:\.\d+)?))?$/
10
+ TAILWIND_PATTERN = %r{^([a-z]+)-(\d+)(?:/(\d+(?:\.\d+)?))?$}
11
11
 
12
12
  def self.parse(input)
13
13
  match = input.match(TAILWIND_PATTERN)
@@ -29,12 +29,12 @@ module Abachrome
29
29
 
30
30
  # Calculate alpha from opacity percentage if provided
31
31
  alpha = if opacity
32
- opacity_value = opacity.to_f
33
- # Opacity in Tailwind is a percentage (0-100)
34
- opacity_value / 100.0
35
- else
36
- 1.0
37
- end
32
+ opacity_value = opacity.to_f
33
+ # Opacity in Tailwind is a percentage (0-100)
34
+ opacity_value / 100.0
35
+ else
36
+ 1.0
37
+ end
38
38
 
39
39
  Color.from_rgb(r, g, b, alpha)
40
40
  end