color_contrast_calc 0.8.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 599083b4c4ea4f652dd649dfc78bf9b15e7322496fa757830e04cbfff1618897
4
- data.tar.gz: 1a551dd81a0042938f7afbd6e6df33f435a09cfc6990a77711258e90d3b93e02
3
+ metadata.gz: 07b766d958883d015466220f97ed047512e0fc6dd2a51288b5274c6b0287f0fe
4
+ data.tar.gz: 1e740d3598c423f07efb3632b31bf155dbf337f8f9265ede39f3b12bd3e50456
5
5
  SHA512:
6
- metadata.gz: 77b7e9bc36cef897660f4dda625e10c658fb48b91608ea13a68f9d6c0b395e668f08d2e4af812f58146724ad4846c479b1bc833bbd338def9549dc4113c44ff2
7
- data.tar.gz: 777fbeed77cbc3b645be5e0a28ae5db2c9c7e1ea7dd38e2a582ca3d9133d01f0b1017b1e253c0171f333ab5b43d5e2933d57c3a696f6be297826ca20b724e7e1
6
+ metadata.gz: 7d3becf7316ad107082efb72a1e777fb23272a4fe73239170296e55e978d7d0935ad23e7b0a9203c0dc94164ac2dca5efc5763139c49e19cf116c50c28539e1c
7
+ data.tar.gz: 6e3e1efa303a5ea42e56fd4011ccee3cd19677f0ae770a279c9a4b6ffa9340bb63d5d192224b4f7b18a1364e2b299ecab878dee3a76aa657843ee333a619341b
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.3
2
+ TargetRubyVersion: 2.4
3
3
  Layout/SpaceInsideBlockBraces:
4
4
  SpaceBeforeBlockParameters: false
5
5
  Layout/EmptyLineAfterGuardClause:
@@ -1,11 +1,18 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3
5
- - 2.4
6
4
  - 2.5
7
5
  - 2.6
8
6
  - 2.7
7
+ - 3.0
9
8
  - ruby-head
10
- - jruby-head
11
9
  before_install: gem install bundler -v '~>2.0'
10
+ jobs:
11
+ include:
12
+ - rvm: 2.4
13
+ before_install:
14
+ - yes | gem update --system --force
15
+ - gem install bundler
16
+ - rvm: jruby-head
17
+ before_install:
18
+ - bundle add rake --version "13.0.0"
@@ -289,32 +289,40 @@ The grayscale of #ffa500 is #acacac.
289
289
  ```ruby
290
290
  require 'color_contrast_calc'
291
291
 
292
- color_names = ['red', 'yellow', 'lime', 'cyan', 'fuchsia', 'blue']
293
- colors = color_names.map {|c| ColorContrastCalc.color_from(c) }
292
+ color_names = ['red', 'lime', 'cyan', 'yellow', 'fuchsia', 'blue']
294
293
 
295
294
  # Sort by hSL order. An uppercase for a component of color means
296
295
  # that component should be sorted in descending order.
297
296
 
298
- hsl_ordered = ColorContrastCalc.sort(colors, 'hSL')
299
- puts("Colors sorted in the order of hSL: #{hsl_ordered.map(&:name)}")
297
+ hsl_ordered = ColorContrastCalc.sort(color_names, 'hSL')
298
+ puts("Colors sorted in the order of hSL: #{hsl_ordered}")
300
299
 
301
300
  # Sort by RGB order.
302
301
 
303
- rgb_ordered = ColorContrastCalc.sort(colors, 'RGB')
304
- puts("Colors sorted in the order of RGB: #{rgb_ordered.map(&:name)}")
302
+ rgb_ordered = ColorContrastCalc.sort(color_names, 'RGB')
303
+ puts("Colors sorted in the order of RGB: #{rgb_ordered}")
305
304
 
306
305
  # You can also change the precedence of components.
307
306
 
308
- grb_ordered = ColorContrastCalc.sort(colors, 'GRB')
309
- puts("Colors sorted in the order of GRB: #{grb_ordered.map(&:name)}")
307
+ grb_ordered = ColorContrastCalc.sort(color_names, 'GRB')
308
+ puts("Colors sorted in the order of GRB: #{grb_ordered}")
310
309
 
311
310
  # And you can directly sort hex color codes.
312
311
 
313
312
  ## Hex color codes that correspond to the color_names given above.
314
- hex_codes = ['#ff0000', '#ff0', '#00ff00', '#0ff', '#f0f', '#0000FF']
313
+ hex_codes = ['#ff0000', '#00ff00', '#0ff', '#ff0', '#f0f', '#0000FF']
315
314
 
316
315
  hsl_ordered = ColorContrastCalc.sort(hex_codes, 'hSL')
317
316
  puts("Colors sorted in the order of hSL: #{hsl_ordered}")
317
+
318
+ # If you want to sort colors in different notations,
319
+ # you should specify a key_mapper.
320
+
321
+ colors = ['rgb(255 0 0)', 'hsl(120 100% 50%)', '#0ff', 'hwb(60 0% 0%)', [255, 0, 255], '#0000ff']
322
+
323
+ key_mapper = proc {|c| ColorContrastCalc.color_from(c) }
324
+ colors_in_hsl_order = ColorContrastCalc.sort(colors, 'hSL', key_mapper)
325
+ puts("Colors sorted in the order of hSL: #{colors_in_hsl_order}")
318
326
  ```
319
327
 
320
328
  以下のように実行します:
