| 1 | package check |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "math" |
| 6 | "regexp" |
| 7 | "strconv" |
| 8 | ) |
| 9 | |
| 10 | // canonColours and what it reaches, from rastrillo's ui/derived_test.go: |
| 11 | // untagged, so the CSS gates that read text and the browser drives that |
| 12 | // read computed styles both use it. The derived-colour evaluator it sat |
| 13 | // beside (evalColour, rgba, accentSupportsBlock) is ported with the |
| 14 | // gate that uses it. |
| 15 | |
| 16 | // oklchClip renders OKLCh as eight-bit sRGB the way Chromium paints an |
| 17 | // out-of-gamut colour: each linear channel clipped to 0..1. Lightness |
| 18 | // and chroma are both kept, so a clipped colour is brighter than the |
| 19 | // one asked for; that is what the dark fill's lightness drop exists to |
| 20 | // absorb (day.css, the @supports comment). |
| 21 | func oklchClip(l, c, hue float64) [3]uint8 { |
| 22 | rad := hue * math.Pi / 180 |
| 23 | lin := oklabToLinear(l, c*math.Cos(rad), c*math.Sin(rad)) |
| 24 | var v [3]uint8 |
| 25 | for i, ch := range lin { |
| 26 | n := math.Round(linearToSRGB(math.Min(1, math.Max(0, ch))) * 255) |
| 27 | v[i] = uint8(math.Min(255, math.Max(0, n))) |
| 28 | } |
| 29 | return v |
| 30 | } |
| 31 | |
| 32 | var ( |
| 33 | srgbSer = regexp.MustCompile(`color\(srgb ([0-9.e-]+) ([0-9.e-]+) ([0-9.e-]+)(?: / ([0-9.e-]+))?\)`) |
| 34 | oklchSer = regexp.MustCompile(`oklch\(([0-9.e-]+) ([0-9.e-]+) ([0-9.e-]+)(?: / ([0-9.e-]+))?\)`) |
| 35 | ) |
| 36 | |
| 37 | // canonColours rewrites the colour functions Chromium serialises a |
| 38 | // derived colour as, color(srgb …) for a color-mix() and oklch(…) for a |
| 39 | // relative colour, into the rgb()/rgba() the drives compare against. |
| 40 | // A computed value that is a legacy colour is already in that form. |
| 41 | // Without this every literal in the depth drives would have to be the |
| 42 | // six-decimal serialisation of a float, which changes with the engine's |
| 43 | // printing rather than with the colour. |
| 44 | func canonColours(s string) string { |
| 45 | s = srgbSer.ReplaceAllStringFunc(s, func(m string) string { |
| 46 | p := srgbSer.FindStringSubmatch(m) |
| 47 | var ch [3]int |
| 48 | for i := range ch { |
| 49 | f, _ := strconv.ParseFloat(p[i+1], 64) |
| 50 | ch[i] = int(math.Round(f * 255)) |
| 51 | } |
| 52 | if p[4] == "" { |
| 53 | return fmt.Sprintf("rgb(%d, %d, %d)", ch[0], ch[1], ch[2]) |
| 54 | } |
| 55 | return fmt.Sprintf("rgba(%d, %d, %d, %s)", ch[0], ch[1], ch[2], p[4]) |
| 56 | }) |
| 57 | return oklchSer.ReplaceAllStringFunc(s, func(m string) string { |
| 58 | p := oklchSer.FindStringSubmatch(m) |
| 59 | l, _ := strconv.ParseFloat(p[1], 64) |
| 60 | c, _ := strconv.ParseFloat(p[2], 64) |
| 61 | h, _ := strconv.ParseFloat(p[3], 64) |
| 62 | rgb := oklchClip(l, c, h) |
| 63 | if p[4] == "" { |
| 64 | return fmt.Sprintf("rgb(%d, %d, %d)", rgb[0], rgb[1], rgb[2]) |
| 65 | } |
| 66 | return fmt.Sprintf("rgba(%d, %d, %d, %s)", rgb[0], rgb[1], rgb[2], p[4]) |
| 67 | }) |
| 68 | } |
| 69 | |