color_contrast_calc 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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