@@ -325,6 +333,7 @@ Colors sorted in the order of hSL: ["red", "yellow", "lime", "cyan", "blue", "fu
325
333
  Colors sorted in the order of RGB: ["yellow", "fuchsia", "red", "cyan", "lime", "blue"]
326
334
  Colors sorted in the order of GRB: ["yellow", "cyan", "lime", "fuchsia", "red", "blue"]
327
335
  Colors sorted in the order of hSL: ["#ff0000", "#ff0", "#00ff00", "#0ff", "#0000FF", "#f0f"]
336
+ Colors sorted in the order of hSL: ["rgb(255 0 0)", "hwb(60 0% 0%)", "hsl(120 100% 50%)", "#0ff", "#0000ff", [255, 0, 255]]
328
337
  ```
329
338
 
330
339
  ### 例5: 定義済みの色のリスト
data/README.md CHANGED
@@ -292,32 +292,40 @@ For example, save the following code as `sort_colors.rb`:
292
292
  ```ruby
293
293
  require 'color_contrast_calc'
294
294
 
295
- color_names = ['red', 'yellow', 'lime', 'cyan', 'fuchsia', 'blue']
296
- colors = color_names.map {|c| ColorContrastCalc.color_from(c) }
295
+ color_names = ['red', 'lime', 'cyan', 'yellow', 'fuchsia', 'blue']
297
296
 
298
297
  # Sort by hSL order. An uppercase for a component of color means
299
298
  # that component should be sorted in descending order.
300
299
 
301
- hsl_ordered = ColorContrastCalc.sort(colors, 'hSL')
302
- puts("Colors sorted in the order of hSL: #{hsl_ordered.map(&:name)}")
300
+ hsl_ordered = ColorContrastCalc.sort(color_names, 'hSL')
301
+ puts("Colors sorted in the order of hSL: #{hsl_ordered}")
303
302
 
304
303
  # Sort by RGB order.
305
304
 
306
- rgb_ordered = ColorContrastCalc.sort(colors, 'RGB')
307
- puts("Colors sorted in the order of RGB: #{rgb_ordered.map(&:name)}")
305
+ rgb_ordered = ColorContrastCalc.sort(color_names, 'RGB')
306
+ puts("Colors sorted in the order of RGB: #{rgb_ordered}")
308
307
 
309
308
  # You can also change the precedence of components.
310
309
 
311
- grb_ordered = ColorContrastCalc.sort(colors, 'GRB')
312
- puts("Colors sorted in the order of GRB: #{grb_ordered.map(&:name)}")
310
+ grb_ordered = ColorContrastCalc.sort(color_names, 'GRB')
311
+ puts("Colors sorted in the order of GRB: #{grb_ordered}")
313
312
 
314
313
  # And you can directly sort hex color codes.
315
314
 
316
315
  ## Hex color codes that correspond to the color_names given above.
317
- hex_codes = ['#ff0000', '#ff0', '#00ff00', '#0ff', '#f0f', '#0000FF']
316
+ hex_codes = ['#ff0000', '#00ff00', '#0ff', '#ff0', '#f0f', '#0000FF']
318
317
 
319
318
  hsl_ordered = ColorContrastCalc.sort(hex_codes, 'hSL')
320
319
  puts("Colors sorted in the order of hSL: #{hsl_ordered}")
320
+
321
+ # If you want to sort colors in different notations,
322
+ # you should specify a key_mapper.
323
+
324
+ colors = ['rgb(255 0 0)', 'hsl(120 100% 50%)', '#0ff', 'hwb(60 0% 0%)', [255, 0, 255], '#0000ff']
325
+
326
+ key_mapper = proc {|c| ColorContrastCalc.color_from(c) }
327
+ colors_in_hsl_order = ColorContrastCalc.sort(colors, 'hSL', key_mapper)
328
+ puts("Colors sorted in the order of hSL: #{colors_in_hsl_order}")
321
329
  ```
322
330
 
323
331
  Then execute the script:
@@ -328,6 +336,7 @@ Colors sorted in the order of hSL: ["red", "yellow", "lime", "cyan", "blue", "fu
328
336
  Colors sorted in the order of RGB: ["yellow", "fuchsia", "red", "cyan", "lime", "blue"]
329
337
  Colors sorted in the order of GRB: ["yellow", "cyan", "lime", "fuchsia", "red", "blue"]
330
338
  Colors sorted in the order of hSL: ["#ff0000", "#ff0", "#00ff00", "#0ff", "#0000FF", "#f0f"]
339
+ Colors sorted in the order of hSL: ["rgb(255 0 0)", "hwb(60 0% 0%)", "hsl(120 100% 50%)", "#0ff", "#0000ff", [255, 0, 255]]
331
340
  ```
332
341
 
333
342
  ### Example 5: Lists of predefined colors
@@ -7,7 +7,7 @@ require 'color_contrast_calc/version'
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'color_contrast_calc'
9
9
  spec.version = ColorContrastCalc::VERSION
10
- spec.required_ruby_version = ">= 2.2"
10
+ spec.required_ruby_version = ">= 2.4"
11
11
  spec.authors = ['HASHIMOTO, Naoki']
12
12
  spec.email = ['hashimoto.naoki@gmail.com']
13
13
 
@@ -23,9 +23,9 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
24
24
  spec.require_paths = ['lib']
25
25
 
26
- spec.add_development_dependency 'bundler', '~> 2.0'
26
+ spec.add_development_dependency 'bundler', '~> 2.1'
27
27
  spec.add_development_dependency 'rake', '~> 13.0'
28
- spec.add_development_dependency 'rspec', '~> 3.9'
29
- spec.add_development_dependency 'rubocop', '~> 0.80'
30
- spec.add_development_dependency 'yard', '~> 0.9.24'
28
+ spec.add_development_dependency 'rspec', '~> 3.10'
29
+ spec.add_development_dependency 'rubocop', '~> 1.7'
30
+ spec.add_development_dependency 'yard', '~> 0.9'
31
31
  end
@@ -2,29 +2,37 @@
2
2
 
3
3
  require 'color_contrast_calc'
4
4
 
5
- color_names = ['red', 'yellow', 'lime', 'cyan', 'fuchsia', 'blue']
6
- colors = color_names.map {|c| ColorContrastCalc.color_from(c) }
5
+ color_names = ['red', 'lime', 'cyan', 'yellow', 'fuchsia', 'blue']
7
6
 
8
7
  # Sort by hSL order. An uppercase for a component of color means
9
8
  # that component should be sorted in descending order.
10
9
 
11
- hsl_ordered = ColorContrastCalc.sort(colors, 'hSL')
12
- puts("Colors sorted in the order of hSL: #{hsl_ordered.map(&:name)}")
10
+ hsl_ordered = ColorContrastCalc.sort(color_names, 'hSL')
11
+ puts("Colors sorted in the order of hSL: #{hsl_ordered}")
13
12
 
14
13
  # Sort by RGB order.
15
14
 
16
- rgb_ordered = ColorContrastCalc.sort(colors, 'RGB')
17
- puts("Colors sorted in the order of RGB: #{rgb_ordered.map(&:name)}")
15
+ rgb_ordered = ColorContrastCalc.sort(color_names, 'RGB')
16
+ puts("Colors sorted in the order of RGB: #{rgb_ordered}")
18
17
 
19
18
  # You can also change the precedence of components.
20
19
 
21
- grb_ordered = ColorContrastCalc.sort(colors, 'GRB')
22
- puts("Colors sorted in the order of GRB: #{grb_ordered.map(&:name)}")
20
+ grb_ordered = ColorContrastCalc.sort(color_names, 'GRB')
21
+ puts("Colors sorted in the order of GRB: #{grb_ordered}")
23
22
 
24
23
  # And you can directly sort hex color codes.
25
24
 
26
25
  ## Hex color codes that correspond to the color_names given above.
27
- hex_codes = ['#ff0000', '#ff0', '#00ff00', '#0ff', '#f0f', '#0000FF']
26
+ hex_codes = ['#ff0000', '#00ff00', '#0ff', '#ff0', '#f0f', '#0000FF']
28
27
 
29
28
  hsl_ordered = ColorContrastCalc.sort(hex_codes, 'hSL')
30
29
  puts("Colors sorted in the order of hSL: #{hsl_ordered}")
30
+
31
+ # If you want to sort colors in different notations,
32
+ # you should specify a key_mapper.
33
+
34
+ colors = ['rgb(255 0 0)', 'hsl(120 100% 50%)', '#0ff', 'hwb(60 0% 0%)', [255, 0, 255], '#0000ff']
35
+
36
+ key_mapper = proc {|c| ColorContrastCalc.color_from(c) }
37
+ colors_in_hsl_order = ColorContrastCalc.sort(colors, 'hSL', key_mapper)
38
+ puts("Colors sorted in the order of hSL: #{colors_in_hsl_order}")
@@ -45,8 +45,7 @@ module ColorContrastCalc
45
45
  # @return [Color] Instance of Color
46
46
 
47
47
  def from_rgb(rgb, name = nil)
48
- !name && List::HEX_TO_COLOR[Utils.rgb_to_hex(rgb)] ||
49
- Color.new(rgb, name)
48
+ !name && List::HEX_TO_COLOR[Utils.rgb_to_hex(rgb)] || new(rgb, name)
50
49
  end
51
50
 
52
51
  ##
@@ -58,8 +57,7 @@ module ColorContrastCalc
58
57
 
59
58
  def from_hex(hex, name = nil)
60
59
  normalized_hex = Utils.normalize_hex(hex)
61
- !name && List::HEX_TO_COLOR[normalized_hex] ||
62
- Color.new(normalized_hex, name)
60
+ !name && List::HEX_TO_COLOR[normalized_hex] || new(normalized_hex, name)
63
61
  end
64
62
 
65
63
  ##
@@ -72,12 +70,11 @@ module ColorContrastCalc
72
70
  def from_hsl(hsl, name = nil)
73
71
  if hsl.length == 4
74
72
  rgb = Utils.hsl_to_rgb(hsl[0, 3])
75
- return Color.new(rgb.push(hsl.last), name) unless opaque?(hsl)
73
+ return new(rgb.push(hsl.last), name) unless opaque?(hsl)
76
74
  end
77
75
 
78
76
  rgb ||= Utils.hsl_to_rgb(hsl)
79
- !name && List::HEX_TO_COLOR[Utils.rgb_to_hex(rgb)] ||
80
- Color.new(rgb, name)
77
+ !name && List::HEX_TO_COLOR[Utils.rgb_to_hex(rgb)] || new(rgb, name)
81
78
  end
82
79
 
83
80
  ##
@@ -141,19 +138,19 @@ module ColorContrastCalc
141
138
  end
142
139
 
143
140
  def opaque?(color_value)
144
- color_value[-1] == 1.0
141
+ color_value[-1] == Utils::MAX_OPACITY
145
142
  end
146
143
 
147
144
  private :opaque?
148
145
 
149
146
  def color_from_rgba(rgba_value, name = nil)
150
147
  unless Utils.valid_rgb?(rgba_value[0, 3])
151
- raise Invalidcolorrepresentationerror.from_value(rgb_value)
148
+ raise InvalidColorRepresentationError.from_value(rgba_value)
152
149
  end
153
150
 
154
151
  return color_from_rgb(rgba_value[0, 3], name) if opaque?(rgba_value)
155
152
 
156
- Color.new(rgba_value, name)
153
+ new(rgba_value, name)
157
154
  end
158
155
 
159
156
  private :color_from_rgba
@@ -164,7 +161,7 @@ module ColorContrastCalc
164
161
  end
165
162
 
166
163
  hex_code = Utils.rgb_to_hex(rgb_value)
167
- !name && List::HEX_TO_COLOR[hex_code] || Color.new(rgb_value, name)
164
+ !name && List::HEX_TO_COLOR[hex_code] || new(rgb_value, name)
168
165
  end
169
166
 
170
167
  private :color_from_rgb
@@ -193,7 +190,7 @@ module ColorContrastCalc
193
190
  end
194
191
 
195
192
  hex_code = Utils.normalize_hex(color_value)
196
- !name && List::HEX_TO_COLOR[hex_code] || Color.new(hex_code, name)
193
+ !name && List::HEX_TO_COLOR[hex_code] || new(hex_code, name)
197
194
  end
198
195
 
199
196
  private :color_from_str
@@ -234,6 +231,12 @@ module ColorContrastCalc
234
231
  @relative_luminance = Checker.relative_luminance(@rgb)
235
232
  end
236
233
 
234
+ def create(rgb, name = nil)
235
+ self.class.new(rgb, name)
236
+ end
237
+
238
+ private :create
239
+
237
240
  ##
238
241
  # Return HSL value of the color.
239
242
  #
@@ -248,6 +251,20 @@ module ColorContrastCalc
248
251
  @hsl ||= Utils.rgb_to_hsl(@rgb)
249
252
  end
250
253
 
254
+ ##
255
+ # Return HWB value of the color.
256
+ #
257
+ # The value is calculated from the RGB value, so if you create
258
+ # the instance by Color.color_from method, the value used to
259
+ # create the color does not necessarily correspond to the value
260
+ # of this property.
261
+ #
262
+ # @return [Array<Float>] HWB value represented as an array of numbers
263
+
264
+ def hwb
265
+ @hwb ||= Utils.rgb_to_hwb(@rgb)
266
+ end
267
+
251
268
  ##
252
269
  # Return a {https://www.w3.org/TR/SVG/types.html#ColorKeywords
253
270
  # color keyword name} when the name corresponds to the hex code
@@ -256,8 +273,7 @@ module ColorContrastCalc
256
273
  # @return [String] Color keyword name or hex color code
257
274
 
258
275
  def common_name
259
- named_color = List::HEX_TO_COLOR[@hex]
260
- named_color && named_color.name || @hex
276
+ List::HEX_TO_COLOR[@hex]&.name || @hex
261
277
  end
262
278
 
263
279
  ##
@@ -347,7 +363,7 @@ module ColorContrastCalc
347
363
  # @return [Color] New complementary color
348
364
  def complementary(name = nil)
349
365
  minmax = rgb.minmax.reduce {|min, max| min + max }
350
- self.class.new(rgb.map {|c| minmax - c }, name)
366
+ create(rgb.map {|c| minmax - c }, name)
351
367
  end
352
368
 
353
369
  ##
@@ -363,8 +379,8 @@ module ColorContrastCalc
363
379
  # of +other_color+
364
380
 
365
381
  def find_brightness_threshold(other_color, level = Checker::Level::AA)
366
- other_color = Color.new(other_color) unless other_color.is_a? Color
367
- Color.new(ThresholdFinder::Brightness.find(rgb, other_color.rgb, level))
382
+ other_color = create(other_color) unless other_color.is_a? Color
383
+ create(ThresholdFinder::Brightness.find(rgb, other_color.rgb, level))
368
384
  end
369
385
 
370
386
  ##
@@ -380,8 +396,8 @@ module ColorContrastCalc
380
396
  # of +other_color+
381
397
 
382
398
  def find_lightness_threshold(other_color, level = Checker::Level::AA)
383
- other_color = Color.new(other_color) unless other_color.is_a? Color
384
- Color.new(ThresholdFinder::Lightness.find(rgb, other_color.rgb, level))
399
+ other_color = create(other_color) unless other_color.is_a? Color
400
+ create(ThresholdFinder::Lightness.find(rgb, other_color.rgb, level))
385
401
  end
386
402
 
387
403
  ##
@@ -522,7 +538,7 @@ module ColorContrastCalc
522
538
 
523
539
  def generate_new_color(calc, ratio, name = nil)
524
540
  new_rgb = calc.calc_rgb(rgb, ratio)
525
- self.class.new(new_rgb, name)
541
+ create(new_rgb, name)
526
542
  end
527
543
 
528
544
  private :generate_new_color
@@ -267,7 +267,7 @@ module ColorContrastCalc
267
267
  # @return [true, false] return true when the opacity equals 1.0
268
268
 
269
269
  def opaque?
270
- opacity == 1.0
270
+ opacity == Utils::MAX_OPACITY
271
271
  end
272
272
 
273
273
  # @private
@@ -335,6 +335,16 @@ module ColorContrastCalc
335
335
 
336
336
  private_constant :MAX_SOURCE_LENGTH
337
337
 
338
+ def self.compose_error_message(scanner, message)
339
+ color_value = sanitized_source(scanner)
340
+ out = StringIO.new
341
+ out.print message
342
+ out.print ' '
343
+ ErrorReporter.print_error_pos!(out, color_value, scanner.charpos)
344
+ out.puts
345
+ out.string
346
+ end
347
+
338
348
  def self.format_error_message(scanner, re)
339
349
  out = StringIO.new
340
350
  color_value = sanitized_source(scanner)
@@ -368,7 +378,7 @@ module ColorContrastCalc
368
378
 
369
379
  class Parser
370
380
  class << self
371
- attr_accessor :parsers
381
+ attr_accessor :parsers, :main, :value, :function
372
382
  end
373
383
 
374
384
  def skip_spaces!(scanner)
@@ -449,7 +459,7 @@ module ColorContrastCalc
449
459
  read_unit!(scanner, parsed_value)
450
460
  end
451
461
 
452
- private :read_number!
462
+ protected :read_number!
453
463
 
454
464
  def read_unit!(scanner, parsed_value)
455
465
  unit = scanner.scan(TokenRe::UNIT)
@@ -463,15 +473,10 @@ module ColorContrastCalc
463
473
 
464
474
  def read_separator!(scanner, parsed_value)
465
475
  if next_spaces_as_separator?(scanner)
466
- return read_number!(scanner, parsed_value)
476
+ return Parser.function.read_number!(scanner, parsed_value)
467
477
  end
468
478
 
469
- if parsed_value[:parameters].length == 3 &&
470
- check_next_token(scanner, TokenRe::SLASH)
471
- return read_opacity!(scanner, parsed_value)
472
- end
473
-
474
- read_comma!(scanner, parsed_value)
479
+ Parser.value.read_comma!(scanner, parsed_value)
475
480
  end
476
481
 
477
482
  private :read_separator!
@@ -496,11 +501,6 @@ module ColorContrastCalc
496
501
 
497
502
  private :next_spaces_as_separator?
498
503
 
499
- def read_opacity!(scanner, parsed_value)
500
- read_token!(scanner, TokenRe::SLASH)
501
- read_number!(scanner, parsed_value)
502
- end
503
-
504
504
  def read_comma!(scanner, parsed_value)
505
505
  skip_spaces!(scanner)
506
506
 
@@ -510,10 +510,74 @@ module ColorContrastCalc
510
510
  read_number!(scanner, parsed_value)
511
511
  end
512
512
 
513
- private :read_comma!
513
+ protected :read_comma!
514
+ end
515
+
516
+ class ValueParser < Parser
517
+ def read_separator!(scanner, parsed_value)
518
+ if next_spaces_as_separator?(scanner)
519
+ error_message = report_wrong_separator!(scanner, parsed_value)
520
+ raise InvalidColorRepresentationError, error_message
521
+ end
522
+
523
+ read_comma!(scanner, parsed_value)
524
+ end
525
+
526
+ def report_wrong_separator!(scanner, parsed_value)
527
+ scheme = parsed_value[:scheme].upcase
528
+ template = '" " and "," as a separator should not be used mixedly in %s functions.'
529
+ message = format(template, scheme)
530
+ ErrorReporter.compose_error_message(scanner, message)
531
+ end
532
+
533
+ private :report_wrong_separator!
514
534
  end
515
535
 
516
536
  class FunctionParser < Parser
537
+ def read_separator!(scanner, parsed_value)
538
+ if next_spaces_as_separator?(scanner)
539
+ error_if_opacity_separator_expected(scanner, parsed_value)
540
+ read_number!(scanner, parsed_value)
541
+ elsif opacity_separator_is_next?(scanner, parsed_value)
542
+ read_opacity!(scanner, parsed_value)
543
+ else
544
+ read_comma!(scanner, parsed_value)
545
+ end
546
+ end
547
+
548
+ private :read_separator!
549
+
550
+ def error_if_opacity_separator_expected(scanner, parsed_value)
551
+ return unless parsed_value[:parameters].length == 3
552
+
553
+ error_message = report_wrong_opacity_separator!(scanner, parsed_value)
554
+ raise InvalidColorRepresentationError, error_message
555
+ end
556
+
557
+ private :error_if_opacity_separator_expected
558
+
559
+ def report_wrong_opacity_separator!(scanner, parsed_value)
560
+ scheme = parsed_value[:scheme].upcase
561
+ message = "\"/\" is expected as a separator for opacity in #{scheme} functions."
562
+ ErrorReporter.compose_error_message(scanner, message)
563
+ end
564
+
565
+ private :report_wrong_opacity_separator!
566
+
567
+ def opacity_separator_is_next?(scanner, parsed_value)
568
+ parsed_value[:parameters].length == 3 &&
569
+ check_next_token(scanner, TokenRe::SLASH)
570
+ end
571
+
572
+ private :opacity_separator_is_next?
573
+
574
+ def read_opacity!(scanner, parsed_value)
575
+ read_token!(scanner, TokenRe::SLASH)
576
+ read_number!(scanner, parsed_value)
577
+ end
578
+
579
+ private :read_opacity!
580
+
517
581
  def read_comma!(scanner, parsed_value)
518
582
  skip_spaces!(scanner)
519
583
 
@@ -527,15 +591,9 @@ module ColorContrastCalc
527
591
  end
528
592
 
529
593
  def report_wrong_separator!(scanner, parsed_value)
530
- out = StringIO.new
531
- color_value = scanner.string
532
594
  scheme = parsed_value[:scheme].upcase
533
- # The trailing space after the first message is intentional,
534
- # because it is immediately followed by another message.
535
- out.print "\",\" is not a valid separator for #{scheme} functions. "
536
- ErrorReporter.print_error_pos!(out, color_value, scanner.charpos)
537
- out.puts
538
- out.string
595
+ message = "\",\" is not a valid separator for #{scheme} functions."
596
+ ErrorReporter.compose_error_message(scanner, message)
539
597
  end
540
598
 
541
599
  private :report_wrong_separator!
@@ -552,7 +610,9 @@ module ColorContrastCalc
552
610
  Scheme::HWB => FunctionParser.new
553
611
  }
