sass-embedded 1.54.6-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +42 -0
  4. data/ext/sass/embedded.rb +9 -0
  5. data/ext/sass/embedded_sass_pb.rb +349 -0
  6. data/ext/sass/sass_embedded/dart-sass-embedded +17 -0
  7. data/ext/sass/sass_embedded/src/LICENSE +1476 -0
  8. data/ext/sass/sass_embedded/src/dart +0 -0
  9. data/ext/sass/sass_embedded/src/dart-sass-embedded.snapshot +0 -0
  10. data/lib/sass/compile_error.rb +28 -0
  11. data/lib/sass/compile_result.rb +23 -0
  12. data/lib/sass/embedded/async.rb +65 -0
  13. data/lib/sass/embedded/channel.rb +61 -0
  14. data/lib/sass/embedded/compiler.rb +60 -0
  15. data/lib/sass/embedded/dispatcher.rb +90 -0
  16. data/lib/sass/embedded/host/function_registry.rb +90 -0
  17. data/lib/sass/embedded/host/importer_registry.rb +108 -0
  18. data/lib/sass/embedded/host/logger_registry.rb +50 -0
  19. data/lib/sass/embedded/host/value_protofier.rb +241 -0
  20. data/lib/sass/embedded/host.rb +141 -0
  21. data/lib/sass/embedded/protofier.rb +78 -0
  22. data/lib/sass/embedded/structifier.rb +36 -0
  23. data/lib/sass/embedded/varint.rb +35 -0
  24. data/lib/sass/embedded/version.rb +7 -0
  25. data/lib/sass/embedded.rb +245 -0
  26. data/lib/sass/logger/silent.rb +26 -0
  27. data/lib/sass/logger/source_location.rb +21 -0
  28. data/lib/sass/logger/source_span.rb +27 -0
  29. data/lib/sass/script_error.rb +6 -0
  30. data/lib/sass/value/argument_list.rb +30 -0
  31. data/lib/sass/value/boolean.rb +52 -0
  32. data/lib/sass/value/color.rb +253 -0
  33. data/lib/sass/value/function.rb +54 -0
  34. data/lib/sass/value/fuzzy_math.rb +81 -0
  35. data/lib/sass/value/list.rb +79 -0
  36. data/lib/sass/value/map.rb +71 -0
  37. data/lib/sass/value/null.rb +48 -0
  38. data/lib/sass/value/number/unit.rb +186 -0
  39. data/lib/sass/value/number.rb +358 -0
  40. data/lib/sass/value/string.rb +55 -0
  41. data/lib/sass/value.rb +132 -0
  42. data/lib/sass-embedded.rb +4 -0
  43. metadata +186 -0
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../ext/sass/embedded'
4
+ require_relative '../../ext/sass/embedded_sass_pb'
5
+ require_relative 'compile_error'
6
+ require_relative 'compile_result'
7
+ require_relative 'embedded/async'
8
+ require_relative 'embedded/channel'
9
+ require_relative 'embedded/compiler'
10
+ require_relative 'embedded/dispatcher'
11
+ require_relative 'embedded/host'
12
+ require_relative 'embedded/protofier'
13
+ require_relative 'embedded/structifier'
14
+ require_relative 'embedded/varint'
15
+ require_relative 'embedded/version'
16
+ require_relative 'logger/silent'
17
+ require_relative 'logger/source_location'
18
+ require_relative 'logger/source_span'
19
+ require_relative 'value'
20
+
21
+ # The Sass module.
22
+ #
23
+ # This communicates with Embedded Dart Sass using the Embedded Sass protocol.
24
+ #
25
+ # @example
26
+ # Sass.compile('style.scss')
27
+ #
28
+ # @example
29
+ # Sass.compile_string('h1 { font-size: 40px; }')
30
+ module Sass
31
+ class << self
32
+ # Compiles the Sass file at +path+ to CSS.
33
+ # @param (see Embedded#compile)
34
+ # @return (see Embedded#compile)
35
+ # @raise (see Embedded#compile)
36
+ # @see Embedded#compile
37
+ def compile(path, **kwargs)
38
+ instance.compile(path, **kwargs)
39
+ end
40
+
41
+ # Compiles a stylesheet whose contents is +source+ to CSS.
42
+ # @param (see Embedded#compile_string)
43
+ # @return (see Embedded#compile_string)
44
+ # @raise (see Embedded#compile_string)
45
+ # @see Embedded#compile_string
46
+ def compile_string(source, **kwargs)
47
+ instance.compile_string(source, **kwargs)
48
+ end
49
+
50
+ # @param (see Embedded#info)
51
+ # @return (see Embedded#info)
52
+ # @raise (see Embedded#info)
53
+ # @see Embedded#info
54
+ def info
55
+ instance.info
56
+ end
57
+
58
+ private
59
+
60
+ def instance
61
+ if defined? @instance
62
+ @instance = Embedded.new if @instance.closed?
63
+ else
64
+ @instance = Embedded.new
65
+ at_exit do
66
+ @instance.close
67
+ end
68
+ end
69
+ @instance
70
+ end
71
+ end
72
+
73
+ # The {Embedded} host for using dart-sass-embedded. Each instance creates
74
+ # its own communication {Channel} with a dedicated compiler process.
75
+ #
76
+ # @example
77
+ # embedded = Sass::Embedded.new
78
+ # result = embedded.compile_string('h1 { font-size: 40px; }')
79
+ # result = embedded.compile('style.scss')
80
+ # embedded.close
81
+ class Embedded
82
+ def initialize
83
+ @channel = Channel.new
84
+ end
85
+
86
+ # Compiles the Sass file at +path+ to CSS.
87
+ # @param path [String]
88
+ # @param load_paths [Array<String>] Paths in which to look for stylesheets loaded by rules like
89
+ # {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
90
+ # @param charset [Boolean] By default, if the CSS document contains non-ASCII characters, Sass adds a +@charset+
91
+ # declaration (in expanded output mode) or a byte-order mark (in compressed mode) to indicate its encoding to
92
+ # browsers or other consumers. If +charset+ is +false+, these annotations are omitted.
93
+ # @param source_map [Boolean] Whether or not Sass should generate a source map.
94
+ # @param source_map_include_sources [Boolean] Whether Sass should include the sources in the generated source map.
95
+ # @param style [String, Symbol] The OutputStyle of the compiled CSS.
96
+ # @param functions [Hash<String, Proc>] Additional built-in Sass functions that are available in all stylesheets.
97
+ # @param importers [Array<Object>] Custom importers that control how Sass resolves loads from rules like
98
+ # {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
99
+ # @param alert_ascii [Boolean] If this is +true+, the compiler will exclusively use ASCII characters in its error
100
+ # and warning messages. Otherwise, it may use non-ASCII Unicode characters as well.
101
+ # @param alert_color [Boolean] If this is +true+, the compiler will use ANSI color escape codes in its error and
102
+ # warning messages. If it's +false+, it won't use these. If it's +nil+, the compiler will determine whether or
103
+ # not to use colors depending on whether the user is using an interactive terminal.
104
+ # @param logger [Object] An object to use to handle warnings and/or debug messages from Sass.
105
+ # @param quiet_deps [Boolean] If this option is set to +true+, Sass won’t print warnings that are caused by
106
+ # dependencies. A “dependency” is defined as any file that’s loaded through +load_paths+ or +importer+.
107
+ # Stylesheets that are imported relative to the entrypoint are not considered dependencies.
108
+ # @param verbose [Boolean] By default, Dart Sass will print only five instances of the same deprecation warning per
109
+ # compilation to avoid deluging users in console noise. If you set verbose to +true+, it will instead print every
110
+ # deprecation warning it encounters.
111
+ # @return [CompileResult]
112
+ # @raise [CompileError]
113
+ # @see https://sass-lang.com/documentation/js-api/modules#compile
114
+ def compile(path,
115
+ load_paths: [],
116
+
117
+ charset: true,
118
+ source_map: false,
119
+ source_map_include_sources: false,
120
+ style: :expanded,
121
+
122
+ functions: {},
123
+ importers: [],
124
+
125
+ alert_ascii: false,
126
+ alert_color: nil,
127
+ logger: nil,
128
+ quiet_deps: false,
129
+ verbose: false)
130
+ raise ArgumentError, 'path must be set' if path.nil?
131
+
132
+ Protofier.from_proto_compile_response(
133
+ Host.new(@channel).compile_request(
134
+ path: path,
135
+ source: nil,
136
+ importer: nil,
137
+ load_paths: load_paths,
138
+ syntax: nil,
139
+ url: nil,
140
+ charset: charset,
141
+ source_map: source_map,
142
+ source_map_include_sources: source_map_include_sources,
143
+ style: style,
144
+ functions: functions,
145
+ importers: importers,
146
+ alert_color: alert_color,
147
+ alert_ascii: alert_ascii,
148
+ logger: logger,
149
+ quiet_deps: quiet_deps,
150
+ verbose: verbose
151
+ )
152
+ )
153
+ end
154
+
155
+ # Compiles a stylesheet whose contents is +source+ to CSS.
156
+ # @param source [String]
157
+ # @param importer [Object] The importer to use to handle loads that are relative to the entrypoint stylesheet.
158
+ # @param load_paths [Array<String>] Paths in which to look for stylesheets loaded by rules like
159
+ # {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
160
+ # @param syntax [String, Symbol] The Syntax to use to parse the entrypoint stylesheet.
161
+ # @param url [String] The canonical URL of the entrypoint stylesheet. If this is passed along with +importer+, it's
162
+ # used to resolve relative loads in the entrypoint stylesheet.
163
+ # @param charset [Boolean] By default, if the CSS document contains non-ASCII characters, Sass adds a +@charset+
164
+ # declaration (in expanded output mode) or a byte-order mark (in compressed mode) to indicate its encoding to
165
+ # browsers or other consumers. If +charset+ is +false+, these annotations are omitted.
166
+ # @param source_map [Boolean] Whether or not Sass should generate a source map.
167
+ # @param source_map_include_sources [Boolean] Whether Sass should include the sources in the generated source map.
168
+ # @param style [String, Symbol] The OutputStyle of the compiled CSS.
169
+ # @param functions [Hash<String, Proc>] Additional built-in Sass functions that are available in all stylesheets.
170
+ # @param importers [Array<Object>] Custom importers that control how Sass resolves loads from rules like
171
+ # {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
172
+ # @param alert_ascii [Boolean] If this is +true+, the compiler will exclusively use ASCII characters in its error
173
+ # and warning messages. Otherwise, it may use non-ASCII Unicode characters as well.
174
+ # @param alert_color [Boolean] If this is +true+, the compiler will use ANSI color escape codes in its error and
175
+ # warning messages. If it's +false+, it won't use these. If it's +nil+, the compiler will determine whether or
176
+ # not to use colors depending on whether the user is using an interactive terminal.
177
+ # @param logger [Object] An object to use to handle warnings and/or debug messages from Sass.
178
+ # @param quiet_deps [Boolean] If this option is set to +true+, Sass won’t print warnings that are caused by
179
+ # dependencies. A “dependency” is defined as any file that’s loaded through +load_paths+ or +importer+.
180
+ # Stylesheets that are imported relative to the entrypoint are not considered dependencies.
181
+ # @param verbose [Boolean] By default, Dart Sass will print only five instances of the same deprecation warning per
182
+ # compilation to avoid deluging users in console noise. If you set verbose to +true+, it will instead print every
183
+ # deprecation warning it encounters.
184
+ # @return [CompileResult]
185
+ # @raise [CompileError]
186
+ # @see https://sass-lang.com/documentation/js-api/modules#compileString
187
+ def compile_string(source,
188
+ importer: nil,
189
+ load_paths: [],
190
+ syntax: :scss,
191
+ url: nil,
192
+
193
+ charset: true,
194
+ source_map: false,
195
+ source_map_include_sources: false,
196
+ style: :expanded,
197
+
198
+ functions: {},
199
+ importers: [],
200
+
201
+ alert_ascii: false,
202
+ alert_color: nil,
203
+ logger: nil,
204
+ quiet_deps: false,
205
+ verbose: false)
206
+ raise ArgumentError, 'source must be set' if source.nil?
207
+
208
+ Protofier.from_proto_compile_response(
209
+ Host.new(@channel).compile_request(
210
+ path: nil,
211
+ source: source,
212
+ importer: importer,
213
+ load_paths: load_paths,
214
+ syntax: syntax,
215
+ url: url,
216
+ charset: charset,
217
+ source_map: source_map,
218
+ source_map_include_sources: source_map_include_sources,
219
+ style: style,
220
+ functions: functions,
221
+ importers: importers,
222
+ alert_color: alert_color,
223
+ alert_ascii: alert_ascii,
224
+ logger: logger,
225
+ quiet_deps: quiet_deps,
226
+ verbose: verbose
227
+ )
228
+ )
229
+ end
230
+
231
+ # @return [String] Information about the Sass implementation.
232
+ # @see https://sass-lang.com/documentation/js-api/modules#info
233
+ def info
234
+ @info ||= "sass-embedded\t#{Host.new(@channel).version_request.implementation_version}"
235
+ end
236
+
237
+ def close
238
+ @channel.close
239
+ end
240
+
241
+ def closed?
242
+ @channel.closed?
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # A namespace for built-in Loggers.
5
+ #
6
+ # @see https://sass-lang.com/documentation/js-api/modules/Logger
7
+ module Logger
8
+ module_function
9
+
10
+ # A Logger that silently ignores all warnings and debug messages.
11
+ def silent
12
+ Silent
13
+ end
14
+
15
+ # A Logger that silently ignores all warnings and debug messages.
16
+ module Silent
17
+ module_function
18
+
19
+ def warn(message, deprecation: false, span: nil, stack: nil); end
20
+
21
+ def debug(message, span: nil); end
22
+ end
23
+
24
+ private_constant :Silent
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ module Logger
5
+ # A specific location within a source file.
6
+ #
7
+ # This is always associated with a {SourceSpan} which indicates which file it refers to.
8
+ #
9
+ # @see https://sass-lang.com/documentation/js-api/interfaces/SourceLocation
10
+ class SourceLocation
11
+ # @return [Integer]
12
+ attr_reader :offset, :line, :column
13
+
14
+ def initialize(offset, line, column)
15
+ @offset = offset
16
+ @line = line
17
+ @column = column
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ module Logger
5
+ # A span of text within a source file.
6
+ #
7
+ # @see https://sass-lang.com/documentation/js-api/interfaces/SourceSpan
8
+ class SourceSpan
9
+ # @return [SourceLocation]
10
+ attr_reader :start, :end
11
+
12
+ # @return [String]
13
+ attr_reader :text
14
+
15
+ # @return [String, nil]
16
+ attr_reader :url, :context
17
+
18
+ def initialize(start, end_, text, url, context)
19
+ @start = start
20
+ @end = end_
21
+ @text = text
22
+ @url = url == '' ? nil : url
23
+ @context = context == '' ? nil : context
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # An exception thrown by Sass Script.
5
+ class ScriptError < StandardError; end
6
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ module Value
5
+ # Sass's argument list type.
6
+ #
7
+ # An argument list comes from a rest argument. It's distinct from a normal {List} in that it may contain a keyword
8
+ # map as well as the positional arguments.
9
+ #
10
+ # @see https://sass-lang.com/documentation/js-api/classes/SassArgumentList
11
+ class ArgumentList < Value::List
12
+ # @param contents [Array<Value>]
13
+ # @param keywords [Hash<::String, Value>]
14
+ # @param separator [::String]
15
+ def initialize(contents = [], keywords = {}, separator = ',')
16
+ super(contents, separator: separator)
17
+
18
+ @id = 0
19
+ @keywords_accessed = false
20
+ @keywords = keywords.transform_keys(&:to_s).freeze
21
+ end
22
+
23
+ # @return [Hash<::String, Value>]
24
+ def keywords
25
+ @keywords_accessed = true
26
+ @keywords
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ module Value
5
+ # Sass's boolean type.
6
+ #
7
+ # @see https://sass-lang.com/documentation/js-api/classes/SassBoolean
8
+ class Boolean
9
+ include Value
10
+
11
+ # @param value [::Boolean]
12
+ def initialize(value)
13
+ @value = value
14
+ end
15
+
16
+ # @return [::Boolean]
17
+ attr_reader :value
18
+
19
+ # @return [Boolean]
20
+ def !
21
+ value ? Boolean::FALSE : Boolean::TRUE
22
+ end
23
+
24
+ # @return [::Boolean]
25
+ def ==(other)
26
+ other.is_a?(Sass::Value::Boolean) && other.value == value
27
+ end
28
+
29
+ # @return [Integer]
30
+ def hash
31
+ @hash ||= value.hash
32
+ end
33
+
34
+ alias to_bool value
35
+
36
+ # @return [Boolean]
37
+ def assert_boolean(_name = nil)
38
+ self
39
+ end
40
+
41
+ # Sass's true value.
42
+ TRUE = Boolean.new(true)
43
+
44
+ # Sass's false value.
45
+ FALSE = Boolean.new(false)
46
+
47
+ def self.new(value)
48
+ value ? Boolean::TRUE : Boolean::FALSE
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ module Value
5
+ # Sass's color type.
6
+ #
7
+ # No matter what representation was originally used to create this color, all of its channels are accessible.
8
+ #
9
+ # @see https://sass-lang.com/documentation/js-api/classes/SassColor
10
+ class Color
11
+ include Value
12
+
13
+ # @param red [Numeric]
14
+ # @param green [Numeric]
15
+ # @param blue [Numeric]
16
+ # @param hue [Numeric]
17
+ # @param saturation [Numeric]
18
+ # @param lightness [Numeric]
19
+ # @param whiteness [Numeric]
20
+ # @param blackness [Numeric]
21
+ # @param alpha [Numeric]
22
+ def initialize(red: nil,
23
+ green: nil,
24
+ blue: nil,
25
+ hue: nil,
26
+ saturation: nil,
27
+ lightness: nil,
28
+ whiteness: nil,
29
+ blackness: nil,
30
+ alpha: nil)
31
+ @alpha = alpha.nil? ? 1 : FuzzyMath.assert_between(alpha, 0, 1, 'alpha')
32
+ if red && green && blue
33
+ @red = FuzzyMath.assert_between(FuzzyMath.round(red), 0, 255, 'red')
34
+ @green = FuzzyMath.assert_between(FuzzyMath.round(green), 0, 255, 'green')
35
+ @blue = FuzzyMath.assert_between(FuzzyMath.round(blue), 0, 255, 'blue')
36
+ elsif hue && saturation && lightness
37
+ @hue = hue % 360
38
+ @saturation = FuzzyMath.assert_between(saturation, 0, 100, 'saturation')
39
+ @lightness = FuzzyMath.assert_between(lightness, 0, 100, 'lightness')
40
+ elsif hue && whiteness && blackness
41
+ @hue = hue % 360
42
+ @whiteness = FuzzyMath.assert_between(whiteness, 0, 100, 'whiteness')
43
+ @blackness = FuzzyMath.assert_between(blackness, 0, 100, 'blackness')
44
+ hwb_to_rgb
45
+ @whiteness = @blackness = nil
46
+ else
47
+ raise error 'Invalid Color'
48
+ end
49
+ end
50
+
51
+ # @return [Integer]
52
+ def red
53
+ hsl_to_rgb unless defined? @red
54
+
55
+ @red
56
+ end
57
+
58
+ # @return [Integer]
59
+ def green
60
+ hsl_to_rgb unless defined? @green
61
+
62
+ @green
63
+ end
64
+
65
+ # @return [Integer]
66
+ def blue
67
+ hsl_to_rgb unless defined? @blue
68
+
69
+ @blue
70
+ end
71
+
72
+ # @return [Numeric]
73
+ def hue
74
+ rgb_to_hsl unless defined? @hue
75
+
76
+ @hue
77
+ end
78
+
79
+ # @return [Numeric]
80
+ def saturation
81
+ rgb_to_hsl unless defined? @saturation
82
+
83
+ @saturation
84
+ end
85
+
86
+ # @return [Numeric]
87
+ def lightness
88
+ rgb_to_hsl unless defined? @lightness
89
+
90
+ @lightness
91
+ end
92
+
93
+ # @return [Numeric]
94
+ def whiteness
95
+ @whiteness ||= Rational([red, green, blue].min, 255) * 100
96
+ end
97
+
98
+ # @return [Numeric]
99
+ def blackness
100
+ @blackness ||= 100 - (Rational([red, green, blue].max, 255) * 100)
101
+ end
102
+
103
+ # @return [Numeric]
104
+ attr_reader :alpha
105
+
106
+ # @param red [Numeric]
107
+ # @param green [Numeric]
108
+ # @param blue [Numeric]
109
+ # @param hue [Numeric]
110
+ # @param saturation [Numeric]
111
+ # @param lightness [Numeric]
112
+ # @param whiteness [Numeric]
113
+ # @param blackness [Numeric]
114
+ # @param alpha [Numeric]
115
+ # @return [Color]
116
+ def change(red: nil,
117
+ green: nil,
118
+ blue: nil,
119
+ hue: nil,
120
+ saturation: nil,
121
+ lightness: nil,
122
+ whiteness: nil,
123
+ blackness: nil,
124
+ alpha: nil)
125
+ if whiteness || blackness
126
+ Sass::Value::Color.new(hue: hue || self.hue,
127
+ whiteness: whiteness || self.whiteness,
128
+ blackness: blackness || self.blackness,
129
+ alpha: alpha || self.alpha)
130
+ elsif hue || saturation || lightness
131
+ Sass::Value::Color.new(hue: hue || self.hue,
132
+ saturation: saturation || self.saturation,
133
+ lightness: lightness || self.lightness,
134
+ alpha: alpha || self.alpha)
135
+ elsif red || green || blue
136
+ Sass::Value::Color.new(red: red ? FuzzyMath.round(red) : self.red,
137
+ green: green ? FuzzyMath.round(green) : self.green,
138
+ blue: blue ? FuzzyMath.round(blue) : self.blue,
139
+ alpha: alpha || self.alpha)
140
+ else
141
+ dup.instance_eval do
142
+ @alpha = FuzzyMath.assert_between(alpha, 0, 1, 'alpha')
143
+ self
144
+ end
145
+ end
146
+ end
147
+
148
+ # @return [::Boolean]
149
+ def ==(other)
150
+ other.is_a?(Sass::Value::Color) &&
151
+ other.red == red &&
152
+ other.green == green &&
153
+ other.blue == blue &&
154
+ other.alpha == alpha
155
+ end
156
+
157
+ # @return [Integer]
158
+ def hash
159
+ @hash ||= [red, green, blue, alpha].hash
160
+ end
161
+
162
+ # @return [Color]
163
+ def assert_color(_name = nil)
164
+ self
165
+ end
166
+
167
+ private
168
+
169
+ def rgb_to_hsl
170
+ scaled_red = Rational(red, 255)
171
+ scaled_green = Rational(green, 255)
172
+ scaled_blue = Rational(blue, 255)
173
+
174
+ max = [scaled_red, scaled_green, scaled_blue].max
175
+ min = [scaled_red, scaled_green, scaled_blue].min
176
+ delta = max - min
177
+
178
+ if max == min
179
+ @hue = 0
180
+ elsif max == scaled_red
181
+ @hue = (60 * (scaled_green - scaled_blue) / delta) % 360
182
+ elsif max == scaled_green
183
+ @hue = (120 + (60 * (scaled_blue - scaled_red) / delta)) % 360
184
+ elsif max == scaled_blue
185
+ @hue = (240 + (60 * (scaled_red - scaled_green) / delta)) % 360
186
+ end
187
+
188
+ lightness = @lightness = 50 * (max + min)
189
+
190
+ @saturation = if max == min
191
+ 0
192
+ elsif lightness < 50
193
+ 100 * delta / (max + min)
194
+ else
195
+ 100 * delta / (2 - max - min)
196
+ end
197
+ end
198
+
199
+ def hsl_to_rgb
200
+ scaled_hue = Rational(hue, 360)
201
+ scaled_saturation = Rational(saturation, 100)
202
+ scaled_lightness = Rational(lightness, 100)
203
+
204
+ tmp2 = if scaled_lightness <= 0.5
205
+ scaled_lightness * (scaled_saturation + 1)
206
+ else
207
+ scaled_lightness + scaled_saturation - (scaled_lightness * scaled_saturation)
208
+ end
209
+ tmp1 = (scaled_lightness * 2) - tmp2
210
+ @red = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue + Rational(1, 3)) * 255)
211
+ @green = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue) * 255)
212
+ @blue = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue - Rational(1, 3)) * 255)
213
+ end
214
+
215
+ def hsl_hue_to_rgb(tmp1, tmp2, hue)
216
+ hue += 1 if hue.negative?
217
+ hue -= 1 if hue > 1
218
+
219
+ if hue < Rational(1, 6)
220
+ tmp1 + ((tmp2 - tmp1) * hue * 6)
221
+ elsif hue < Rational(1, 2)
222
+ tmp2
223
+ elsif hue < Rational(2, 3)
224
+ tmp1 + ((tmp2 - tmp1) * (Rational(2, 3) - hue) * 6)
225
+ else
226
+ tmp1
227
+ end
228
+ end
229
+
230
+ def hwb_to_rgb
231
+ scaled_hue = Rational(hue, 360)
232
+ scaled_whiteness = Rational(whiteness, 100)
233
+ scaled_blackness = Rational(blackness, 100)
234
+
235
+ sum = scaled_whiteness + scaled_blackness
236
+ if sum > 1
237
+ scaled_whiteness /= sum
238
+ scaled_blackness /= sum
239
+ end
240
+
241
+ factor = 1 - scaled_whiteness - scaled_blackness
242
+ @red = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue + Rational(1, 3))
243
+ @green = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue)
244
+ @blue = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue - Rational(1, 3))
245
+ end
246
+
247
+ def hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue)
248
+ channel = (hsl_hue_to_rgb(0, 1, scaled_hue) * factor) + scaled_whiteness
249
+ FuzzyMath.round(channel * 255)
250
+ end
251
+ end
252
+ end
253
+ end