package d2svgimport ()// fontCorpora contains the characters rendered by each embedded font class.// Absence means the face is unused. A nil map asks the embedding code to keep// its legacy source triggers and whole-diagram corpus instead.type fontCorpora map[string]string// BaseStylesheet is public and callers may add font-selection rules. Keep the// original text so those callers retain whole-corpus font subsets.var fontCorpusBaseStylesheet = BaseStylesheet// restrictFontCorpora narrows the freshly collected map in place only when its// characters appeared in the legacy corpus. Rendering can introduce characters// absent from that corpus (notably code's nonbreaking spaces). Their browser// fallback can use other legacy glyphs, such as ordinary spaces, so preserve the// complete legacy subset for that face instead of changing those dependencies.// Unicode normalization and bidirectional shaping can also use characters from// other runs or faces. Keep all legacy faces and their corpora for non-ASCII// input, and for controls whose whitespace aliases are browser-dependent.func restrictFontCorpora( fontCorpora, string) fontCorpora {if == nil {returnnil }var [128]boolfor := 0; < len(); ++ { := []if >= 128 || == 127 || < 32 && != '\n' {returnnil } [] = true }for , := range {for , := range {if >= 128 || ![] { [] = break } } }return}func appendFontOnTrigger( *bytes.Buffer, string, []string, fontCorpora, , string, func(string) string) {if == nil {appendOnTriggerLazy(, , , func() string { return () }) } elseif , := []; { .WriteString(()) }}// This order is the cascade order of the font-family rules in EmbedFonts.// In particular, matching text-mono-bold must not also match text-mono.var embeddedFontClasses = [...]string{"text", "text-semibold", "text-bold", "text-italic","text-mono", "text-mono-semibold", "text-mono-bold", "text-mono-italic",}// collectFontCorpora is for the body fragment produced by Render, before its// stylesheets are added. It follows SVG text inheritance, including native// Markdown runs and syntax-highlighted tspans. It is deliberately not a CSS or// HTML evaluator: unsupported font selection, referenced/dynamic text, and// malformed fragments request the existing full-corpus embedding path.func collectFontCorpora( string) (fontCorpora, bool) { , , := collectFontCorporaAndClasses()return , }// collectFontCorporaAndClasses also records palette classes during the text// scan. A nonnil class map means the complete fragment passed the existing// theme parser's safety checks and can avoid a second XML pass.func collectFontCorporaAndClasses( string) (fontCorpora, map[string]bool, bool) {ifBaseStylesheet != fontCorpusBaseStylesheet {returnnil, nil, false } := xml.NewDecoder(io.MultiReader(strings.NewReader(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`),strings.NewReader(), strings.NewReader(`</svg>`), ))typestruct {intboolbool } := []{{: -1}}var [len(embeddedFontClasses)]strings.Builder := make(map[string]bool) := truefor { , := .Token()if == io.EOF {break }if != nil {returnnil, nil, false }switch token := .(type) {casexml.StartElement:ifstrings.EqualFold(.Name.Local, "script") { = false }if .Name.Space != "" && .Name.Space != "http://www.w3.org/2000/svg" {returnnil, nil, false } := [len()-1]switch .Name.Local {case"style", "script", "foreignObject", "use", "tref", "textPath","animate", "animateMotion", "animateTransform", "set":returnnil, nil, falsecase"title", "desc", "metadata": . = truecase"text": . = truecase"tspan", "a":default:if . && !. {returnnil, nil, false } }// A class on the child overrides an inherited family, even if the // parent's class occurs later in the stylesheet. Only classes on // the same element compete according to stylesheet order. := -1 := falsefor , := range .Attr {// Match pruneThemeCSS's guards independently of the stricter // font guards, so neither specialization changes the other. := strings.ToLower(.Name.Local)ifstrings.HasPrefix(, "on") || == "attributename" && strings.EqualFold(strings.TrimSpace(.Value), "class") { = false }if == "href" { := strings.TrimSpace(.Value)ifstrings.EqualFold(.Name.Local, "use") && !strings.HasPrefix(, "#") || strings.HasPrefix(strings.ToLower(), "javascript:") { = false } }ifstrings.HasPrefix(.Name.Local, "font-variant") {returnnil, nil, false }switch .Name.Local {case"class":if .Name.Space != "" || {returnnil, nil, false } = truefor , := rangestrings.FieldsFunc(.Value, func( rune) bool {// The separate theme parser uses strings.Fields, which // also splits Unicode whitespace. Preserve that path // for custom classes with different tokenization.if >= 128 && unicode.IsSpace() { = false }return == ' ' || == '\t' || == '\n' || == '\r' || == '\f' }) {ifisPaletteClass() { [] = true }for , := rangeembeddedFontClasses {if == && > { = } } }case"font-family", "text-transform", "direction", "unicode-bidi":returnnil, nil, falsecase"style":if .Name.Space != "" || !fontCorpusStyleSafe(.Value) {returnnil, nil, false }default:ifstrings.HasPrefix(.Name.Local, "on") {returnnil, nil, false } } }if >= 0 { . = } = append(, )casexml.EndElement:iflen() == 1 {returnnil, nil, false } = [:len()-1]casexml.CharData: := [len()-1]if . && !. && len() != 0 {if . < 0 {returnnil, nil, false }// Keep whitespace too: code uses nonbreaking spaces, and text // layout can preserve ordinary spaces with xml:space. [.].Write() }casexml.Directive, xml.ProcInst:returnnil, nil, false } }iflen() != 1 {returnnil, nil, false } := make(fontCorpora)for := range {if [].Len() != 0 { [embeddedFontClasses[]] = [].String() } }// The legacy regular-face trigger is narrower than CSS class matching. // Customized SVG fragments may use another class first, or single quotes; // do not introduce a regular-family rule that the old output lacked.if , := ["text"]; && !strings.Contains(, `class="text"`) && !strings.Contains(, `class="text `) && !strings.Contains(, `class="md"`) && !strings.Contains(, `class="md `) {returnnil, nil, false }if ! { = nil }return , , true}// Generated inline styles use simple declarations. Reject CSS syntax that// could disguise a font-family override or change the rendered characters.// Font weight/style alone synthesize against the selected family; the D2// @font-face declarations do not declare separate weight/style descriptors.func fontCorpusStyleSafe( string) bool {ifstrings.ContainsAny(, `\/{}`) {returnfalse }for , := rangestrings.Split(, ";") {ifstrings.TrimSpace() == "" {continue } , , := strings.Cut(, ":")if ! {returnfalse } = strings.ToLower(strings.TrimSpace())ifstrings.HasPrefix(, "font-variant") {returnfalse }switch {case"font", "font-family", "text-transform", "direction", "unicode-bidi", "all", "content":returnfalse } }returntrue}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.