554
612
 
555
- MAIN_PARSER = Parser.new
613
+ Parser.main = Parser.new
614
+ Parser.value = ValueParser.new
615
+ Parser.function = FunctionParser.new
556
616
 
557
617
  ##
558
618
  # Parse an RGB/HSL/HWB function and store the result as an instance of
@@ -563,7 +623,7 @@ module ColorContrastCalc
563
623
  # @return [ColorFunction] An instance of ColorFunctionParser::ColorFunction
564
624
 
565
625
  def self.parse(color_value)
566
- parsed_value = MAIN_PARSER.read_scheme!(StringScanner.new(color_value))
626
+ parsed_value = Parser.main.read_scheme!(StringScanner.new(color_value))
567
627
  ColorFunction.create(parsed_value, color_value)
568
628
  end
569
629
 
@@ -7,6 +7,10 @@ module ColorContrastCalc
7
7
  class InvalidColorRepresentationError < StandardError
8
8
  module Template
9
9
  RGB = 'An RGB value should be in form of [r, g, b], but %s.'
10
+ RGBA = <<~RGBA_MESSAGE
11
+ An RGB value should be in form of [r, g, b, opacity]
12
+ (r, g, b should be in the range between 0 and 255), but %s.
13
+ RGBA_MESSAGE
10
14
  COLOR_NAME = '%s seems to be an undefined color name.'
