Class: SvgConform::CssColor
- Inherits:
-
Object
- Object
- SvgConform::CssColor
- Defined in:
- lib/svg_conform/css_color.rb
Overview
Handles CSS color equivalence, normalization, and conversion
Constant Summary collapse
- NAMED_COLORS =
Named colors mapping to their hex equivalents
{ "black" => "#000000", "white" => "#ffffff", "red" => "#ff0000", "green" => "#008000", "blue" => "#0000ff", "yellow" => "#ffff00", "cyan" => "#00ffff", "magenta" => "#ff00ff", "silver" => "#c0c0c0", "gray" => "#808080", "grey" => "#808080", "maroon" => "#800000", "olive" => "#808000", "lime" => "#00ff00", "aqua" => "#00ffff", "teal" => "#008080", "navy" => "#000080", "fuchsia" => "#ff00ff", "purple" => "#800080", }.freeze
- HEX_TO_NAMED =
Reverse mapping for canonical named color lookup
NAMED_COLORS.invert.freeze
- SPECIAL_KEYWORDS =
CSS color keywords that have special meaning
%w[inherit currentcolor transparent none].freeze
Class Method Summary collapse
-
.allowed_in_list?(color, allowed_colors) ⇒ Boolean
Check if a color is in a list of allowed colors (with equivalence).
-
.equivalent?(color1, color2) ⇒ Boolean
Check if two colors are equivalent.
-
.equivalent_forms(color) ⇒ Object
Get all equivalent forms of a color.
-
.expand_short_hex(hex) ⇒ Object
Expand short hex colors: #fff → #ffffff.
-
.normalize(color) ⇒ Object
Normalize a color to its canonical hex representation.
-
.rgb_sum(color) ⇒ Object
Calculate RGB sum for threshold-based validation.
-
.rgb_to_hex(red, green, blue) ⇒ Object
Convert RGB values to hex.
-
.to_canonical(color) ⇒ Object
Convert color to its canonical form (prefer named colors when available).
-
.valid_css_color?(color) ⇒ Boolean
Check if a color is valid according to SVG specifications.
Class Method Details
.allowed_in_list?(color, allowed_colors) ⇒ Boolean
Check if a color is in a list of allowed colors (with equivalence)
179 180 181 182 183 184 185 186 |
# File 'lib/svg_conform/css_color.rb', line 179 def allowed_in_list?(color, allowed_colors) return false if color.nil? || allowed_colors.nil? || allowed_colors.empty? normalized = normalize(color) return false if normalized.nil? allowed_colors.any? { |allowed| equivalent?(color, allowed) } end |
.equivalent?(color1, color2) ⇒ Boolean
Check if two colors are equivalent
94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/svg_conform/css_color.rb', line 94 def equivalent?(color1, color2) return false if color1.nil? || color2.nil? return true if color1 == color2 norm1 = normalize(color1) norm2 = normalize(color2) return false if norm1.nil? || norm2.nil? norm1 == norm2 end |
.equivalent_forms(color) ⇒ Object
Get all equivalent forms of a color
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/svg_conform/css_color.rb', line 154 def equivalent_forms(color) normalized = normalize(color) return [color] if normalized.nil? forms = [normalized] # Add named form if available named = HEX_TO_NAMED[normalized] forms << named if named # Add short hex form if applicable if normalized.match?(/^#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/) short_hex = "##{$1}#{$2}#{$3}" forms << short_hex end # Add uppercase variants for hex if normalized.match?(/^#[0-9a-f]{6}$/) forms << normalized.upcase end forms.uniq end |
.expand_short_hex(hex) ⇒ Object
Expand short hex colors: #fff → #ffffff
123 124 125 126 127 128 |
# File 'lib/svg_conform/css_color.rb', line 123 def (hex) return hex unless hex.match?(/^#[0-9a-f]{3}$/) chars = hex[1..3].chars "##{chars.map { |c| c * 2 }.join}" end |
.normalize(color) ⇒ Object
Normalize a color to its canonical hex representation
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/svg_conform/css_color.rb', line 37 def normalize(color) return nil if color.nil? || color.empty? color = color.strip.downcase # Handle special keywords return color if SPECIAL_KEYWORDS.include?(color) # Handle named colors return NAMED_COLORS[color] if NAMED_COLORS.key?(color) # Handle hex colors if color.match?(/^#[0-9a-f]{3}$/) # Expand short hex: #fff → #ffffff return (color) elsif color.match?(/^#[0-9a-f]{6}$/) # Already normalized 6-digit hex return color end # Handle RGB functions with integers rgb_match = color.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/) if rgb_match r, g, b = rgb_match[1..3].map(&:to_i) return rgb_to_hex(r, g, b) end # Handle RGB functions with percentages rgb_percent_match = color.match(/^rgb\s*\(\s*(\d+(?:\.\d+)?)%\s*,\s*(\d+(?:\.\d+)?)%\s*,\s*(\d+(?:\.\d+)?)%\s*\)$/) if rgb_percent_match r = (rgb_percent_match[1].to_f * 255 / 100).round g = (rgb_percent_match[2].to_f * 255 / 100).round b = (rgb_percent_match[3].to_f * 255 / 100).round return rgb_to_hex(r, g, b) end # Handle mixed RGB functions (percentage and absolute values) rgb_mixed_match = color.match(/^rgb\s*\(\s*(\d+(?:\.\d+)?%?)\s*,\s*(\d+(?:\.\d+)?%?)\s*,\s*(\d+(?:\.\d+)?%?)\s*\)$/) if rgb_mixed_match r = parse_rgb_value(rgb_mixed_match[1]) g = parse_rgb_value(rgb_mixed_match[2]) b = parse_rgb_value(rgb_mixed_match[3]) return rgb_to_hex(r, g, b) end # Handle RGBA functions (ignore alpha for SVG purposes) rgba_match = color.match(/^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)$/) if rgba_match r, g, b = rgba_match[1..3].map(&:to_i) return rgb_to_hex(r, g, b) end # Return original if unrecognized format color end |
.rgb_sum(color) ⇒ Object
Calculate RGB sum for threshold-based validation
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/svg_conform/css_color.rb', line 189 def rgb_sum(color) return nil if color.nil? || color.empty? color = color.strip.downcase # Handle hex colors if color.match?(/^#[0-9a-f]{3}$/) # Expand short hex and calculate = (color) r = [1..2].to_i(16) g = [3..4].to_i(16) b = [5..6].to_i(16) return r + g + b elsif color.match?(/^#[0-9a-f]{6}$/) r = color[1..2].to_i(16) g = color[3..4].to_i(16) b = color[5..6].to_i(16) return r + g + b end # Handle RGB functions with integers rgb_match = color.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/) if rgb_match r, g, b = rgb_match[1..3].map(&:to_i) return r + g + b end # Handle RGB functions with percentages rgb_percent_match = color.match(/^rgb\s*\(\s*(\d+(?:\.\d+)?)%\s*,\s*(\d+(?:\.\d+)?)%\s*,\s*(\d+(?:\.\d+)?)%\s*\)$/) if rgb_percent_match r = (rgb_percent_match[1].to_f * 255 / 100).round g = (rgb_percent_match[2].to_f * 255 / 100).round b = (rgb_percent_match[3].to_f * 255 / 100).round return r + g + b end # Handle mixed RGB functions rgb_mixed_match = color.match(/^rgb\s*\(\s*(\d+(?:\.\d+)?%?)\s*,\s*(\d+(?:\.\d+)?%?)\s*,\s*(\d+(?:\.\d+)?%?)\s*\)$/) if rgb_mixed_match r = parse_rgb_value(rgb_mixed_match[1]) g = parse_rgb_value(rgb_mixed_match[2]) b = parse_rgb_value(rgb_mixed_match[3]) return r + g + b end # Handle named colors if NAMED_COLORS.key?(color) hex = NAMED_COLORS[color] r = hex[1..2].to_i(16) g = hex[3..4].to_i(16) b = hex[5..6].to_i(16) return r + g + b end nil end |
.rgb_to_hex(red, green, blue) ⇒ Object
Convert RGB values to hex
131 132 133 134 135 136 137 138 |
# File 'lib/svg_conform/css_color.rb', line 131 def rgb_to_hex(red, green, blue) # Clamp values to 0-255 range r = [[red.to_i, 0].max, 255].min g = [[green.to_i, 0].max, 255].min b = [[blue.to_i, 0].max, 255].min format("#%02x%02x%02x", r, g, b) end |
.to_canonical(color) ⇒ Object
Convert color to its canonical form (prefer named colors when available)
107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/svg_conform/css_color.rb', line 107 def to_canonical(color) normalized = normalize(color) return color if normalized.nil? # Return special keywords as-is return normalized if SPECIAL_KEYWORDS.include?(normalized) # Convert hex to named color if available named = HEX_TO_NAMED[normalized] return named if named # Return normalized hex normalized end |
.valid_css_color?(color) ⇒ Boolean
Check if a color is valid according to SVG specifications
141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/svg_conform/css_color.rb', line 141 def valid_css_color?(color) return false if color.nil? || color.empty? normalized = normalize(color) return false if normalized.nil? # Valid if it normalizes to something we recognize SPECIAL_KEYWORDS.include?(normalized) || NAMED_COLORS.key?(color.strip.downcase) || normalized.match?(/^#[0-9a-f]{6}$/) end |