// Copyright 2017-2022 The MathJax Consortium
// SPDX-License-Identifier: Apache-2.0
// This file is a Go translation and modification of MathJax 3.2.2.

// Package svg implements MathJax 3.2.2's SVG output jax.
package svg import ( ) // Node is a deterministic in-memory SVG node. type Node interface { appendXML(*strings.Builder) } // Attribute is one ordered XML attribute. type Attribute struct { Name string Value string } // Style is one ordered CSS declaration. type Style struct { Name string Value string } // Element is an SVG element. Attribute and style insertion order is retained // because LiteAdaptor serialization order is part of the frozen D2 oracle. type Element struct { Tag string Attributes []Attribute Styles []Style Children []Node stylesAfterAttributes bool } // NewElement creates an element and appends children in order. func ( string, ...Node) *Element { return &Element{Tag: , Children: append([]Node(nil), ...)} } // SetAttr adds or replaces an attribute without changing its position. func ( *Element) (, string) *Element { for := range .Attributes { if .Attributes[].Name == { .Attributes[].Value = return } } .Attributes = append(.Attributes, Attribute{Name: , Value: }) return } // RemoveAttr removes an attribute, retaining the order of all others. func ( *Element) ( string) bool { for := range .Attributes { if .Attributes[].Name == { copy(.Attributes[:], .Attributes[+1:]) .Attributes = .Attributes[:len(.Attributes)-1] return true } } return false } // SetStyle adds or replaces a CSS declaration without changing its position. func ( *Element) (, string) *Element { for := range .Styles { if .Styles[].Name == { .Styles[].Value = return } } .Styles = append(.Styles, Style{Name: , Value: }) return } // StylesAfterAttributes makes the serialized style attribute follow the // ordinary attributes. LiteAdaptor preserves the order in which properties // and attributes are installed, and a few MathJax wrappers (notably mtext's // explicit-font path) install their style after standardSVGnode has added // data-mml-node. func ( *Element) () *Element { .stylesAfterAttributes = true return } // Append appends children. func ( *Element) ( ...Node) *Element { .Children = append(.Children, ...) return } // Prepend inserts children before the existing children. func ( *Element) ( ...Node) *Element { := make([]Node, 0, len()+len(.Children)) = append(, ...) = append(, .Children...) .Children = return } // Text is escaped XML character data. type Text string func ( Text) ( *strings.Builder) { escapeText(, string()) } func ( *Element) ( *strings.Builder) { .WriteByte('<') .WriteString(.Tag) if !.stylesAfterAttributes { .appendStyle() } for , := range .Attributes { .WriteByte(' ') .WriteString(.Name) .WriteString(`="`) escapeAttribute(, .Value) .WriteByte('"') } if .stylesAfterAttributes { .appendStyle() } .WriteByte('>') for , := range .Children { if != nil { .appendXML() } } .WriteString("</") .WriteString(.Tag) .WriteByte('>') } func ( *Element) ( *strings.Builder) { if len(.Styles) == 0 { return } .WriteString(` style="`) for , := range .Styles { if != 0 { .WriteByte(' ') } .WriteString(.Name) .WriteString(": ") escapeAttribute(, .Value) .WriteByte(';') } .WriteByte('"') } // String serializes e exactly once without indentation or a trailing newline. func ( *Element) () string { var strings.Builder .appendXML(&) return .String() } func escapeText( *strings.Builder, string) { for , := range { switch { case '&': .WriteString("&amp;") case '<': .WriteString("&lt;") case '>': .WriteString("&gt;") default: .WriteRune() } } } func escapeAttribute( *strings.Builder, string) { for , := range { switch { case '&': .WriteString("&amp;") case '"': .WriteString("&quot;") case '<': .WriteString("&lt;") case '>': .WriteString("&gt;") default: .WriteRune() } } }