///|
/// Read CSS named colours, #rgb[a], #rrggbb[aa], rgb() and rgba(). Values that
/// need a viewer context, such as currentColor, are deliberately unresolved.
pub fn parse_color(source : String) -> Color? {
  let s = source.trim().to_owned().to_lower()
  if s == "transparent" { return Some(Color::rgba(0, 0, 0, 0)) }
  if named_colors.get(s) is Some(rgb) { return Some(Color::hex(rgb)) }
  if s.has_prefix("#") {
    let digits = s.view(start_offset=1).to_owned()
    let n = digits.length()
    guard n == 3 || n == 4 || n == 6 || n == 8 else { return None }
    let values : Array[Int] = []
    for c in digits {
      let d = "0123456789abcdef".find(c.to_string())
      guard d is Some(d) else { return None }
      values.push(d)
    }
    let short = n < 5
    let channel = (i : Int) => if short { values[i] * 17 } else { values[i * 2] * 16 + values[i * 2 + 1] }
    let alpha = if n == 4 || n == 8 { (channel(3) * 100 + 127) / 255 } else { 100 }
    return Some(Color::rgba(channel(0), channel(1), channel(2), alpha))
  }
  let rgba = s.has_prefix("rgba(")
  guard (rgba || s.has_prefix("rgb(")) && s.has_suffix(")") else { return None }
  let body = s.view(start_offset=if rgba { 5 } else { 4 }, end_offset=s.length() - 1)
  let comma = body.contains(",")
  let parts = if comma {
    if body.contains("/") { return None }
    body.split(",").map(p => p.trim().to_owned()).to_array()
  } else {
    let sides = body.split("/").to_array()
    if sides.length() > 2 { return None }
    let channels = sides[0].to_owned().replace_all(old="\t", new=" ").replace_all(old="\r", new=" ").replace_all(old="\n", new=" ")
      .split(" ").filter(p => p != "").map(p => p.to_owned()).to_array()
    if channels.length() != 3 { return None }
    if sides.length() == 2 { channels.push(sides[1].trim().to_owned()) }
    channels
  }
  guard parts.length() == 3 || parts.length() == 4 else { return None }
  fn number(s : String, maximum : Double) -> Double? {
    let percent = s.has_suffix("%")
    let raw = if percent { s.view(end_offset=s.length() - 1).to_owned() } else { s }
    // Read decimal CSS numbers, including exponents, without accepting NaN.
    guard raw != "" && raw.iter().all(c => (c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-' || c == 'e') else { return None }
    let n = try @string.parse_double(raw) catch { _ => return None }
    let value = if percent { n * maximum / 100.0 } else { n }
    Some(if value < 0.0 { 0.0 } else if value > maximum { maximum } else { value })
  }
  guard number(parts[0], 255.0) is Some(r) && number(parts[1], 255.0) is Some(g) && number(parts[2], 255.0) is Some(b) else { return None }
  let alpha = if parts.length() == 4 {
    guard number(parts[3], 1.0) is Some(a) else { return None }
    (a * 100.0).round().to_int()
  } else { 100 }
  Some(Color::rgba(r.round().to_int(), g.round().to_int(), b.round().to_int(), alpha))
}

///|
// CSS named-colour values, https://www.w3.org/TR/css-color-4/#named-colors
let named_colors : Map[String, Int] = {
  "aliceblue": 0xf0f8ff,
  "antiquewhite": 0xfaebd7,
  "aqua": 0x00ffff,
  "aquamarine": 0x7fffd4,
  "azure": 0xf0ffff,
  "beige": 0xf5f5dc,
  "bisque": 0xffe4c4,
  "black": 0x000000,
  "blanchedalmond": 0xffebcd,
  "blue": 0x0000ff,
  "blueviolet": 0x8a2be2,
  "brown": 0xa52a2a,
  "burlywood": 0xdeb887,
  "cadetblue": 0x5f9ea0,
  "chartreuse": 0x7fff00,
  "chocolate": 0xd2691e,
  "coral": 0xff7f50,
  "cornflowerblue": 0x6495ed,
  "cornsilk": 0xfff8dc,
  "crimson": 0xdc143c,
  "cyan": 0x00ffff,
  "darkblue": 0x00008b,
  "darkcyan": 0x008b8b,
  "darkgoldenrod": 0xb8860b,
  "darkgray": 0xa9a9a9,
  "darkgreen": 0x006400,
  "darkgrey": 0xa9a9a9,
  "darkkhaki": 0xbdb76b,
  "darkmagenta": 0x8b008b,
  "darkolivegreen": 0x556b2f,
  "darkorange": 0xff8c00,
  "darkorchid": 0x9932cc,
  "darkred": 0x8b0000,
  "darksalmon": 0xe9967a,
  "darkseagreen": 0x8fbc8f,
  "darkslateblue": 0x483d8b,
  "darkslategray": 0x2f4f4f,
  "darkslategrey": 0x2f4f4f,
  "darkturquoise": 0x00ced1,
  "darkviolet": 0x9400d3,
  "deeppink": 0xff1493,
  "deepskyblue": 0x00bfff,
  "dimgray": 0x696969,
  "dimgrey": 0x696969,
  "dodgerblue": 0x1e90ff,
  "firebrick": 0xb22222,
  "floralwhite": 0xfffaf0,
  "forestgreen": 0x228b22,
  "fuchsia": 0xff00ff,
  "gainsboro": 0xdcdcdc,
  "ghostwhite": 0xf8f8ff,
  "gold": 0xffd700,
  "goldenrod": 0xdaa520,
  "gray": 0x808080,
  "green": 0x008000,
  "greenyellow": 0xadff2f,
  "grey": 0x808080,
  "honeydew": 0xf0fff0,
  "hotpink": 0xff69b4,
  "indianred": 0xcd5c5c,
  "indigo": 0x4b0082,
  "ivory": 0xfffff0,
  "khaki": 0xf0e68c,
  "lavender": 0xe6e6fa,
  "lavenderblush": 0xfff0f5,
  "lawngreen": 0x7cfc00,
  "lemonchiffon": 0xfffacd,
  "lightblue": 0xadd8e6,
  "lightcoral": 0xf08080,
  "lightcyan": 0xe0ffff,
  "lightgoldenrodyellow": 0xfafad2,
  "lightgray": 0xd3d3d3,
  "lightgreen": 0x90ee90,
  "lightgrey": 0xd3d3d3,
  "lightpink": 0xffb6c1,
  "lightsalmon": 0xffa07a,
  "lightseagreen": 0x20b2aa,
  "lightskyblue": 0x87cefa,
  "lightslategray": 0x778899,
  "lightslategrey": 0x778899,
  "lightsteelblue": 0xb0c4de,
  "lightyellow": 0xffffe0,
  "lime": 0x00ff00,
  "limegreen": 0x32cd32,
  "linen": 0xfaf0e6,
  "magenta": 0xff00ff,
  "maroon": 0x800000,
  "mediumaquamarine": 0x66cdaa,
  "mediumblue": 0x0000cd,
  "mediumorchid": 0xba55d3,
  "mediumpurple": 0x9370db,
  "mediumseagreen": 0x3cb371,
  "mediumslateblue": 0x7b68ee,
  "mediumspringgreen": 0x00fa9a,
  "mediumturquoise": 0x48d1cc,
  "mediumvioletred": 0xc71585,
  "midnightblue": 0x191970,
  "mintcream": 0xf5fffa,
  "mistyrose": 0xffe4e1,
  "moccasin": 0xffe4b5,
  "navajowhite": 0xffdead,
  "navy": 0x000080,
  "oldlace": 0xfdf5e6,
  "olive": 0x808000,
  "olivedrab": 0x6b8e23,
  "orange": 0xffa500,
  "orangered": 0xff4500,
  "orchid": 0xda70d6,
  "palegoldenrod": 0xeee8aa,
  "palegreen": 0x98fb98,
  "paleturquoise": 0xafeeee,
  "palevioletred": 0xdb7093,
  "papayawhip": 0xffefd5,
  "peachpuff": 0xffdab9,
  "peru": 0xcd853f,
  "pink": 0xffc0cb,
  "plum": 0xdda0dd,
  "powderblue": 0xb0e0e6,
  "purple": 0x800080,
  "rebeccapurple": 0x663399,
  "red": 0xff0000,
  "rosybrown": 0xbc8f8f,
  "royalblue": 0x4169e1,
  "saddlebrown": 0x8b4513,
  "salmon": 0xfa8072,
  "sandybrown": 0xf4a460,
  "seagreen": 0x2e8b57,
  "seashell": 0xfff5ee,
  "sienna": 0xa0522d,
  "silver": 0xc0c0c0,
  "skyblue": 0x87ceeb,
  "slateblue": 0x6a5acd,
  "slategray": 0x708090,
  "slategrey": 0x708090,
  "snow": 0xfffafa,
  "springgreen": 0x00ff7f,
  "steelblue": 0x4682b4,
  "tan": 0xd2b48c,
  "teal": 0x008080,
  "thistle": 0xd8bfd8,
  "tomato": 0xff6347,
  "turquoise": 0x40e0d0,
  "violet": 0xee82ee,
  "wheat": 0xf5deb3,
  "white": 0xffffff,
  "whitesmoke": 0xf5f5f5,
  "yellow": 0xffff00,
  "yellowgreen": 0x9acd32,
}