Module: Echoes::SvgColor

Defined in:
lib/echoes/svg_color.rb

Overview

SVG color parser for the CG fast-path renderer. Returns

r, g, b, a

(each 0..1), :none (“don’t paint this stroke/fill”),

or nil (“unsupported — bail to WKWebView”).

Constant Summary collapse

NAMED =

20 CSS / SVG named colors covering the common cases. Anything outside this set returns nil so the caller falls through to the WKWebView renderer (which has the full CSS palette).

{
  'black'       => [  0,   0,   0],
  'white'       => [255, 255, 255],
  'red'         => [255,   0,   0],
  'green'       => [  0, 128,   0],
  'blue'        => [  0,   0, 255],
  'yellow'      => [255, 255,   0],
  'cyan'        => [  0, 255, 255],
  'aqua'        => [  0, 255, 255],
  'magenta'     => [255,   0, 255],
  'fuchsia'     => [255,   0, 255],
  'gray'        => [128, 128, 128],
  'grey'        => [128, 128, 128],
  'orange'      => [255, 165,   0],
  'purple'      => [128,   0, 128],
  'pink'        => [255, 192, 203],
  'brown'       => [165,  42,  42],
  'lime'        => [  0, 255,   0],
  'navy'        => [  0,   0, 128],
  'teal'        => [  0, 128, 128],
  'silver'      => [192, 192, 192],
  'maroon'      => [128,   0,   0],
  'olive'       => [128, 128,   0],
}.freeze
HEX_RE =
/\A#([0-9a-f]{3,8})\z/i
RGB_RE =
/\Argba?\(\s*([^)]*)\)\z/i

Class Method Summary collapse

Class Method Details

.parse(str, current_color: [0.0, 0.0, 0.0, 1.0]) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/echoes/svg_color.rb', line 41

def parse(str, current_color: [0.0, 0.0, 0.0, 1.0])
  return nil if str.nil?
  s = str.strip
  return nil if s.empty?

  down = s.downcase
  return :none                if down == 'none'
  return current_color.dup    if down == 'currentcolor'
  return [0.0, 0.0, 0.0, 0.0] if down == 'transparent'

  if (m = NAMED[down])
    return [m[0] / 255.0, m[1] / 255.0, m[2] / 255.0, 1.0]
  end

  if (m = HEX_RE.match(s))
    return parse_hex(m[1])
  end

  if (m = RGB_RE.match(s))
    return parse_rgb_func(m[1])
  end

  nil
end

.parse_component(p) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/echoes/svg_color.rb', line 108

def parse_component(p)
  if p.end_with?('%')
    f = Float(p[0..-2]) rescue nil
    return nil if f.nil?
    (f / 100.0).clamp(0.0, 1.0)
  else
    i = Integer(p, 10) rescue nil
    return nil if i.nil?
    (i / 255.0).clamp(0.0, 1.0)
  end
end

.parse_hex(hex) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/echoes/svg_color.rb', line 66

def parse_hex(hex)
  case hex.length
  when 3
    r, g, b = hex.chars.map { |c| (c + c).to_i(16) }
    [r / 255.0, g / 255.0, b / 255.0, 1.0]
  when 4
    r, g, b, a = hex.chars.map { |c| (c + c).to_i(16) }
    [r / 255.0, g / 255.0, b / 255.0, a / 255.0]
  when 6
    r = hex[0, 2].to_i(16)
    g = hex[2, 2].to_i(16)
    b = hex[4, 2].to_i(16)
    [r / 255.0, g / 255.0, b / 255.0, 1.0]
  when 8
    r = hex[0, 2].to_i(16)
    g = hex[2, 2].to_i(16)
    b = hex[4, 2].to_i(16)
    a = hex[6, 2].to_i(16)
    [r / 255.0, g / 255.0, b / 255.0, a / 255.0]
  else
    nil
  end
end

.parse_rgb_func(inner) ⇒ Object

Parse the body of ‘rgb(…)` or `rgba(…)`. Components can be int 0..255 or `N%` (0..100). Alpha (if present) is a plain 0..1 float, not a percentage.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/echoes/svg_color.rb', line 93

def parse_rgb_func(inner)
  parts = inner.split(',').map(&:strip)
  return nil unless parts.size == 3 || parts.size == 4
  rgb = parts[0, 3].map { |p| parse_component(p) }
  return nil if rgb.any?(&:nil?)
  a = if parts.size == 4
        f = Float(parts[3]) rescue nil
        return nil if f.nil?
        f.clamp(0.0, 1.0)
      else
        1.0
      end
  [*rgb, a]
end