1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 | package render
import (
"bytes"
"encoding/xml"
"slices"
"strings"
)
// svgRootEnd returns the offset just past the root <svg ...> start tag,
// where themeSVG injects its style, or -1 when the document does not start
// with an <svg> element, the root is empty (<svg/>), or the XML decoder
// cannot read that far. Nothing in an SVG needs vetting: it is embedded
// through <img>, which runs no script, and served under httpd's sandbox
// CSP when opened directly.
func svgRootEnd(data []byte) int {
d := xml.NewDecoder(bytes.NewReader(data))
d.Strict = true
d.Entity = xml.HTMLEntity
for {
tok, err := d.RawToken()
if err != nil {
return -1
}
if t, ok := tok.(xml.StartElement); ok {
if off := int(d.InputOffset()); strings.EqualFold(t.Name.Local, "svg") && data[off-2] != '/' {
return off
}
return -1
}
}
}
// themeSVG returns a copy of an SVG document with a <style> injected at
// rootEnd, just after the root start tag (as svgRootEnd reports it), that sets
// the document colour, so currentColor resolves to the page foreground when
// the SVG is shown through <img>. With rootEnd < 0 or no colour, data is
// returned unchanged.
func themeSVG(data []byte, rootEnd int, colour, light string) []byte {
if rootEnd < 0 || colour == "" {
return data
}
css := `:root{color:` + colour + `}`
if light != "" {
// An image cannot see the page's theme toggle, only the system
// preference; that is the best it can follow.
css += `@media (prefers-color-scheme:light){:root{color:` + light + `}}`
}
return slices.Concat(data[:rootEnd], []byte(`<style>`+css+`</style>`), data[rootEnd:])
}
|