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 +4 -4
- data/.rubocop.yml +2 -2
- data/README.ja.md +13 -2
- data/README.md +13 -2
- data/color_contrast_calc.gemspec +1 -1
- data/examples/color_instance.rb +2 -2
- data/lib/color_contrast_calc.rb +35 -6
- data/lib/color_contrast_calc/color.rb +17 -14
- data/lib/color_contrast_calc/color_function_parser.rb +298 -82
- data/lib/color_contrast_calc/data/color_keywords.json +1 -0
- data/lib/color_contrast_calc/deprecated.rb +1 -1
- data/lib/color_contrast_calc/invalid_color_representation_error.rb +2 -0
- data/lib/color_contrast_calc/utils.rb +34 -0
- data/lib/color_contrast_calc/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4eca556d535a8e8eda4ad0bf84e1891447b7065823b18cce4b4695f5a7e2beb6
|
4
|
+
data.tar.gz: 2548294c153d699792adceb71818b4a670da7f5faca6e48d67a1f511ae389f91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf7971d631606c8cd89f2c23661cb873f73874877c2668e742683c044f634b508262aca2745077831104bdaeeefb123ee30ca303c9cffbef2bc4298bb7518b35
|
7
|
+
data.tar.gz: 67851e719c99d0c047e86f8634c4c7b5ad6511c924b80f2246197bd01e7781c0ff6959dd05f16dba44a43792c916857c4fd698db4081bb6a06a242af5b0dbd1c
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 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/
|
15
|
+
Naming/MethodParameterName:
|
16
16
|
MinNameLength: 1
|
data/README.ja.md
CHANGED
@@ -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)'
|
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)'
|
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
|
data/color_contrast_calc.gemspec
CHANGED
@@ -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.
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 0.79'
|
30
30
|
spec.add_development_dependency 'yard', '~> 0.9.9'
|
31
31
|
end
|
data/examples/color_instance.rb
CHANGED
@@ -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)'
|
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
|
data/lib/color_contrast_calc.rb
CHANGED
@@ -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,
|
16
|
-
# RGB value represented as an array of integers
|
17
|
-
# as
|
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%)"
|
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,
|
82
|
-
# RGB value represented as an array of integers
|
83
|
-
# as
|
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,
|
110
|
-
#
|
111
|
-
# returned instance.
|
112
|
-
# @param color_value [
|
113
|
-
#
|
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::
|
142
|
-
return
|
144
|
+
if conv.scheme == ColorFunctionParser::Scheme::HSL
|
145
|
+
return from_hsl(conv.to_a, name || color_value)
|
143
146
|
end
|
144
147
|
|
145
|
-
|
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
|
-
#
|
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
|
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
|
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
|
-
|
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]
|
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
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
139
|
-
|
278
|
+
def skip_spaces!(scanner)
|
279
|
+
scanner.scan(TokenRe::SPACES)
|
280
|
+
end
|
140
281
|
|
141
|
-
|
282
|
+
private :skip_spaces!
|
142
283
|
|
143
|
-
|
144
|
-
|
145
|
-
end
|
284
|
+
def read_scheme!(scanner)
|
285
|
+
scheme = read_token!(scanner, TokenRe::SCHEME).downcase
|
146
286
|
|
147
|
-
|
287
|
+
parsed_value = {
|
288
|
+
scheme: scheme,
|
289
|
+
parameters: []
|
290
|
+
}
|
148
291
|
|
149
|
-
|
150
|
-
skip_spaces!(scanner)
|
151
|
-
token = scanner.scan(re)
|
292
|
+
parser = Parser.parsers[scheme] || self
|
152
293
|
|
153
|
-
|
294
|
+
parser.read_open_paren!(scanner, parsed_value)
|
295
|
+
end
|
154
296
|
|
155
|
-
|
156
|
-
|
157
|
-
|
297
|
+
def format_error_message(scanner, re)
|
298
|
+
out = StringIO.new
|
299
|
+
color_value = scanner.string
|
158
300
|
|
159
|
-
|
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
|
-
|
162
|
-
|
305
|
+
out.string
|
306
|
+
end
|
163
307
|
|
164
|
-
|
165
|
-
scheme: scheme.downcase,
|
166
|
-
parameters: []
|
167
|
-
}
|
308
|
+
private :format_error_message
|
168
309
|
|
169
|
-
|
170
|
-
|
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
|
-
|
316
|
+
private :print_error_pos!
|
173
317
|
|
174
|
-
|
175
|
-
|
318
|
+
def read_token!(scanner, re)
|
319
|
+
skip_spaces!(scanner)
|
320
|
+
token = scanner.scan(re)
|
176
321
|
|
177
|
-
|
178
|
-
end
|
322
|
+
return token if token
|
179
323
|
|
180
|
-
|
324
|
+
error_message = format_error_message(scanner, re)
|
325
|
+
raise InvalidColorRepresentationError, error_message
|
326
|
+
end
|
181
327
|
|
182
|
-
|
183
|
-
scanner.scan(TokenRe::CLOSE_PAREN)
|
184
|
-
end
|
328
|
+
private :read_token!
|
185
329
|
|
186
|
-
|
330
|
+
def read_open_paren!(scanner, parsed_value)
|
331
|
+
read_token!(scanner, TokenRe::OPEN_PAREN)
|
187
332
|
|
188
|
-
|
189
|
-
|
190
|
-
end
|
333
|
+
read_parameters!(scanner, parsed_value)
|
334
|
+
end
|
191
335
|
|
192
|
-
|
336
|
+
protected :read_open_paren!
|
193
337
|
|
194
|
-
|
195
|
-
|
338
|
+
def read_close_paren!(scanner)
|
339
|
+
scanner.scan(TokenRe::CLOSE_PAREN)
|
340
|
+
end
|
196
341
|
|
197
|
-
|
342
|
+
private :read_close_paren!
|
198
343
|
|
199
|
-
|
200
|
-
|
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
|
-
|
355
|
+
read_unit!(scanner, parsed_value)
|
356
|
+
end
|
357
|
+
|
358
|
+
private :read_number!
|
203
359
|
|
204
|
-
|
205
|
-
|
360
|
+
def read_unit!(scanner, parsed_value)
|
361
|
+
unit = scanner.scan(TokenRe::UNIT)
|
206
362
|
|
207
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
215
|
-
|
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
|
-
|
425
|
+
private :report_wrong_separator!
|
218
426
|
|
219
|
-
|
220
|
-
|
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
|
-
|
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
|
|
@@ -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
|
##
|
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.
|
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-
|
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.
|
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.
|
68
|
+
version: '0.79'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: yard
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|