color_contrast_calc 0.6.1 → 0.7.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: 7f14c1b61fcfcbab962811f5d6c2bee98841b5fc88aedbe3cdcb1f66747c7735
4
- data.tar.gz: 60c833fd6e0374f319172ff40307f496f3d3e31b7ae7e77b01105f2283855776
3
+ metadata.gz: 4eca556d535a8e8eda4ad0bf84e1891447b7065823b18cce4b4695f5a7e2beb6
4
+ data.tar.gz: 2548294c153d699792adceb71818b4a670da7f5faca6e48d67a1f511ae389f91
5
5
  SHA512:
6
- metadata.gz: 2406be8e787bc8730cc4297429f37081a21cfde4acd5fc82f53703eccce10f0d1868ce8072ee4f592c2ea7a4a8372c4ca7bdfa97a6866b4d632f30b7c2a49675
7
- data.tar.gz: 226e05b5b73f8f19e7589bed424cb1eb6c14dabfa1b87b2b8e1db84c646ce4110ad3cbd93970746a9df1d8df41fa5286801fea849168ac260e76724d55ab4cca
6
+ metadata.gz: cf7971d631606c8cd89f2c23661cb873f73874877c2668e742683c044f634b508262aca2745077831104bdaeeefb123ee30ca303c9cffbef2bc4298bb7518b35
7
+ data.tar.gz: 67851e719c99d0c047e86f8634c4c7b5ad6511c924b80f2246197bd01e7781c0ff6959dd05f16dba44a43792c916857c4fd698db4081bb6a06a242af5b0dbd1c
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.2
2
+ TargetRubyVersion: 2.3
3
3
  Layout/SpaceInsideBlockBraces:
4
4
  SpaceBeforeBlockParameters: false
5
5
  Layout/EmptyLineAfterGuardClause:
@@ -12,5 +12,5 @@ Style/FormatStringToken:
12
12
  Enabled: false
13
13
  Style/AccessModifierDeclarations:
14
14
  EnforcedStyle: inline
15
- Naming/UncommunicativeMethodParamName:
15
+ Naming/MethodParameterName:
16
16
  MinNameLength: 1
@@ -47,8 +47,8 @@ Or install it yourself as:
47
47
  require 'color_contrast_calc'
48
48
 
49
49
  # Create an instance of Color from a hex code
50
- # (You can pass 'red', [255, 0, 0], 'rgb(255, 0, 0)' or 'hsl(0deg, 100%, 50%)'
51
- # instead of '#ff0000')
50
+ # (You can pass 'red', [255, 0, 0], 'rgb(255, 0, 0)', 'hsl(0deg, 100%, 50%)' or
51
+ # hwb(60deg 0% 0%) instead of '#ff0000')
52
52
  red = ColorContrastCalc.color_from('#ff0000')
53
53
  puts red.class
54
54
  puts red.name
@@ -70,6 +70,17 @@ red
70
70
 
71
71
  ```
72
72
 
73
+ #### `ColorContrastCalc.color_from()`の引数に使える色の表現
74
+
75
+ `ColorContrastCalc.color_from()`の第1引数は以下の形式で指定できます。
76
+
77
+ * RGB値の16進数表記: #ff0, #ffff00, #FF0, etc.
78
+ * RGB値の関数形式表記: rgb(255, 255, 0), rgb(255 255 0), etc.
79
+ * Integerの配列で表したRGB値: [255, 255, 0], etc.
80
+ * HSL値の関数形式表記: hsl(60deg, 100%, 50%), hsl(60 100% 50%), etc.
81
+ * [実験的対応] HWB値の関数形式表記: hwb(60deg 0% 0%), hwb(60 0% 0%), etc.
82
+ * [拡張カラーキーワード](https://www.w3.org/TR/css-color-3/#svg-color): white, black, red, etc.
83
+
73
84
  ### 例1: 2つの色のコントラスト比を計算する
74
85
 
75
86
  #### 1.1: 最も簡便なやり方
data/README.md CHANGED
@@ -46,8 +46,8 @@ Save the following code as `color_instance.rb`:
46
46
  require 'color_contrast_calc'
47
47
 
48
48
  # Create an instance of Color from a hex code
49
- # (You can pass 'red', [255, 0, 0], 'rgb(255, 0, 0)' or 'hsl(0deg, 100%, 50%)'
50
- # instead of '#ff0000')
49
+ # (You can pass 'red', [255, 0, 0], 'rgb(255, 0, 0)', 'hsl(0deg, 100%, 50%)' or
50
+ # hwb(60deg 0% 0%) instead of '#ff0000')
51
51
  red = ColorContrastCalc.color_from('#ff0000')
52
52
  puts red.class
53
53
  puts red.name
@@ -69,6 +69,17 @@ red
69
69
 
70
70
  ```
