2015-09-11 05:46:00 +02:00
|
|
|
// Copyright 2015 The Go Authors. All rights reserved.
|
2015-08-05 07:08:17 +02:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// +build ignore
|
|
|
|
|
|
|
|
// This program generates table.go from
|
2018-11-08 21:06:01 +01:00
|
|
|
// https://www.w3.org/TR/SVG11/types.html#ColorKeywords
|
2015-08-05 07:08:17 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"go/format"
|
|
|
|
"image/color"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"regexp"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"golang.org/x/net/html"
|
|
|
|
"golang.org/x/net/html/atom"
|
|
|
|
)
|
|
|
|
|
|
|
|
// matchFunc matches HTML nodes.
|
|
|
|
type matchFunc func(*html.Node) bool
|
|
|
|
|
|
|
|
// appendAll recursively traverses the parse tree rooted under the provided
|
|
|
|
// node and appends all nodes matched by the matchFunc to dst.
|
|
|
|
func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node {
|
|
|
|
if mf(n) {
|
|
|
|
dst = append(dst, n)
|
|
|
|
}
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
|
|
dst = appendAll(dst, c, mf)
|
|
|
|
}
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
|
|
|
// matchAtom returns a matchFunc that matches a Node with the specified Atom.
|
|
|
|
func matchAtom(a atom.Atom) matchFunc {
|
|
|
|
return func(n *html.Node) bool {
|
|
|
|
return n.DataAtom == a
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// matchAtomAttr returns a matchFunc that matches a Node with the specified
|
|
|
|
// Atom and a html.Attribute's namespace, key and value.
|
|
|
|
func matchAtomAttr(a atom.Atom, namespace, key, value string) matchFunc {
|
|
|
|
return func(n *html.Node) bool {
|
|
|
|
return n.DataAtom == a && getAttr(n, namespace, key) == value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAttr fetches the value of a html.Attribute for a given namespace and key.
|
|
|
|
func getAttr(n *html.Node, namespace, key string) string {
|
|
|
|
for _, attr := range n.Attr {
|
|
|
|
if attr.Namespace == namespace && attr.Key == key {
|
|
|
|
return attr.Val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// re extracts RGB values from strings like "rgb( 0, 223, 128)".
|
|
|
|
var re = regexp.MustCompile(`rgb\(\s*([0-9]+),\s*([0-9]+),\s*([0-9]+)\)`)
|
|
|
|
|
|
|
|
// parseRGB parses a color from a string like "rgb( 0, 233, 128)". It sets
|
|
|
|
// the alpha value of the color to full opacity.
|
|
|
|
func parseRGB(s string) (color.RGBA, error) {
|
|
|
|
m := re.FindStringSubmatch(s)
|
|
|
|
if m == nil {
|
|
|
|
return color.RGBA{}, fmt.Errorf("malformed color: %q", s)
|
|
|
|
}
|
|
|
|
var rgb [3]uint8
|
|
|
|
for i, t := range m[1:] {
|
|
|
|
num, err := strconv.ParseUint(t, 10, 8)
|
|
|
|
if err != nil {
|
|
|
|
return color.RGBA{}, fmt.Errorf("malformed value %q in %q: %s", t, s, err)
|
|
|
|
}
|
|
|
|
rgb[i] = uint8(num)
|
|
|
|
}
|
|
|
|
return color.RGBA{rgb[0], rgb[1], rgb[2], 0xFF}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// extractSVGColors extracts named colors from the parse tree of the SVG 1.1
|
|
|
|
// spec HTML document "Chapter 4: Basic data types and interfaces".
|
|
|
|
func extractSVGColors(tree *html.Node) (map[string]color.RGBA, error) {
|
|
|
|
ret := make(map[string]color.RGBA)
|
|
|
|
|
|
|
|
// Find the tables which store the color keywords in the parse tree.
|
|
|
|
colorTables := appendAll(nil, tree, func(n *html.Node) bool {
|
|
|
|
return n.DataAtom == atom.Table && strings.Contains(getAttr(n, "", "summary"), "color keywords part")
|
|
|
|
})
|
|
|
|
|
|
|
|
for _, table := range colorTables {
|
|
|
|
// Color names and values are stored in TextNodes within spans in each row.
|
|
|
|
for _, tr := range appendAll(nil, table, matchAtom(atom.Tr)) {
|
|
|
|
nameSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "prop-value"))
|
|
|
|
valueSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "color-keyword-value"))
|
|
|
|
|
|
|
|
// Since SVG 1.1 defines an odd number of colors, the last row
|
|
|
|
// in the second table does not have contents. We skip it.
|
|
|
|
if len(nameSpan) != 1 || len(valueSpan) != 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
n, v := nameSpan[0].FirstChild, valueSpan[0].FirstChild
|
|
|
|
// This sanity checks for the existence of TextNodes under spans.
|
|
|
|
if n == nil || n.Type != html.TextNode || v == nil || v.Type != html.TextNode {
|
|
|
|
return nil, fmt.Errorf("extractSVGColors: couldn't find name/value text nodes")
|
|
|
|
}
|
|
|
|
val, err := parseRGB(v.Data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("extractSVGColors: couldn't parse name/value %q/%q: %s", n.Data, v.Data, err)
|
|
|
|
}
|
|
|
|
ret[n.Data] = val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const preamble = `// generated by go generate; DO NOT EDIT.
|
|
|
|
|
|
|
|
package colornames
|
|
|
|
|
|
|
|
import "image/color"
|
|
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
// WriteColorNames writes table.go.
|
|
|
|
func writeColorNames(w io.Writer, m map[string]color.RGBA) {
|
|
|
|
keys := make([]string, 0, len(m))
|
|
|
|
for k := range m {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
fmt.Fprintln(w, preamble)
|
|
|
|
fmt.Fprintln(w, "// Map contains named colors defined in the SVG 1.1 spec.")
|
|
|
|
fmt.Fprintln(w, "var Map = map[string]color.RGBA{")
|
|
|
|
for _, k := range keys {
|
2016-05-26 05:27:44 +02:00
|
|
|
c := m[k]
|
2015-08-05 07:08:17 +02:00
|
|
|
fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n",
|
2016-05-26 05:27:44 +02:00
|
|
|
k, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
|
2015-08-05 07:08:17 +02:00
|
|
|
}
|
|
|
|
fmt.Fprintln(w, "}\n")
|
|
|
|
fmt.Fprintln(w, "// Names contains the color names defined in the SVG 1.1 spec.")
|
|
|
|
fmt.Fprintln(w, "var Names = []string{")
|
|
|
|
for _, k := range keys {
|
|
|
|
fmt.Fprintf(w, "%q,\n", k)
|
|
|
|
}
|
2016-05-26 05:27:44 +02:00
|
|
|
fmt.Fprintln(w, "}\n")
|
|
|
|
fmt.Fprintln(w, "var (")
|
|
|
|
for _, k := range keys {
|
|
|
|
c := m[k]
|
|
|
|
// Make the upper case version of k: "Darkred" instead of "darkred".
|
|
|
|
k = string(k[0]-0x20) + k[1:]
|
|
|
|
fmt.Fprintf(w, "%s=color.RGBA{%#02x, %#02x, %#02x, %#02x} // rgb(%d, %d, %d)\n",
|
|
|
|
k, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
|
|
|
|
}
|
|
|
|
fmt.Fprintln(w, ")")
|
2015-08-05 07:08:17 +02:00
|
|
|
}
|
|
|
|
|
2018-11-08 21:06:01 +01:00
|
|
|
const url = "https://www.w3.org/TR/SVG11/types.html"
|
2015-08-05 07:08:17 +02:00
|
|
|
|
|
|
|
func main() {
|
|
|
|
res, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't read from %s: %s\n", url, err)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
tree, err := html.Parse(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't parse %s: %s\n", url, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
colors, err := extractSVGColors(tree)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't extract colors: %s\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
writeColorNames(buf, colors)
|
|
|
|
fmted, err := format.Source(buf.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error while formatting code: %s\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ioutil.WriteFile("table.go", fmted, 0644); err != nil {
|
|
|
|
log.Fatalf("Error writing table.go: %s\n", err)
|
|
|
|
}
|
|
|
|
}
|