11
15
  HEX = 'A hex code #xxxxxx where 0 <= x <= f is expected, but %s.'
12
16
  UNEXPECTED = 'A color should be given as an array or string, but %s.'
@@ -22,7 +26,7 @@ module ColorContrastCalc
22
26
  def self.select_message_template(value)
23
27
  case value
24
28
  when Array
25
- Template::RGB
29
+ value.length == 3 ? Template::RGB : Template::RGBA
26
30
  when String
27
31
  may_be_name?(value) ? Template::COLOR_NAME : Template::HEX
28
32
  else
@@ -18,6 +18,7 @@ module ColorContrastCalc
18
18
  module ColorComponent
19
19
  RGB = 'rgb'.chars
20
20
  HSL = 'hsl'.chars
21
+ HWB = 'hwb'.chars
21
22
  end
22
23
 
23
24
  module CompFunc
@@ -45,6 +46,9 @@ module ColorContrastCalc
45
46
  # The function returned by Sorter.compile_compare_function() expects
46
47
  # hex color codes as key values when this constants is specified.
47
48
  HEX = :hex
49
+ # The function returned by Sorter.compile_compare_function() expects
50
+ # color functions as key values when this constants is specified.
51
+ FUNCTION = :function
48
52
  # @private
49
53
  CLASS_TO_TYPE = {
50
54
  Color => COLOR,
@@ -62,14 +66,157 @@ module ColorContrastCalc
62
66
 
63
67
  def self.guess(color, key_mapper = nil)
64
68
  key = key_mapper ? key_mapper[color] : color
69
+ return FUNCTION if non_hex_code_string?(key)
65
70
  CLASS_TO_TYPE[key.class]
66
71
  end
72
+
73
+ def self.non_hex_code_string?(color)
74
+ color.is_a?(String) && !Utils.valid_hex?(color)
75
+ end
76
+
77
+ private_class_method :non_hex_code_string?
78
+ end
79
+
80
+ class CompareFunctionCompiler
81
+ def initialize(converters = nil)
82
+ @converters = converters
83
+ end
84
+
85
+ def compile(color_order)
86
+ order = parse_color_order(color_order)
87
+ create_proc(order, color_order)
88
+ end
89
+
90
+ # @private
91
+
92
+ def parse_color_order(color_order)
93
+ ordered_components = select_ordered_components(color_order)
94
+ pos = color_component_pos(color_order, ordered_components)
95
+ funcs = []
96
+ pos.each_with_index do |ci, i|
97
+ c = color_order[i]
98
+ funcs[ci] = Utils.uppercase?(c) ? CompFunc::DESCEND : CompFunc::ASCEND
99
+ end
100
+ { pos: pos, funcs: funcs }
101
+ end
102
+
103
+ def select_ordered_components(color_order)
104
+ case color_order
105
+ when /[hsl]{3}/i
106
+ ColorComponent::HSL
107
+ when /[hwb]{3}/i
108
+ ColorComponent::HWB
109
+ else
110
+ ColorComponent::RGB
111
+ end
112
+ end
113
+
114
+ private :select_ordered_components
115
+
116
+ # @private
117
+
118
+ def color_component_pos(color_order, ordered_components)
119
+ color_order.downcase.chars.map do |component|
120
+ ordered_components.index(component)
121
+ end
122
+ end
123
+
124
+ def create_proc(order, color_order)
125
+ if @converters
126
+ conv = select_converter(color_order)
127
+ proc {|color1, color2| compare(conv[color1], conv[color2], order) }
128
+ else
129
+ proc {|color1, color2| compare(color1, color2, order) }
130
+ end
131
+ end
132
+
133
+ private :create_proc
134
+
135
+ # @private
136
+
137
+ def compare_components(color1, color2, order)
138
+ funcs = order[:funcs]
139
+ order[:pos].each do |i|
140
+ result = funcs[i][color1[i], color2[i]]
141
+ return result unless result.zero?
142
+ end
143
+
144
+ 0
145
+ end
146
+
147
+ alias compare compare_components
148
+
149
+ def select_converter(color_order)
150
+ scheme = select_scheme(color_order)
151
+ @converters[scheme]
152
+ end
153
+
154
+ private :select_converter
155
+
156
+ def select_scheme(color_order)
157
+ case color_order
158
+ when /[hsl]{3}/i
159
+ :hsl
160
+ when /[hwb]{3}/i
161
+ :hwb
162
+ else
163
+ :rgb
164
+ end
165
+ end
166
+
167
+ private :select_scheme
168
+ end
169
+
170
+ class CachingCompiler < CompareFunctionCompiler
171
+ def create_proc(order, color_order)
172
+ converter = select_converter(color_order)
173
+ cache = {}
174
+
175
+ proc do |color1, color2|
176
+ c1 = to_components(color1, converter, cache)
177
+ c2 = to_components(color2, converter, cache)
178
+
179
+ compare(c1, c2, order)
180
+ end
181
+ end
182
+
183
+ def to_components(color, converter, cache)
184
+ cached_components = cache[color]
185
+ return cached_components if cached_components
186
+
187
+ components = converter[color]
188
+ cache[color] = components
189
+
190
+ components
191
+ end
192
+
193
+ private :to_components
67
194
  end
68
195
 
69
- # @private shorthands for Utils.hex_to_rgb() and .hex_to_hsl()
70
- HEX_TO_COMPONENTS = {
196
+ hex_to_components = {
197
+ # shorthands for Utils.hex_to_rgb() and .hex_to_hsl()
71
198
  rgb: Utils.method(:hex_to_rgb),
72
- hsl: Utils.method(:hex_to_hsl)
199
+ hsl: Utils.method(:hex_to_hsl),
200
+ hwb: Utils.method(:hex_to_hwb)
201
+ }
202
+
203
+ function_to_components = {
204
+ rgb: proc {|func| ColorContrastCalc.color_from(func).rgb },
205
+ hsl: proc {|func| ColorContrastCalc.color_from(func).hsl },
206
+ hwb: proc {|func| ColorContrastCalc.color_from(func).hwb }
207
+ }
208
+
209
+ color_to_components = {
210
+ rgb: proc {|color| color.rgb },
211
+ hsl: proc {|color| color.hsl },
212
+ hwb: proc {|color| color.hwb }
213
+ }
214
+
215
+ COMPARE_FUNCTION_COMPILERS = {
216
+ KeyTypes::COLOR => CompareFunctionCompiler.new(color_to_components),
217
+ KeyTypes::COMPONENTS => CompareFunctionCompiler.new,
218
+ KeyTypes::HEX => CachingCompiler.new(hex_to_components),
219
+ KeyTypes::FUNCTION => CachingCompiler.new(function_to_components)
73
220
  }.freeze
74
221
 
75
222
  ##
@@ -114,14 +261,7 @@ module ColorContrastCalc
114
261
  key_mapper = nil, &key_mapper_block)
115
262
  key_mapper = key_mapper_block if !key_mapper && key_mapper_block
116
263
 
117
- case key_type
118
- when KeyTypes::COLOR
119
- compare = compile_color_compare_function(color_order)
120
- when KeyTypes::COMPONENTS
121
- compare = compile_components_compare_function(color_order)
122
- when KeyTypes::HEX
123
- compare = compile_hex_compare_function(color_order)
124
- end
264
+ compare = COMPARE_FUNCTION_COMPILERS[key_type].compile(color_order)
125
265
 
126
266
  compose_function(compare, key_mapper)
127
267
  end
@@ -135,101 +275,5 @@ module ColorContrastCalc
135
275
  compare_function[key_mapper[color1], key_mapper[color2]]
136
276
  end
137
277
  end
138
-
139
- # @private
140
-
141
- def self.color_component_pos(color_order, ordered_components)
142
- color_order.downcase.chars.map do |component|
143
- ordered_components.index(component)
144
- end
145
- end
146
-
147
- # @private
148
-
149
- def self.parse_color_order(color_order)
150
- ordered_components = ColorComponent::RGB
151
- ordered_components = ColorComponent::HSL if hsl_order?(color_order)
152
- pos = color_component_pos(color_order, ordered_components)
153
- funcs = []
154
- pos.each_with_index do |ci, i|
155
- c = color_order[i]
156
- funcs[ci] = Utils.uppercase?(c) ? CompFunc::DESCEND : CompFunc::ASCEND
157
- end
158
- { pos: pos, funcs: funcs }
159
- end
160
-
161
- # @private
162
-
163
- def self.hsl_order?(color_order)
164
- /[hsl]{3}/i.match?(color_order)
165
- end
166
-
167
- # @private
168
-
169
- def self.compare_color_components(color1, color2, order)
170
- funcs = order[:funcs]
171
- order[:pos].each do |i|
172
- result = funcs[i][color1[i], color2[i]]
173
- return result unless result.zero?
174
- end
175
-
176
- 0
177
- end
178
-
179
- # @private
180
-
181
- def self.compile_components_compare_function(color_order)
182
- order = parse_color_order(color_order)
183
-
184
- proc do |color1, color2|
185
- compare_color_components(color1, color2, order)
186
- end
187
- end
188
-
189
- # @private
190
-
191
- def self.compile_hex_compare_function(color_order)
192
- order = parse_color_order(color_order)
193
- converter = HEX_TO_COMPONENTS[:rgb]
194
- converter = HEX_TO_COMPONENTS[:hsl] if hsl_order?(color_order)
195
- cache = {}
196
-
197
- proc do |hex1, hex2|
198
- color1 = hex_to_components(hex1, converter, cache)
199
- color2 = hex_to_components(hex2, converter, cache)
200
-
201
- compare_color_components(color1, color2, order)
202
- end
203
- end
204
-
205
- # @private
206
-
207
- def self.hex_to_components(hex, converter, cache)
208
- cached_components = cache[hex]
209
- return cached_components if cached_components
210
-
211
- components = converter[hex]
212
- cache[hex] = components
213
-
214
- components
215
- end
216
-
217
- private_class_method :hex_to_components
218
-
219
- # @private
220
-
221
- def self.compile_color_compare_function(color_order)
222
- order = parse_color_order(color_order)
223
-
224
- if hsl_order?(color_order)
225
- proc do |color1, color2|
226
- compare_color_components(color1.hsl, color2.hsl, order)
227
- end
228
- else
229
- proc do |color1, color2|
230
- compare_color_components(color1.rgb, color2.rgb, order)
231
- end
232
- end
233
- end
234
278
  end
235
279
  end
@@ -28,7 +28,7 @@ module ColorContrastCalc
28
28
  end
29
29
 
30
30
  def self.opaque?(rgba)
31
- rgba[-1] == 1.0
31
+ rgba[-1] == Utils::MAX_OPACITY
32
32
  end
33
33
 
34
34
  private_class_method :opaque?
@@ -12,6 +12,9 @@ module ColorContrastCalc
12
12
  module Utils
13
13
  using Shim unless //.respond_to? :match?
14
14
 
15
+ MIN_OPACITY = 0
16
+ MAX_OPACITY = 1.0
17
+
15
18
  HSL_UPPER_LIMIT = [360, 100, 100].freeze
16
19
 
17
20
  private_constant :HSL_UPPER_LIMIT
@@ -196,11 +199,9 @@ module ColorContrastCalc
196
199
  # @return [true, false] true if a valid HSL value is passed
197
200
 
198
201
  def self.valid_hsl?(hsl)
199
- return false unless hsl.length == 3
200
- hsl.each_with_index do |c, i|
201
- return false if !c.is_a?(Numeric) || c < 0 || c > HSL_UPPER_LIMIT[i]
202
+ hsl.length == 3 && hsl.each_with_index.all? do |c, i|
203
+ c.is_a?(Numeric) && c >= 0 && c <= HSL_UPPER_LIMIT[i]
202
204
  end
203
- true
204
205
  end
205
206
 
206
207
  ##
@@ -238,10 +239,10 @@ module ColorContrastCalc
238
239
  end
239
240
 
240
241
  module Hwb
241
- ##
242
- # ref: https://www.w3.org/TR/2019/WD-css-color-4-20191105/
242
+ HWB_UPPER_LIMIT = [360, 100, 100].freeze
243
243
 
244
244
  def normalize_hwb(hwb)
245
+ # https://www.w3.org/TR/2019/WD-css-color-4-20191105/
245
246
  h, w, b = hwb
246
247
 
247
248
  achromatic_percent = w + b
@@ -255,6 +256,12 @@ module ColorContrastCalc
255
256
 
256
257
  private :normalize_hwb
257
258
 
259
+ ##
260
+ # Convert an HWB value to an RGB value.
261
+ #
262
+ # @param hwb [Array<Float>] HWB value represented as an array of numbers
263
+ # @return [Array<Integer>] RGB value represented as an array of integers
264
+
258
265
  def hwb_to_rgb(hwb)
259
266
  hue, white, black = normalize_hwb(hwb)
260
267
  rgb = Utils.hsl_to_rgb([hue, 100, 50])
@@ -264,8 +271,51 @@ module ColorContrastCalc
264
271
  end
265
272
  end
266
273
 
267
- def rgb_to_hwb(_rgb)
268
- raise Notimplementederror, 'Must be implemented later'
274
+ ##
275
+ # Convert an HWB value to hex color code.
276
+ #
277
+ # @param hwb [Array<Float>] HWB value represented as an array of numbers
278
+ # @return [String] Hex color code such as "#ffff00"
279
+
280
+ def hwb_to_hex(hwb)
281
+ rgb_to_hex(hwb_to_rgb(hwb))
282
+ end
283
+
284
+ ##
285
+ # Convert an RGB value to an HWB value.
286
+ #
287
+ # @param rgb [Array<Integer>] RGB value represented as an array of
288
+ # integers
289
+ # @return [Array<Float>] HWB value represented as an array of numbers
290
+
291
+ def rgb_to_hwb(rgb)
292
+ # https://www.w3.org/TR/2020/WD-css-color-4-20201112/
293
+ hsl = Utils.rgb_to_hsl(rgb)
294
+ white = rgb.min
295
+ black = 255 - rgb.max
296
+ [hsl[0], white * 100 / 255.0, black * 100 / 255.0]
297
+ end
298
+
299
+ ##
300
+ # Convert hex color code to an HWB value.
301
+ #
302
+ # @param hex_code [String] Hex color code such as "#ffff00"
303
+ # @return [Array<Float>] HWB value represented as an array of numbers
304
+
305
+ def hex_to_hwb(hex_code)
306
+ rgb_to_hwb(Utils.hex_to_rgb(hex_code))
307
+ end
308
+
309
+ ##
310
+ # Check if a given array is a valid representation of HWB color.
311
+ #
312
+ # @param hwb [Array<Float>] HWB value represented as an array of numbers
313
+ # @return [true, false] true if a valid HWB value is passed
314
+
315
+ def valid_hwb?(hwb)
316
+ hwb.length == 3 && hwb.each_with_index.all? do |c, i|
317
+ c.is_a?(Numeric) && c >= 0 && c <= HWB_UPPER_LIMIT[i]
318
+ end
269
319
  end
270
320
  end
271
321
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ColorContrastCalc
4
- VERSION = '0.8.0'
4
+ VERSION = '0.9.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: color_contrast_calc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - HASHIMOTO, Naoki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-02 00:00:00.000000000 Z
11
+ date: 2021-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: '2.1'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: '2.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,42 +44,42 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.9'
47
+ version: '3.10'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '3.9'
54
+ version: '3.10'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.80'
61
+ version: '1.7'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.80'
68
+ version: '1.7'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yard
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.9.24
75
+ version: '0.9'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.9.24
82
+ version: '0.9'
83
83
  description:
84
84
  email:
85
85
  - hashimoto.naoki@gmail.com
@@ -135,15 +135,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: '2.2'
138
+ version: '2.4'
139
139
  required_rubygems_version: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - ">="
142
142
  - !ruby/object:Gem::Version
143
143
  version: '0'
144
144
  requirements: []
145
- rubyforge_project:
146
- rubygems_version: 2.7.6
145
+ rubygems_version: 3.1.4
147
146
  signing_key:
148
147
  specification_version: 4
149
148
  summary: Utility that helps you choose colors with sufficient contrast, WCAG 2.0 in