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