71
71
 
72
+ #### Color units for the argument of `ColorContrastCalc.color_from()`
73
+
74
+ The following formats are supported for the first argument of `ColorContrastCalc.color_from()`.
75
+
76
+ * RGB values in hexadecimal notation: #ff0, #ffff00, #FF0, etc.
77
+ * RGB values in functional notation: rgb(255, 255, 0), rgb(255 255 0), etc.
78
+ * RGB values as an Array of Integers: [255, 255, 0], etc.
79
+ * HSL colors in functional notation: hsl(60deg, 100%, 50%), hsl(60 100% 50%), etc.
80
+ * [Experimental] HWB colors in functional notation: hwb(60deg 0% 0%), hwb(60 0% 0%), etc.
81
+ * [Extended color keywords](https://www.w3.org/TR/css-color-3/#svg-color): white, black, red, etc.
82
+
72
83
  ### Example 1: Calculate the contrast ratio between two colors
73
84
 
74
85
  #### 1.1: The easiest way
@@ -26,6 +26,6 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'bundler', '~> 2.0'
27
27
  spec.add_development_dependency 'rake', '~> 10.0'
28
28
  spec.add_development_dependency 'rspec', '~> 3.0'
29
- spec.add_development_dependency 'rubocop', '~> 0.49.1'
29
+ spec.add_development_dependency 'rubocop', '~> 0.79'
30
30
  spec.add_development_dependency 'yard', '~> 0.9.9'
31
31
  end
@@ -3,8 +3,8 @@ require 'color_contrast_calc'
3
3
  require 'color_contrast_calc'
4
4
 
5
5
  # Create an instance of Color from a hex code
6
- # (You can pass 'red', [255, 0, 0], 'rgb(255, 0, 0)' or 'hsl(0deg, 100%, 50%)'
7
- # instead of '#ff0000')
6
+ # (You can pass 'red', [255, 0, 0], 'rgb(255, 0, 0)', 'hsl(0deg, 100%, 50%)' or
7
+ # hwb(60deg 0% 0%) instead of '#ff0000')
8
8
  red = ColorContrastCalc.color_from('#ff0000')
9
9
  puts red.class
10
10
  puts red.name
@@ -12,12 +12,14 @@ module ColorContrastCalc
12
12
  ##
13
13
  # Return an instance of Color.
14
14
  #
15
- # As +color_value+, you can pass a predefined color name, or an
16
- # RGB value represented as an array of integers or a hex code such
17
- # as [255, 255, 0] or "#ffff00". +name+ is assigned to the returned
18
- # instance.
15
+ # As +color_value+, you can pass a predefined color name, an
16
+ # RGB value represented as an array of integers like [255, 255, 0],
17
+ # or a string such as a hex code like "#ffff00". +name+ is assigned
18
+ # to the returned instance.
19
19
  # @param color_value [String, Array<Integer>] Name of a predefined
20
- # color, hex color code, rgb/hsl functions or RGB value
20
+ # color, hex color code, rgb/hsl/hwb functions or RGB value.
21
+ # Yellow, for example, can be given as [255, 255, 0], "#ffff00",
22
+ # "rgb(255, 255, 255)", "hsl(60deg, 100% 50%)" or "hwb(60deg 0% 0%)".
21
23
  # @param name [String] Without specifying a name, a color keyword name
22
24
  # (if exists) or the value of normalized hex color code is assigned
23
25
  # to Color#name
@@ -63,7 +65,8 @@ module ColorContrastCalc
63
65
  #
64
66
  # @param color1 [String, Array<Integer>] RGB color given as a string or
65
67
  # an array of integers. Yellow, for example, can be given as "#ffff00",
66
- # "#ff0", "rgb(255, 255, 0)", "hsl(60deg, 100%, 50%)" or [255, 255, 0].
68
+ # "#ff0", "rgb(255, 255, 0)", "hsl(60deg, 100%, 50%)", "hwb(60deg 0% 0%)"
69
+ # or [255, 255, 0].
67
70
  # @param color2 [String, Array<Integer>] RGB color given as a string or
68
71
  # an array of integers.
69
72
  # @return [Float] Contrast ratio
@@ -72,6 +75,32 @@ module ColorContrastCalc
72
75
  Color.as_color(color1).contrast_ratio_against(Color.as_color(color2))
73
76
  end
74
77
 
78
+ ##
79
+ # Select from two colors the one of which the contrast ratio is higher
80
+ # than the other's, against a given color.
81
+ #
82
+ # Note that this method is tentatively provided and may be changed later
83
+ # including its name.
84
+ #
85
+ # @param color [String, Array<Integer>, Color] A color against which
86
+ # the contrast ratio of other two colors will be calculated
87
+ # @param light_base [String, Array<Integer>, Color] One of two colors
88
+ # which will be returned depending their contrast ratio: This one
89
+ # will be returned when the contast ratio of the colors happen to
90
+ # be same.
91
+ # @param dark_base [String, Array<Integer>, Color] One of two colors
92
+ # which will be returned depending their contrast ratio
93
+ # @return [String, Array<Integer>, Color] One of the values
94
+ # specified as +light_base+ and +dark_base+
95
+
96
+ def self.higher_contrast_base_color_for(color,
97
+ light_base: Color::WHITE,
98
+ dark_base: Color::BLACK)
99
+ ratio_with_light = contrast_ratio(color, light_base)
100
+ ratio_with_dark = contrast_ratio(color, dark_base)
101
+ ratio_with_light < ratio_with_dark ? dark_base : light_base
102
+ end
103
+
75
104
  ##
76
105
  # Return an array of named colors.
77
106
  #
@@ -78,12 +78,14 @@ module ColorContrastCalc
78
78
  ##
79
79
  # Return an instance of Color.
80
80
  #
81
- # As +color_value+, you can pass a predefined color name, or an
82
- # RGB value represented as an array of integers or a hex code such
83
- # as [255, 255, 0] or "#ffff00". +name+ is assigned to the returned
84
- # instance.
81
+ # As +color_value+, you can pass a predefined color name, an
82
+ # RGB value represented as an array of integers like [255, 255, 0],
83
+ # or a string such as a hex code like "#ffff00". +name+ is assigned
84
+ # to the returned instance.
85
85
  # @param color_value [String, Array<Integer>] Name of a predefined
86
- # color, hex color code, rgb/hsl functions or RGB value
86
+ # color, hex color code, rgb/hsl/hwb functions or RGB value.
87
+ # Yellow, for example, can be given as [255, 255, 0], "#ffff00",
88
+ # "rgb(255, 255, 255)", "hsl(60deg, 100% 50%)" or "hwb(60deg 0% 0%)".
87
89
  # @param name [String] Without specifying a name, a color keyword name
88
90
  # (if exists) or the value of normalized hex color code is assigned
89
91
  # to Color#name
@@ -96,7 +98,7 @@ module ColorContrastCalc
96
98
 
97
99
  return color_from_rgb(color_value, name) if color_value.is_a?(Array)
98
100
 
99
- if /\A(?:rgb|hsl)/i =~ color_value
101
+ if /\A(?:rgb|hsl|hwb)/i =~ color_value
100
102
  return color_from_func(color_value, name)
101
103
  end
102
104
  color_from_str(color_value, name)
@@ -106,11 +108,12 @@ module ColorContrastCalc
106
108
  # Return an instance of Color.
107
109
  #
108
110
  # As +color_value+, you can pass a Color instance, a predefined color
109
- # name, or an RGB value represented as an array of integers or a hex
110
- # code such as [255, 255, 0] or "#ffff00". +name+ is assigned to the
111
- # returned instance.
112
- # @param color_value [Color, String, Array<Integer>] An instance of
113
- # Color, a name of a predefined, color, hex color code or RGB value
111
+ # name, an RGB value represented as an array of integers like
112
+ # [255, 255, 0], or a string such as a hex code like "#ffff00".
113
+ # +name+ is assigned to the returned instance.
114
+ # @param color_value [String, Array<Integer>] An instance of Color,
115
+ # a predefined color name, hex color code, rgb/hsl/hwb functions
116
+ # or RGB value
114
117
  # @param name [String] Without specifying a name, a color keyword name
115
118
  # (if exists) or the value of normalized hex color code is assigned
116
119
  # to Color#name
@@ -138,11 +141,11 @@ module ColorContrastCalc
138
141
 
139
142
  def color_from_func(color_value, name = nil)
140
143
  conv = ColorFunctionParser.parse(color_value)
141
- if conv.scheme == ColorFunctionParser::Scheme::RGB
142
- return color_from_rgb(conv.to_a, name || color_value)
144
+ if conv.scheme == ColorFunctionParser::Scheme::HSL
145
+ return from_hsl(conv.to_a, name || color_value)
143
146
  end
144
147
 
145
- from_hsl(conv.to_a, name || color_value)
148
+ color_from_rgb(conv.rgb, name || color_value)
146
149
  end
147
150
 
148
151
  private :color_from_func
@@ -7,7 +7,7 @@ require 'color_contrast_calc/invalid_color_representation_error'
7
7
 
8
8
  module ColorContrastCalc
9
9
  ##
10
- # Module that converts RGB/HSL functions into data apt for calculation.
10
+ # Module that converts RGB/HSL/HWB functions into data apt for calculation.
11
11
 
12
12
  module ColorFunctionParser
13
13
  ##
@@ -16,21 +16,154 @@ module ColorContrastCalc
16
16
  module Scheme
17
17
  RGB = 'rgb'
18
18
  HSL = 'hsl'
19
+ HWB = 'hwb'
19
20
  end
20
21
 
21
22
  ##
22
- # Hold information about a parsed RGB/HSL function.
23
+ # Supported units
24
+
25
+ module Unit
26
+ PERCENT = '%'
27
+ DEG = 'deg'
28
+ GRAD = 'grad'
29
+ RAD = 'rad'
30
+ TURN = 'turn'
31
+ end
32
+
33
+ ##
34
+ # Validate the unit of each parameter in a color functions.
35
+
36
+ class Validator
37
+ include Unit
38
+
39
+ POS = %w[1st 2nd 3rd].freeze
40
+
41
+ private_constant :POS
42
+
43
+ def initialize
44
+ @config = yield
45
+ @scheme = @config[:scheme]
46
+ end
47
+
48
+ def format_to_function(parameters)
49
+ params = parameters.map {|param| "#{param[:number]}#{param[:unit]}" }
50
+ "#{@scheme}(#{params.join(' ')})"
51
+ end
52
+
53
+ private :format_to_function
54
+
55
+ def error_message(parameters, passed_unit, pos, original_value = nil)
56
+ color_func = original_value || format_to_function(parameters)
57
+
58
+ if passed_unit
59
+ return format('"%s" is not allowed for %s.',
60
+ passed_unit, format_to_function(parameters))
61
+ end
62
+
63
+ format('A unit is required for the %s parameter of %s.',
64
+ POS[pos], color_func)
65
+ end
66
+
67
+ private :error_message
68
+
69
+ # @private
70
+ def validate_units(parameters, original_value = nil)
71
+ @config[:units].each_with_index do |unit, i|
72
+ passed_unit = parameters[i][:unit]
73
+
74
+ unless unit.include? passed_unit
75
+ raise InvalidColorRepresentationError,
76
+ error_message(parameters, passed_unit, i, original_value)
77
+ end
78
+ end
79
+
80
+ true
81
+ end
82
+
83
+ # @private
84
+ RGB = Validator.new do
85
+ {
86
+ scheme: Scheme::RGB,
87
+ units: [
88
+ [nil, PERCENT],
89
+ [nil, PERCENT],
90
+ [nil, PERCENT]
91
+ ]
92
+ }
93
+ end
94
+
95
+ # @private
96
+ HSL = Validator.new do
97
+ {
98
+ scheme: Scheme::HSL,
99
+ units: [
100
+ [nil, DEG, GRAD, RAD, TURN],
101
+ [PERCENT],
102
+ [PERCENT]
103
+ ]
104
+ }
105
+ end
106
+
107
+ # @private
108
+ HWB = Validator.new do
109
+ {
110
+ scheme: Scheme::HWB,
111
+ units: [
112
+ [nil, DEG, GRAD, RAD, TURN],
113
+ [PERCENT],
114
+ [PERCENT]
115
+ ]
116
+ }
117
+ end
118
+
119
+ VALIDATORS = {
120
+ Scheme::RGB => RGB,
121
+ Scheme::HSL => HSL,
122
+ Scheme::HWB => HWB
123
+ }.freeze
124
+
125
+ private_constant :VALIDATORS
126
+
127
+ def self.validate(parsed_value, original_value = nil)
128
+ scheme = parsed_value[:scheme]
129
+ params = parsed_value[:parameters]
130
+ VALIDATORS[scheme].validate_units(params, original_value)
131
+ end
132
+ end
133
+
134
+ ##
135
+ # Hold information about a parsed RGB/HSL/HWB function.
23
136
  #
24
137
  # This class is intended to be used internally in ColorFunctionParser,
25
138
  # so do not rely on the current class name and its interfaces.
26
139
  # They may change in the future.
27
140
 
28
141
  class Converter
142
+ UNIT_CONV = {
143
+ Unit::PERCENT => proc do |n, base|
144
+ if base == 255
145
+ (n.to_f * base / 100.0).round
146
+ else
147
+ n.to_f
148
+ end
149
+ end,
150
+ Unit::DEG => proc {|n| n.to_f },
151
+ Unit::GRAD => proc {|n| n.to_f * 9 / 10 },
152
+ Unit::TURN => proc {|n| n.to_f * 360 },
153
+ Unit::RAD => proc {|n| n.to_f * 180 / Math::PI }
154
+ }
155
+
156
+ UNIT_CONV.default = proc {|n| /\./ =~ n ? n.to_f : n.to_i }
157
+ UNIT_CONV.freeze
158
+
159
+ private_constant :UNIT_CONV
160
+
29
161
  ##
30
162
  # @!attribute [r] scheme
31
163
  # @return [String] Type of function: 'rgb' or 'hsl'
32
164
  # @!attribute [r] source
33
- # @return [String] The original RGB/HSL function before the conversion
165
+ # @return [String] The original RGB/HSL/HWB function before
166
+ # the conversion
34
167
 
35
168
  attr_reader :scheme, :source
36
169
 
@@ -55,7 +188,7 @@ module ColorContrastCalc
55
188
  private :normalize_params
56
189
 
57
190
  ##
58
- # Return the RGB value gained from a RGB/HSL function.
191
+ # Return the RGB value gained from a RGB/HSL/HWB function.
59
192
  #
60
193
  # @return [Array<Integer>] RGB value represented as an array
61
194
 
@@ -64,11 +197,12 @@ module ColorContrastCalc
64
197
  end
65
198
 
66
199
  ##
67
- # Return the parameters of a RGB/HSL function as an array of
200
+ # Return the parameters of a RGB/HSL/HWB function as an array of
68
201
  # Integer/Float.
69
202
  # The unit for H, S, L is assumed to be deg, %, % respectively.
70
203
  #
71
- # @return [Array<Integer, Float>] RGB/HSL value represented as an array
204
+ # @return [Array<Integer, Float>] RGB/HSL/HWB value represented
205
+ # as an array
72
206
 
73
207
  def to_a
74
208
  @normalized
@@ -78,11 +212,7 @@ module ColorContrastCalc
78
212
  class Rgb < self
79
213
  def normalize_params
80
214
  @params.map do |param|
81
- if param[:unit] == '%'
82
- (param[:number] * 255.0 / 100).round
83
- else
84
- param[:number].to_i
85
- end
215
+ UNIT_CONV[param[:unit]][param[:number], 255]
86
216
  end
87
217
  end
88
218
 
@@ -93,7 +223,7 @@ module ColorContrastCalc
93
223
  class Hsl < self
94
224
  def normalize_params
95
225
  @params.map do |param|
96
- param[:number].to_f
226
+ UNIT_CONV[param[:unit]][param[:number]]
97
227
  end
98
228
  end
99
229
 
@@ -102,13 +232,29 @@ module ColorContrastCalc
102
232
  end
103
233
  end
104
234
 
235
+ # @private
236
+ class Hwb < self
237
+ def normalize_params
238
+ @params.map do |param|
239
+ UNIT_CONV[param[:unit]][param[:number]]
240
+ end
241
+ end
242
+
243
+ def rgb
244
+ Utils.hwb_to_rgb(to_a)
245
+ end
246
+ end
247
+
105
248
  # @private
106
249
  def self.create(parsed_value, original_value)
250
+ Validator.validate(parsed_value, original_value)
107
251
  case parsed_value[:scheme]
108
252
  when Scheme::RGB
109
253
  Rgb.new(parsed_value, original_value)
110
254
  when Scheme::HSL
111
255
  Hsl.new(parsed_value, original_value)
256
+ when Scheme::HWB
257
+ Hwb.new(parsed_value, original_value)
112
258
  end
113
259
  end
114
260
  end
@@ -116,127 +262,197 @@ module ColorContrastCalc
116
262
  # @private
117
263
  module TokenRe
118
264
  SPACES = /\s+/.freeze
119
- SCHEME = /(rgb|hsl)/i.freeze
265
+ SCHEME = /(rgb|hsl|hwb)/i.freeze
120
266
  OPEN_PAREN = /\(/.freeze
121
267
  CLOSE_PAREN = /\)/.freeze
122
268
  COMMA = /,/.freeze
123
269
  NUMBER = /(\d+)(:?\.\d+)?/.freeze
124
- UNIT = /(%|deg)/.freeze
270
+ UNIT = /(%|deg|grad|rad|turn)/.freeze
125
271
  end
126
272
 
127
- def self.format_error_message(scanner, re)
128
- out = StringIO.new
129
- color_value = scanner.string
130
- [
131
- format('"%s" is not a valid code. An error occurred at:', color_value),
132
- color_value,
133
- "#{' ' * scanner.charpos}^ while searching with #{re}"
134
- ].each do |line|
135
- out.puts line
273
+ class Parser
274
+ class << self
275
+ attr_accessor :parsers
136
276
  end
137
277
 
138
- out.string
139
- end
278
+ def skip_spaces!(scanner)
279
+ scanner.scan(TokenRe::SPACES)
280
+ end
140
281
 
141
- private_class_method :format_error_message
282
+ private :skip_spaces!
142
283
 
143
- def self.skip_spaces!(scanner)
144
- scanner.scan(TokenRe::SPACES)
145
- end
284
+ def read_scheme!(scanner)
285
+ scheme = read_token!(scanner, TokenRe::SCHEME).downcase
146
286
 
147
- private_class_method :skip_spaces!
287
+ parsed_value = {
288
+ scheme: scheme,
289
+ parameters: []
290
+ }
148
291
 
149
- def self.read_token!(scanner, re)
150
- skip_spaces!(scanner)
151
- token = scanner.scan(re)
292
+ parser = Parser.parsers[scheme] || self
152
293
 
153
- return token if token
294
+ parser.read_open_paren!(scanner, parsed_value)
295
+ end
154
296
 
155
- error_message = format_error_message(scanner, re)
156
- raise InvalidColorRepresentationError, error_message
157
- end
297
+ def format_error_message(scanner, re)
298
+ out = StringIO.new
299
+ color_value = scanner.string
158
300
 
159
- private_class_method :read_token!
301
+ out.print format('"%s" is not a valid code. ', color_value)
302
+ print_error_pos!(out, color_value, scanner.charpos)
303
+ out.puts " while searching with #{re}"
160
304
 
161
- def self.read_scheme!(scanner)
162
- scheme = read_token!(scanner, TokenRe::SCHEME)
305
+ out.string
306
+ end
163
307
 
164
- parsed_value = {
165
- scheme: scheme.downcase,
166
- parameters: []
167
- }
308
+ private :format_error_message
168
309
 
169
- read_open_paren!(scanner, parsed_value)
170
- end
310
+ def print_error_pos!(out, color_value, pos)
311
+ out.puts 'An error occurred at:'
312
+ out.puts color_value
313
+ out.print "#{' ' * pos}^"
314
+ end
171
315
 
172
- private_class_method :read_scheme!
316
+ private :print_error_pos!
173
317
 
174
- def self.read_open_paren!(scanner, parsed_value)
175
- read_token!(scanner, TokenRe::OPEN_PAREN)
318
+ def read_token!(scanner, re)
319
+ skip_spaces!(scanner)
320
+ token = scanner.scan(re)
176
321
 
177
- read_parameters!(scanner, parsed_value)
178
- end
322
+ return token if token
179
323
 
180
- private_class_method :read_open_paren!
324
+ error_message = format_error_message(scanner, re)
325
+ raise InvalidColorRepresentationError, error_message
326
+ end
181
327
 
182
- def self.read_close_paren!(scanner)
183
- scanner.scan(TokenRe::CLOSE_PAREN)
184
- end
328
+ private :read_token!
185
329
 
186
- private_class_method :read_close_paren!
330
+ def read_open_paren!(scanner, parsed_value)
331
+ read_token!(scanner, TokenRe::OPEN_PAREN)
187
332
 
188
- def self.read_parameters!(scanner, parsed_value)
189
- read_number!(scanner, parsed_value)
190
- end
333
+ read_parameters!(scanner, parsed_value)
334
+ end
191
335
 
192
- private_class_method :read_parameters!
336
+ protected :read_open_paren!
193
337
 
194
- def self.read_number!(scanner, parsed_value)
195
- number = read_token!(scanner, TokenRe::NUMBER)
338
+ def read_close_paren!(scanner)
339
+ scanner.scan(TokenRe::CLOSE_PAREN)
340
+ end
196
341
 
197
- parsed_value[:parameters].push({ number: number, unit: nil })
342
+ private :read_close_paren!
198
343
 
199
- read_unit!(scanner, parsed_value)
200
- end
344
+ def read_parameters!(scanner, parsed_value)
345
+ read_number!(scanner, parsed_value)
346
+ end
347
+
348
+ private :read_parameters!
349
+
350
+ def read_number!(scanner, parsed_value)
351
+ number = read_token!(scanner, TokenRe::NUMBER)
352
+
353
+ parsed_value[:parameters].push({ number: number, unit: nil })
201
354
 
202
- private_class_method :read_number!
355
+ read_unit!(scanner, parsed_value)
356
+ end
357
+
358
+ private :read_number!
203
359
 
204
- def self.read_unit!(scanner, parsed_value)
205
- unit = scanner.scan(TokenRe::UNIT)
360
+ def read_unit!(scanner, parsed_value)
361
+ unit = scanner.scan(TokenRe::UNIT)
206
362
 
207
- parsed_value[:parameters].last[:unit] = unit if unit
363
+ parsed_value[:parameters].last[:unit] = unit if unit
364
+
365
+ read_comma!(scanner, parsed_value)
366
+ end
367
+
368
+ private :read_unit!
369
+
370
+ def next_spaces_as_separator?(scanner)
371
+ cur_pos = scanner.pos
372
+ spaces = skip_spaces!(scanner)
373
+ next_token_is_number = scanner.check(TokenRe::NUMBER)
374
+ scanner.pos = cur_pos
375
+ spaces && next_token_is_number
376
+ end
208
377
 
209
- read_comma!(scanner, parsed_value)
378
+ private :next_spaces_as_separator?
379
+
380
+ def read_comma!(scanner, parsed_value)
381
+ if next_spaces_as_separator?(scanner)
382
+ return read_number!(scanner, parsed_value)
383
+ end
384
+
385
+ skip_spaces!(scanner)
386
+
387
+ return parsed_value if read_close_paren!(scanner)
388
+
389
+ read_token!(scanner, TokenRe::COMMA)
390
+ read_number!(scanner, parsed_value)
391
+ end
392
+
393
+ private :read_comma!
210
394
  end
211
395
 
212
- private_class_method :read_unit!
396
+ class FunctionParser < Parser
397
+ def read_comma!(scanner, parsed_value)
398
+ if next_spaces_as_separator?(scanner)
399
+ return read_number!(scanner, parsed_value)
400
+ end
401
+
402
+ skip_spaces!(scanner)
403
+
404
+ if scanner.check(TokenRe::COMMA)
405
+ wrong_separator_error(scanner, parsed_value)
406
+ end
407
+
408
+ return parsed_value if read_close_paren!(scanner)
213
409
 
214
- def self.read_comma!(scanner, parsed_value)
215
- skip_spaces!(scanner)
410
+ read_number!(scanner, parsed_value)
411
+ end
412
+
413
+ def report_wrong_separator!(scanner, parsed_value)
414
+ out = StringIO.new
415
+ color_value = scanner.string
416
+ scheme = parsed_value[:scheme].upcase
417
+ # The trailing space after the first message is intentional,
418
+ # because it is immediately followed by another message.
419
+ out.print "\",\" is not a valid separator for #{scheme} functions. "
420
+ print_error_pos!(out, color_value, scanner.charpos)
421
+ out.puts
422
+ out.string
423
+ end
216
424
 
217
- return parsed_value if read_close_paren!(scanner)
425
+ private :report_wrong_separator!
218
426
 
219
- read_token!(scanner, TokenRe::COMMA)
220
- read_number!(scanner, parsed_value)
427
+ def wrong_separator_error(scanner, parsed_value)
428
+ error_message = report_wrong_separator!(scanner, parsed_value)
429
+ raise InvalidColorRepresentationError, error_message
430
+ end
431
+
432
+ private :wrong_separator_error
221
433
  end
222
434
 
223
- private_class_method :read_comma!
435
+ Parser.parsers = {
436
+ Scheme::HWB => FunctionParser.new
437
+ }
438
+
439
+ MAIN_PARSER = Parser.new
224
440
 
225
441
  ##
226
- # Parse an RGB/HSL function and store the result as an instance of
442
+ # Parse an RGB/HSL/HWB function and store the result as an instance of
227
443
  # ColorFunctionParser::Converter.
228
444
  #
229
- # @param color_value [String] RGB/HSL function defined at
230
- # https://www.w3.org/TR/css-color-4/
445
+ # @param color_value [String] RGB/HSL/HWB function defined at
446
+ # https://www.w3.org/TR/2019/WD-css-color-4-20191105/
231
447
  # @return [Converter] An instance of ColorFunctionParser::Converter
232
448
 
233
449
  def self.parse(color_value)
234
- parsed_value = read_scheme!(StringScanner.new(color_value))
450
+ parsed_value = MAIN_PARSER.read_scheme!(StringScanner.new(color_value))
235
451
  Converter.create(parsed_value, color_value)
236
452
  end
237
453
 
238
454
  ##
239
- # Return An RGB value gained from an RGB/HSL function.
455
+ # Return An RGB value gained from an RGB/HSL/HWB function.
240
456
  #
241
457
  # @return [Array<Integer>] RGB value represented as an array
242
458
 
@@ -118,6 +118,7 @@
118
118
  ["plum", "#dda0dd"],
119
119
  ["powderblue", "#b0e0e6"],
120
120
  ["purple", "#800080"],
121
+ ["rebeccapurple", "#663399"],
121
122
  ["red", "#ff0000"],
122
123
  ["rosybrown", "#bc8f8f"],
123
124
  ["royalblue", "#4169e1"],
@@ -6,7 +6,7 @@ module ColorContrastCalc
6
6
 
7
7
  module Deprecated
8
8
  def self.warn(old_method, new_method)
9
- STDERR.puts "##{old_method} is deprecated. Use ##{new_method} instead"
9
+ Kernel.warn "##{old_method} is deprecated. Use ##{new_method} instead"
10
10
  end
11
11
 
12
12
  module Color
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ColorContrastCalc
2
4
  ##
3
5
  # Error raised if creating a Color instance with invalid value.
@@ -236,6 +236,40 @@ module ColorContrastCalc
236
236
  def self.uppercase?(str)
237
237
  !/[[:lower:]]/.match?(str)
238
238
  end
239
+
240
+ module Hwb
241
+ ##
242
+ # ref: https://www.w3.org/TR/2019/WD-css-color-4-20191105/
243
+
244
+ def normalize_hwb(hwb)
245
+ h, w, b = hwb
246
+
247
+ achromatic_percent = w + b
248
+ denominator = achromatic_percent > 100 ? achromatic_percent : 100
249
+
250
+ normalized_w = w.to_f / denominator
251
+ normalized_b = b.to_f / denominator
252
+
253
+ [h, normalized_w, normalized_b]
254
+ end
255
+
256
+ private :normalize_hwb
257
+
258
+ def hwb_to_rgb(hwb)
259
+ hue, white, black = normalize_hwb(hwb)
260
+ rgb = Utils.hsl_to_rgb([hue, 100, 50])
261
+
262
+ rgb.map do |c|
263
+ ((c * (1.0 - white - black)) + white * 255).round
264
+ end
265
+ end
266
+
267
+ def rgb_to_hwb(_rgb)
268
+ raise Notimplementederror, 'Must be implemented later'
269
+ end
270
+ end
271
+
272
+ extend Hwb
239
273
  end
240
274
 
241
275
  ##
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ColorContrastCalc
4
- VERSION = '0.6.1'
4
+ VERSION = '0.7.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.6.1
4
+ version: 0.7.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-01-05 00:00:00.000000000 Z
11
+ date: 2020-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.49.1
61
+ version: '0.79'
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.49.1
68
+ version: '0.79'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yard
71
71
  requirement: !ruby/object:Gem::Requirement