unmagic-color 0.1.0 → 0.2.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/CHANGELOG.md +79 -0
- data/README.md +209 -36
- data/data/css.jsonc +150 -0
- data/data/css.txt +148 -0
- data/data/x11.jsonc +660 -0
- data/data/x11.txt +753 -0
- data/lib/unmagic/color/console/banner.rb +55 -0
- data/lib/unmagic/color/console/card.rb +165 -0
- data/lib/unmagic/color/console/help.rb +70 -0
- data/lib/unmagic/color/console/highlighter.rb +114 -0
- data/lib/unmagic/color/gradient/base.rb +252 -0
- data/lib/unmagic/color/gradient/bitmap.rb +91 -0
- data/lib/unmagic/color/gradient/stop.rb +48 -0
- data/lib/unmagic/color/gradient.rb +154 -0
- data/lib/unmagic/color/harmony.rb +293 -0
- data/lib/unmagic/color/hsl/gradient/linear.rb +152 -0
- data/lib/unmagic/color/hsl.rb +136 -21
- data/lib/unmagic/color/oklch/gradient/linear.rb +151 -0
- data/lib/unmagic/color/oklch.rb +115 -12
- data/lib/unmagic/color/rgb/ansi.rb +227 -0
- data/lib/unmagic/color/rgb/gradient/linear.rb +165 -0
- data/lib/unmagic/color/rgb/hex.rb +20 -8
- data/lib/unmagic/color/rgb/named.rb +213 -43
- data/lib/unmagic/color/rgb.rb +325 -22
- data/lib/unmagic/color/units/degrees.rb +233 -0
- data/lib/unmagic/color/units/direction.rb +206 -0
- data/lib/unmagic/color/util/percentage.rb +150 -22
- data/lib/unmagic/color/version.rb +8 -0
- data/lib/unmagic/color.rb +95 -0
- metadata +23 -3
- data/data/rgb.txt +0 -164
|
@@ -3,107 +3,277 @@
|
|
|
3
3
|
module Unmagic
|
|
4
4
|
class Color
|
|
5
5
|
class RGB < Color
|
|
6
|
-
# Named colors support for RGB colors.
|
|
6
|
+
# Named colors support for RGB colors with X11 and CSS/W3C databases.
|
|
7
7
|
#
|
|
8
|
-
# Provides access to
|
|
9
|
-
#
|
|
8
|
+
# Provides access to named colors from two databases:
|
|
9
|
+
# - X11 database (658 colors) - default
|
|
10
|
+
# - CSS/W3C database (148 colors) - accessible via prefix
|
|
10
11
|
#
|
|
11
|
-
# @example Parse a named color
|
|
12
|
+
# @example Parse a named color (uses X11 by default)
|
|
12
13
|
# Unmagic::Color::RGB::Named.parse("goldenrod")
|
|
13
14
|
# #=> RGB instance for #daa520
|
|
14
15
|
#
|
|
16
|
+
# @example Use CSS/W3C database with prefix
|
|
17
|
+
# Unmagic::Color::RGB::Named.parse("css:gray")
|
|
18
|
+
# #=> RGB instance for #808080 (CSS value)
|
|
19
|
+
# Unmagic::Color::RGB::Named.parse("gray")
|
|
20
|
+
# #=> RGB instance for #bebebe (X11 value)
|
|
21
|
+
#
|
|
15
22
|
# @example Case-insensitive and whitespace-tolerant
|
|
16
23
|
# Unmagic::Color::RGB::Named.parse("Golden Rod")
|
|
17
24
|
# #=> RGB instance for #daa520
|
|
18
25
|
# Unmagic::Color::RGB::Named.parse("GOLDENROD")
|
|
19
26
|
# #=> RGB instance for #daa520
|
|
20
27
|
#
|
|
28
|
+
# Five colors have different values between databases:
|
|
29
|
+
# gray/grey (#bebebe X11 vs #808080 CSS), green (#00ff00 X11 vs #008000 CSS),
|
|
30
|
+
# maroon (#b03060 X11 vs #800000 CSS), purple (#a020f0 X11 vs #800080 CSS)
|
|
31
|
+
#
|
|
21
32
|
# @example Check if a name is valid
|
|
22
33
|
# Unmagic::Color::RGB::Named.valid?("goldenrod")
|
|
23
34
|
# #=> true
|
|
24
35
|
# Unmagic::Color::RGB::Named.valid?("notacolor")
|
|
25
36
|
# #=> false
|
|
26
37
|
class Named
|
|
38
|
+
# Database for loading and accessing color data from files.
|
|
39
|
+
#
|
|
40
|
+
# Handles lazy loading, name normalization, and color lookup.
|
|
41
|
+
# @api private
|
|
42
|
+
class Database
|
|
43
|
+
# @return [String, nil] The name of the database
|
|
44
|
+
# @api private
|
|
45
|
+
attr_reader :name
|
|
46
|
+
|
|
47
|
+
# @return [Array<String>] Alternative names for the database
|
|
48
|
+
# @api private
|
|
49
|
+
attr_reader :aliases
|
|
50
|
+
|
|
51
|
+
# Initialize a new color database.
|
|
52
|
+
#
|
|
53
|
+
# @param path [String] Path to the database file
|
|
54
|
+
# @param name [String, nil] The name of the database (e.g., "x11", "css")
|
|
55
|
+
# @param aliases [Array<String>] Alternative names for the database
|
|
56
|
+
def initialize(path:, name: nil, aliases: [])
|
|
57
|
+
@filepath = path
|
|
58
|
+
@name = name
|
|
59
|
+
@aliases = aliases
|
|
60
|
+
@data = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Lookup color by name, returns RGB color or nil.
|
|
64
|
+
#
|
|
65
|
+
# @param color_name [String] The color name to lookup
|
|
66
|
+
# @return [RGB, nil] The RGB color instance or nil if not found
|
|
67
|
+
def [](color_name)
|
|
68
|
+
normalized = normalize_name(color_name)
|
|
69
|
+
int_value = data[normalized]
|
|
70
|
+
int_value ? RGB.build(int_value) : nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check if color exists in database.
|
|
74
|
+
#
|
|
75
|
+
# @param color_name [String] The color name to check
|
|
76
|
+
# @return [Boolean] true if color exists
|
|
77
|
+
def valid?(color_name)
|
|
78
|
+
normalized = normalize_name(color_name)
|
|
79
|
+
data.key?(normalized)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get all color names in database.
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<String>] Array of all color names
|
|
85
|
+
def all
|
|
86
|
+
data.keys
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Check if database has been loaded.
|
|
90
|
+
#
|
|
91
|
+
# @return [Boolean] true if data has been loaded from file
|
|
92
|
+
def loaded?
|
|
93
|
+
!@data.nil?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Calculate memory size of loaded database.
|
|
97
|
+
#
|
|
98
|
+
# @return [Integer] Memory size in bytes
|
|
99
|
+
# @api private
|
|
100
|
+
def memsize
|
|
101
|
+
require "objspace"
|
|
102
|
+
|
|
103
|
+
memory = ObjectSpace.memsize_of(data)
|
|
104
|
+
|
|
105
|
+
data.each do |key, value|
|
|
106
|
+
memory += ObjectSpace.memsize_of(key)
|
|
107
|
+
memory += ObjectSpace.memsize_of(value)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
memory
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
# Lazy load data from file.
|
|
116
|
+
#
|
|
117
|
+
# @return [Hash] Hash of normalized color names to integer values
|
|
118
|
+
def data
|
|
119
|
+
@data ||= load_data
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Normalize color name for lookup.
|
|
123
|
+
# Converts to lowercase and removes all whitespace.
|
|
124
|
+
#
|
|
125
|
+
# @param name [String] The color name to normalize
|
|
126
|
+
# @return [String] The normalized name
|
|
127
|
+
def normalize_name(name)
|
|
128
|
+
name.to_s.downcase.gsub(/\s+/, "")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
require "json"
|
|
132
|
+
|
|
133
|
+
# Load and parse database file.
|
|
134
|
+
#
|
|
135
|
+
# @return [Hash] Hash of normalized color names to integer values
|
|
136
|
+
def load_data
|
|
137
|
+
unless File.exist?(@filepath)
|
|
138
|
+
raise Error, "Color database file not found: #{@filepath}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Load and parse JSON file (Ruby's JSON parser handles // comments)
|
|
142
|
+
# Keys are already normalized in the JSON file
|
|
143
|
+
JSON.parse(File.read(@filepath))
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
27
147
|
# Error raised when a color name is not found
|
|
28
148
|
class ParseError < Color::Error; end
|
|
29
149
|
|
|
150
|
+
# X11 color database (658 colors)
|
|
151
|
+
X11 = Database.new(
|
|
152
|
+
path: File.join(Color::DATA_PATH, "x11.jsonc"),
|
|
153
|
+
name: "x11",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# CSS/W3C color database (148 colors)
|
|
157
|
+
CSS = Database.new(
|
|
158
|
+
path: File.join(Color::DATA_PATH, "css.jsonc"),
|
|
159
|
+
name: "css",
|
|
160
|
+
aliases: ["w3c"],
|
|
161
|
+
)
|
|
162
|
+
|
|
30
163
|
class << self
|
|
31
164
|
# Parse a named color and return its RGB representation.
|
|
32
165
|
#
|
|
166
|
+
# Supports database prefixes (css:, w3c:, x11:) to select specific database.
|
|
167
|
+
# Without prefix, uses X11 database by default.
|
|
168
|
+
#
|
|
33
169
|
# @param name [String] The color name to parse (case-insensitive)
|
|
34
170
|
# @return [RGB] The RGB color instance
|
|
35
171
|
# @raise [ParseError] If the color name is not recognized
|
|
36
172
|
#
|
|
37
|
-
# @example
|
|
173
|
+
# @example Parse from X11 database (default)
|
|
38
174
|
# Unmagic::Color::RGB::Named.parse("goldenrod")
|
|
39
175
|
# #=> RGB instance for #daa520
|
|
176
|
+
#
|
|
177
|
+
# @example Parse from CSS database
|
|
178
|
+
# Unmagic::Color::RGB::Named.parse("css:gray")
|
|
179
|
+
# #=> RGB instance for #808080
|
|
40
180
|
def parse(name)
|
|
41
|
-
|
|
42
|
-
|
|
181
|
+
database, color_name = resolve_database(name)
|
|
182
|
+
color = database[color_name]
|
|
43
183
|
|
|
44
|
-
raise ParseError, "Unknown color name: #{
|
|
184
|
+
raise ParseError, "Unknown color name in #{database.name} database: #{color_name.inspect}" unless color
|
|
45
185
|
|
|
46
|
-
|
|
186
|
+
color
|
|
47
187
|
end
|
|
48
188
|
|
|
49
189
|
# Check if a color name is valid.
|
|
50
190
|
#
|
|
191
|
+
# Supports database prefixes to check specific database.
|
|
192
|
+
#
|
|
51
193
|
# @param name [String] The color name to check
|
|
52
194
|
# @return [Boolean] true if the name exists
|
|
53
195
|
#
|
|
54
|
-
# @example
|
|
196
|
+
# @example Check in X11 database (default)
|
|
55
197
|
# Unmagic::Color::RGB::Named.valid?("goldenrod")
|
|
56
198
|
# #=> true
|
|
199
|
+
#
|
|
200
|
+
# @example Check in CSS database
|
|
201
|
+
# Unmagic::Color::RGB::Named.valid?("css:gray")
|
|
202
|
+
# #=> true
|
|
57
203
|
def valid?(name)
|
|
58
|
-
|
|
59
|
-
|
|
204
|
+
database, color_name = resolve_database(name)
|
|
205
|
+
database.valid?(color_name)
|
|
60
206
|
end
|
|
61
207
|
|
|
62
|
-
# Get all available color
|
|
208
|
+
# Get all available color databases.
|
|
63
209
|
#
|
|
64
|
-
# @return [Array<
|
|
210
|
+
# @return [Array<Database>] Array of database instances
|
|
65
211
|
#
|
|
66
|
-
# @example
|
|
67
|
-
# Unmagic::Color::RGB::Named.
|
|
68
|
-
# #=> [
|
|
69
|
-
|
|
70
|
-
|
|
212
|
+
# @example Get all databases
|
|
213
|
+
# Unmagic::Color::RGB::Named.databases
|
|
214
|
+
# #=> [X11, CSS]
|
|
215
|
+
#
|
|
216
|
+
# @example Get color names from a specific database
|
|
217
|
+
# Unmagic::Color::RGB::Named.databases.first.all.take(5)
|
|
218
|
+
# #=> ["aliceblue", "antiquewhite", ...]
|
|
219
|
+
def databases
|
|
220
|
+
[X11, CSS]
|
|
71
221
|
end
|
|
72
222
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Normalize a color name for lookup.
|
|
76
|
-
# Converts to lowercase and removes all whitespace.
|
|
223
|
+
# Find a database by name or alias.
|
|
77
224
|
#
|
|
78
|
-
# @param
|
|
79
|
-
# @return [
|
|
80
|
-
|
|
81
|
-
|
|
225
|
+
# @param search [String] Name or alias to search for
|
|
226
|
+
# @return [Database, nil] Matching database or nil
|
|
227
|
+
#
|
|
228
|
+
# @example Find by name
|
|
229
|
+
# Unmagic::Color::RGB::Named.find_by_name("x11")
|
|
230
|
+
# #=> X11 database
|
|
231
|
+
#
|
|
232
|
+
# @example Find by alias
|
|
233
|
+
# Unmagic::Color::RGB::Named.find_by_name("w3c")
|
|
234
|
+
# #=> CSS database
|
|
235
|
+
def find_by_name(search)
|
|
236
|
+
normalized = search.strip.downcase
|
|
237
|
+
all_by_name.fetch(normalized)
|
|
238
|
+
rescue KeyError
|
|
239
|
+
nil
|
|
82
240
|
end
|
|
83
241
|
|
|
84
|
-
|
|
85
|
-
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
# Hash mapping all database names and aliases to their instances.
|
|
86
245
|
#
|
|
87
|
-
# @return [Hash]
|
|
88
|
-
def
|
|
89
|
-
@
|
|
246
|
+
# @return [Hash<String, Database>] Name/alias to database mapping
|
|
247
|
+
def all_by_name
|
|
248
|
+
@all_by_name ||= begin
|
|
249
|
+
hash = {}
|
|
250
|
+
databases.each do |database|
|
|
251
|
+
hash[database.name] = database
|
|
252
|
+
database.aliases.each { |alias_name| hash[alias_name] = database }
|
|
253
|
+
end
|
|
254
|
+
hash
|
|
255
|
+
end
|
|
90
256
|
end
|
|
91
257
|
|
|
92
|
-
#
|
|
258
|
+
# Resolve database and color name from input.
|
|
93
259
|
#
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
260
|
+
# Extracts database prefix if present (css:, w3c:, x11:).
|
|
261
|
+
# Returns the appropriate database instance and cleaned color name.
|
|
262
|
+
#
|
|
263
|
+
# @param name [String] The input name (may include prefix)
|
|
264
|
+
# @return [Array<Database, String>] Database instance and color name
|
|
265
|
+
def resolve_database(name)
|
|
266
|
+
if name.include?(":")
|
|
267
|
+
prefix, color_name = name.split(":", 2)
|
|
268
|
+
database = find_by_name(prefix)
|
|
98
269
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
next if name.nil? || hex.nil?
|
|
270
|
+
# Invalid prefix, treat whole string as color name
|
|
271
|
+
return [X11, name] unless database
|
|
102
272
|
|
|
103
|
-
|
|
273
|
+
[database, color_name]
|
|
274
|
+
else
|
|
275
|
+
[X11, name]
|
|
104
276
|
end
|
|
105
|
-
|
|
106
|
-
colors
|
|
107
277
|
end
|
|
108
278
|
end
|
|
109
279
|
end
|