This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
// Attribute is the minimum method set of of an svg attribute.
|
||||
Attribute interface {
|
||||
// PrintKey must return the svg attribute key
|
||||
PrintKey() string
|
||||
// PrintVAlue must return the svg attribute value string, if applicable
|
||||
PrintValue() (string, bool)
|
||||
}
|
||||
)
|
||||
|
||||
func PrintAttribute(a Attribute) string {
|
||||
v, ok := a.PrintValue()
|
||||
if !ok {
|
||||
return a.PrintKey()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s=%s", a.PrintKey(), v)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Circle = VoidElement[CircleTag, CircleAttribute]
|
||||
|
||||
CircleAttribute interface {
|
||||
Attribute
|
||||
IsCircleAttribute()
|
||||
}
|
||||
|
||||
CircleTag struct{}
|
||||
)
|
||||
|
||||
func (CircleTag) PrintTag() string {
|
||||
return "circle"
|
||||
}
|
||||
|
||||
func (CX) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (CXP) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (CY) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (CYP) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (R) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (RP) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (PathLength) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (Fill) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (Stroke) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (StrokeWidth) IsCircleAttribute() {
|
||||
}
|
||||
|
||||
func (VectorEffect) IsCircleAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
)
|
||||
|
||||
type (
|
||||
Class string
|
||||
)
|
||||
|
||||
func (Class) PrintKey() string {
|
||||
return "class"
|
||||
}
|
||||
|
||||
func (c Class) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`"%s"`, html.EscapeString(string(c))), true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
CX struct {
|
||||
*LengthAttr[CXTag]
|
||||
}
|
||||
CXP struct {
|
||||
PercentageAttr[CXTag]
|
||||
}
|
||||
|
||||
CXTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsCX() CX {
|
||||
return CX{LengthAttr: (*LengthAttr[CXTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsCX() CXP {
|
||||
return CXP{PercentageAttr: (PercentageAttr[CXTag])(p)}
|
||||
}
|
||||
|
||||
func (CXTag) PrintTag() string {
|
||||
return "cx"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
CY struct {
|
||||
*LengthAttr[CYTag]
|
||||
}
|
||||
CYP struct {
|
||||
PercentageAttr[CYTag]
|
||||
}
|
||||
|
||||
CYTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsCY() CY {
|
||||
return CY{LengthAttr: (*LengthAttr[CYTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsCY() CYP {
|
||||
return CYP{PercentageAttr: (PercentageAttr[CYTag])(p)}
|
||||
}
|
||||
|
||||
func (CYTag) PrintTag() string {
|
||||
return "cy"
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
D []PathCommand
|
||||
|
||||
PathCommand interface {
|
||||
PrintPathCommand() (string, string, bool)
|
||||
PrintPathCommandParameters() string
|
||||
}
|
||||
|
||||
// PathCommand implementations
|
||||
|
||||
// PathRelative makes any PathCommand and converts it into a relative command (makes lowercase)
|
||||
PathRelative[C PathCommand] struct {
|
||||
Command C
|
||||
}
|
||||
|
||||
PathMoveTo struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
PathLineTo struct {
|
||||
X, Y float64
|
||||
}
|
||||
PathHorizontalLine float64
|
||||
PathVerticalLine float64
|
||||
|
||||
PathCubicBezier []PathCubicBezierParameterTuple
|
||||
PathCubicBezierParameterTuple struct {
|
||||
X1, Y1 float64
|
||||
X2, Y2 float64
|
||||
X, Y float64
|
||||
}
|
||||
PathSmoothCubicBezier []PathSmoothCubicBezierParameterTuple
|
||||
PathSmoothCubicBezierParameterTuple struct {
|
||||
X2, Y2 float64
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
PathQuadraticBezier []PathQuadraticBezierParameterTuple
|
||||
PathQuadraticBezierParameterTuple struct {
|
||||
X1, Y1 float64
|
||||
X, Y float64
|
||||
}
|
||||
PathSmoothQuadraticBezier []PathSmoothQuadraticBezierParameterTuple
|
||||
PathSmoothQuadraticBezierParameterTuple struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
PathElliptical []PathEllipticalParameterTuple
|
||||
PathEllipticalParameterTuple struct {
|
||||
RX, RY float64
|
||||
Angle float64
|
||||
LargeArc bool
|
||||
Clockwise bool
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
PathClose struct{}
|
||||
)
|
||||
|
||||
func PrintPathCommand(c PathCommand) (string, bool) {
|
||||
cmd, params, ok := c.PrintPathCommand()
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return fmt.Sprintf("%s %s", cmd, params), true
|
||||
}
|
||||
|
||||
func (c PathRelative[C]) PrintPathCommand() (string, string, bool) {
|
||||
cmd, params, ok := c.Command.PrintPathCommand()
|
||||
return strings.ToLower(cmd), params, ok
|
||||
}
|
||||
|
||||
func (c PathMoveTo) PrintPathCommand() (string, string, bool) {
|
||||
return "M", fmt.Sprintf("%v,%v", c.X, c.Y), true
|
||||
}
|
||||
|
||||
func (c PathLineTo) PrintPathCommand() (string, string, bool) {
|
||||
return "L", fmt.Sprintf("%v,%v", c.X, c.Y), true
|
||||
}
|
||||
|
||||
func (c PathHorizontalLine) PrintPathCommand() (string, string, bool) {
|
||||
return "H", fmt.Sprintf("%v", float64(c)), true
|
||||
}
|
||||
|
||||
func (c PathVerticalLine) PrintPathCommand() (string, string, bool) {
|
||||
return "V", fmt.Sprintf("%v", float64(c)), false
|
||||
}
|
||||
|
||||
func (c PathCubicBezier) PrintPathCommand() (string, string, bool) {
|
||||
if len(c) == 0 {
|
||||
return "", "", false
|
||||
}
|
||||
return "C", printTuples(c), true
|
||||
}
|
||||
|
||||
func (c PathCubicBezier) Append(x1, y1, x2, y2, x, y float64) PathCubicBezier {
|
||||
return append(c, PathCubicBezierParameterTuple{
|
||||
X1: x1,
|
||||
Y1: y1,
|
||||
X2: x2,
|
||||
Y2: y2,
|
||||
X: x,
|
||||
Y: y,
|
||||
})
|
||||
}
|
||||
|
||||
func (t PathCubicBezierParameterTuple) String() string {
|
||||
return fmt.Sprintf("%v,%v %v,%v %v,%v", t.X1, t.Y1, t.X2, t.Y2, t.X, t.Y)
|
||||
}
|
||||
|
||||
func (c PathSmoothCubicBezier) PrintPathCommand() (string, string, bool) {
|
||||
if len(c) == 0 {
|
||||
return "", "", false
|
||||
}
|
||||
return "S", printTuples(c), true
|
||||
}
|
||||
|
||||
func (c PathSmoothCubicBezier) Append(x2, y2, x, y float64) PathSmoothCubicBezier {
|
||||
return append(c, PathSmoothCubicBezierParameterTuple{
|
||||
X2: x2,
|
||||
Y2: y2,
|
||||
X: x,
|
||||
Y: y,
|
||||
})
|
||||
}
|
||||
|
||||
func (t PathSmoothCubicBezierParameterTuple) String() string {
|
||||
return fmt.Sprintf("%v,%v %v,%v", t.X2, t.Y2, t.X, t.Y)
|
||||
}
|
||||
|
||||
func (c PathQuadraticBezier) PrintPathCommand() (string, string, bool) {
|
||||
if len(c) == 0 {
|
||||
return "", "", false
|
||||
}
|
||||
return "Q", printTuples(c), true
|
||||
}
|
||||
|
||||
func (c PathQuadraticBezier) Append(x1, y1, x, y float64) PathQuadraticBezier {
|
||||
return append(c, PathQuadraticBezierParameterTuple{
|
||||
X1: x1,
|
||||
Y1: y1,
|
||||
X: x,
|
||||
Y: y,
|
||||
})
|
||||
}
|
||||
|
||||
func (t PathQuadraticBezierParameterTuple) String() string {
|
||||
return fmt.Sprintf("%v,%v %v,%v", t.X1, t.Y1, t.X, t.Y)
|
||||
}
|
||||
|
||||
func (c PathSmoothQuadraticBezier) PrintPathCommand() (string, string, bool) {
|
||||
if len(c) == 0 {
|
||||
return "", "", false
|
||||
}
|
||||
return "T", printTuples(c), true
|
||||
}
|
||||
|
||||
func (c PathSmoothQuadraticBezier) Append(x, y float64) PathSmoothQuadraticBezier {
|
||||
return append(c, PathSmoothQuadraticBezierParameterTuple{
|
||||
X: x,
|
||||
Y: y,
|
||||
})
|
||||
}
|
||||
|
||||
func (t PathSmoothQuadraticBezierParameterTuple) String() string {
|
||||
return fmt.Sprintf("%v,%v", t.X, t.Y)
|
||||
}
|
||||
|
||||
func (c PathElliptical) PrintPathCommand() (string, string, bool) {
|
||||
if len(c) == 0 {
|
||||
return "", "", false
|
||||
}
|
||||
return "A", printTuples(c), true
|
||||
}
|
||||
|
||||
func (c PathElliptical) Append(rx, ry, angle float64, largeArc, clockwise bool, x, y float64) PathElliptical {
|
||||
return append(c, PathEllipticalParameterTuple{
|
||||
RX: rx,
|
||||
RY: ry,
|
||||
Angle: angle,
|
||||
LargeArc: largeArc,
|
||||
Clockwise: clockwise,
|
||||
X: x,
|
||||
Y: y,
|
||||
})
|
||||
}
|
||||
|
||||
func (t PathEllipticalParameterTuple) String() string {
|
||||
var (
|
||||
largeArcFlag int
|
||||
sweepFlag int
|
||||
)
|
||||
|
||||
if t.LargeArc {
|
||||
largeArcFlag = 1
|
||||
}
|
||||
if t.Clockwise {
|
||||
sweepFlag = 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v,%v,%v,%d,%d,%v,%v", t.RX, t.RY, t.Angle, largeArcFlag, sweepFlag, t.X, t.Y)
|
||||
}
|
||||
|
||||
func printTuples[S fmt.Stringer](vs []S) string {
|
||||
ss := make([]string, len(vs))
|
||||
for i, v := range vs {
|
||||
ss[i] = fmt.Sprint(v)
|
||||
}
|
||||
return strings.Join(ss, " ")
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
DominantBaseline int
|
||||
)
|
||||
|
||||
const (
|
||||
DominantBaselineAuto DominantBaseline = iota
|
||||
DominantBaselineTextBottom
|
||||
DominantBaselineAlphabetic
|
||||
DominantBaselineIdeographic
|
||||
DominantBaselineMiddle
|
||||
DominantBaselineCentral
|
||||
DominantBaselineMathematical
|
||||
DominantBaselineHanging
|
||||
DominantBaselineTextTop
|
||||
)
|
||||
|
||||
func (a DominantBaseline) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", a), true
|
||||
}
|
||||
|
||||
func (a DominantBaseline) PrintKey() string {
|
||||
return "dominant-baseline"
|
||||
}
|
||||
|
||||
func (a DominantBaseline) String() string {
|
||||
switch a {
|
||||
case DominantBaselineTextBottom:
|
||||
return "text-bottom"
|
||||
case DominantBaselineAlphabetic:
|
||||
return "alphabetic"
|
||||
case DominantBaselineIdeographic:
|
||||
return "ideographic"
|
||||
case DominantBaselineMiddle:
|
||||
return "middle"
|
||||
case DominantBaselineCentral:
|
||||
return "central"
|
||||
case DominantBaselineMathematical:
|
||||
return "mathematical"
|
||||
case DominantBaselineHanging:
|
||||
return "hanging"
|
||||
case DominantBaselineTextTop:
|
||||
return "text-top"
|
||||
default:
|
||||
return "auto"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
DX struct {
|
||||
*LengthAttr[DXTag]
|
||||
}
|
||||
DXP struct {
|
||||
PercentageAttr[DXTag]
|
||||
}
|
||||
|
||||
DXTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsDX() DX {
|
||||
return DX{LengthAttr: (*LengthAttr[DXTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsDX() DXP {
|
||||
return DXP{PercentageAttr: (PercentageAttr[DXTag])(p)}
|
||||
}
|
||||
|
||||
func (DXTag) PrintTag() string {
|
||||
return "dx"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
DY struct {
|
||||
*LengthAttr[DYTag]
|
||||
}
|
||||
DYP struct {
|
||||
PercentageAttr[DYTag]
|
||||
}
|
||||
|
||||
DYTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsDY() DY {
|
||||
return DY{LengthAttr: (*LengthAttr[DYTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsDY() DYP {
|
||||
return DYP{PercentageAttr: (PercentageAttr[DYTag])(p)}
|
||||
}
|
||||
|
||||
func (DYTag) PrintTag() string {
|
||||
return "dy"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// Element is a convenience for implementing SVG element models.
|
||||
// Element implements MarshalerReader.
|
||||
Element[T Tag, A Attribute, C MarshalerReader] struct {
|
||||
Attributes []A
|
||||
Children []C
|
||||
}
|
||||
)
|
||||
|
||||
func (e *Element[T, A, C]) Attr(as ...A) *Element[T, A, C] {
|
||||
e.Attributes = append(e.Attributes, as...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Element[T, A, C]) Child(cs ...C) *Element[T, A, C] {
|
||||
e.Children = append(e.Children, cs...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Element[T, A, C]) GetMarkup() string {
|
||||
attrs := make([]string, len(e.Attributes))
|
||||
for i, a := range e.Attributes {
|
||||
attrs[i] = PrintAttribute(a)
|
||||
}
|
||||
|
||||
children := make([]string, len(e.Children))
|
||||
for i, c := range e.Children {
|
||||
children[i] = c.GetMarkup()
|
||||
}
|
||||
|
||||
tag := e.PrintTag()
|
||||
|
||||
return fmt.Sprintf(
|
||||
`<%s %s>%s</%s>`,
|
||||
tag,
|
||||
strings.Join(attrs, " "),
|
||||
strings.Join(children, ""),
|
||||
tag,
|
||||
)
|
||||
}
|
||||
|
||||
func (e Element[T, A, C]) GetMarkupReader() io.Reader {
|
||||
return newElementReader(e)
|
||||
}
|
||||
|
||||
func (e Element[T, _, _]) PrintTag() string {
|
||||
var t T
|
||||
return t.PrintTag()
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
elementReader[T Tag, A Attribute, C MarshalerReader] struct {
|
||||
element Element[T, A, C]
|
||||
buf *bytes.Buffer
|
||||
|
||||
openTagRead bool
|
||||
numChildrenRead int
|
||||
childrenRead bool
|
||||
childReader io.Reader
|
||||
closeTagRead bool
|
||||
}
|
||||
)
|
||||
|
||||
func newElementReader[T Tag, A Attribute, C MarshalerReader](e Element[T, A, C]) *elementReader[T, A, C] {
|
||||
return &elementReader[T, A, C]{
|
||||
element: e,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *elementReader[T, A, C]) Read(p []byte) (totalRead int, err error) {
|
||||
if e.buf == nil {
|
||||
// just started reading.
|
||||
// buffer the open tag
|
||||
|
||||
e.buf = new(bytes.Buffer)
|
||||
|
||||
// bytes.Buffer never returns an error on Write()
|
||||
e.buf.WriteByte('<')
|
||||
e.buf.WriteString(e.element.PrintTag())
|
||||
for _, a := range e.element.Attributes {
|
||||
e.buf.WriteByte(' ')
|
||||
e.buf.WriteString(PrintAttribute(a))
|
||||
}
|
||||
e.buf.WriteByte('>')
|
||||
}
|
||||
|
||||
if !e.openTagRead {
|
||||
// read the open tag
|
||||
|
||||
n, err := e.buf.Read(p)
|
||||
totalRead += n
|
||||
if (err != nil && !errors.Is(err, io.EOF)) || n >= len(p) {
|
||||
return totalRead, err
|
||||
}
|
||||
|
||||
// done reading the open tag
|
||||
|
||||
e.openTagRead = true
|
||||
e.buf.Reset()
|
||||
|
||||
p = p[n:]
|
||||
}
|
||||
|
||||
// read the children
|
||||
|
||||
for ; e.numChildrenRead < len(e.element.Children); e.numChildrenRead += 1 {
|
||||
if e.childReader == nil {
|
||||
e.childReader = e.element.Children[e.numChildrenRead].GetMarkupReader()
|
||||
}
|
||||
|
||||
// read the child
|
||||
|
||||
n, err := e.childReader.Read(p)
|
||||
totalRead += n
|
||||
if (err != nil && !errors.Is(err, io.EOF)) || n >= len(p) {
|
||||
return totalRead, err
|
||||
}
|
||||
|
||||
// done reading the child
|
||||
|
||||
e.childReader = nil
|
||||
|
||||
p = p[n:]
|
||||
}
|
||||
|
||||
if !e.childrenRead {
|
||||
// done reading the children
|
||||
|
||||
e.childrenRead = true
|
||||
|
||||
// buffer the close tag
|
||||
|
||||
e.buf.Write([]byte("</"))
|
||||
e.buf.WriteString(e.element.PrintTag())
|
||||
e.buf.WriteByte('>')
|
||||
}
|
||||
|
||||
if !e.closeTagRead {
|
||||
n, err := e.buf.Read(p)
|
||||
totalRead += n
|
||||
if (err != nil && !errors.Is(err, io.EOF)) || n >= len(p) {
|
||||
return totalRead, err
|
||||
}
|
||||
|
||||
// done writing the close tag
|
||||
|
||||
e.closeTagRead = true
|
||||
return totalRead, err
|
||||
}
|
||||
|
||||
return 0, io.EOF
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Ellipse = VoidElement[EllipseTag, EllipseAttribute]
|
||||
|
||||
EllipseTag struct{}
|
||||
|
||||
EllipseAttribute interface {
|
||||
Attribute
|
||||
IsEllipseAttribute()
|
||||
}
|
||||
)
|
||||
|
||||
func (EllipseTag) PrintTag() string {
|
||||
return "ellipse"
|
||||
}
|
||||
|
||||
func (RX) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (RXP) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (RY) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (RYP) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (CX) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (CXP) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (CY) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (CYP) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (PathLength) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (Stroke) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (StrokeWidth) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (Fill) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (Style) IsEllipseAttribute() {
|
||||
}
|
||||
|
||||
func (VectorEffect) IsEllipseAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
Fill string
|
||||
)
|
||||
|
||||
func (Fill) PrintKey() string {
|
||||
return "fill"
|
||||
}
|
||||
|
||||
func (f Fill) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`%q`, string(f)), true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
FontSize struct {
|
||||
*LengthAttr[FontSizeTag]
|
||||
}
|
||||
FontSizeP struct {
|
||||
PercentageAttr[FontSizeTag]
|
||||
}
|
||||
|
||||
FontSizeTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsFontSize() FontSize {
|
||||
return FontSize{LengthAttr: (*LengthAttr[FontSizeTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsFontSize() FontSizeP {
|
||||
return FontSizeP{PercentageAttr: (PercentageAttr[FontSizeTag])(p)}
|
||||
}
|
||||
|
||||
func (FontSizeTag) PrintTag() string {
|
||||
return "font-size"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
G struct {
|
||||
Element[GTag, GAttribute, GChildren]
|
||||
}
|
||||
GTag struct{}
|
||||
|
||||
GAttribute interface {
|
||||
Attribute
|
||||
IsGAttribute()
|
||||
}
|
||||
|
||||
GChildren = MarshalerReader
|
||||
)
|
||||
|
||||
func (GTag) PrintTag() string {
|
||||
return "g"
|
||||
}
|
||||
|
||||
func (Transform) IsGAttribute() {
|
||||
}
|
||||
|
||||
func (X) IsGAttribute() {
|
||||
}
|
||||
|
||||
func (XP) IsGAttribute() {
|
||||
}
|
||||
|
||||
func (Y) IsGAttribute() {
|
||||
}
|
||||
|
||||
func (YP) IsGAttribute() {
|
||||
}
|
||||
|
||||
func (Class) IsGAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Height struct {
|
||||
*LengthAttr[HeightTag]
|
||||
}
|
||||
HeightP struct {
|
||||
PercentageAttr[HeightTag]
|
||||
}
|
||||
|
||||
HeightTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsHeight() Height {
|
||||
return Height{LengthAttr: (*LengthAttr[HeightTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsHeightP() HeightP {
|
||||
return HeightP{PercentageAttr: (PercentageAttr[HeightTag])(p)}
|
||||
}
|
||||
|
||||
func (HeightTag) PrintTag() string {
|
||||
return "height"
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
Length struct {
|
||||
number float64
|
||||
absUnit AbsoluteLengthUnit
|
||||
relUnit RelativeLengthUnit
|
||||
}
|
||||
|
||||
LengthAttr[T Tag] Length
|
||||
)
|
||||
|
||||
func NewLength(n float64) *Length {
|
||||
return &Length{
|
||||
number: n,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Length) Number(n float64) *Length {
|
||||
l.number = n
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Length) Unit(u AbsoluteLengthUnit) *Length {
|
||||
l.absUnit = u
|
||||
l.relUnit = 0
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Length) RUnit(u RelativeLengthUnit) *Length {
|
||||
l.relUnit = u
|
||||
l.absUnit = 0
|
||||
return l
|
||||
}
|
||||
|
||||
func (l Length) String() string {
|
||||
if l.absUnit != 0 {
|
||||
return fmt.Sprintf("%v%s", l.number, l.absUnit)
|
||||
}
|
||||
if l.relUnit != 0 {
|
||||
return fmt.Sprintf("%v%s", l.number, l.relUnit)
|
||||
}
|
||||
return fmt.Sprintf("%v", l.number)
|
||||
}
|
||||
|
||||
func (LengthAttr[T]) PrintKey() string {
|
||||
var tag T
|
||||
return tag.PrintTag()
|
||||
}
|
||||
|
||||
func (a LengthAttr[T]) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`"%s"`, Length(a)), true
|
||||
}
|
||||
|
||||
// Relative length units
|
||||
type RelativeLengthUnit int
|
||||
|
||||
const (
|
||||
_ RelativeLengthUnit = iota
|
||||
|
||||
// based on font
|
||||
Cap
|
||||
Ch
|
||||
Em
|
||||
Ex
|
||||
Ic
|
||||
Lh
|
||||
|
||||
// based on root element's font
|
||||
Rcap
|
||||
Rch
|
||||
Rem
|
||||
Rex
|
||||
Ric
|
||||
Rlh
|
||||
|
||||
// based on viewport
|
||||
Vh
|
||||
Vw
|
||||
Vmax
|
||||
Vmin
|
||||
Vb
|
||||
Vi
|
||||
// small viewport
|
||||
Svh
|
||||
Svw
|
||||
Svmax
|
||||
Svmin
|
||||
Svb
|
||||
Svi
|
||||
// large viewport
|
||||
Lvh
|
||||
Lvw
|
||||
Lvmax
|
||||
Lvmin
|
||||
Lvb
|
||||
Lvi
|
||||
// dynamic viewport
|
||||
Dvh
|
||||
Dvw
|
||||
Dvmax
|
||||
Dvmin
|
||||
Dvb
|
||||
Dvi
|
||||
|
||||
// container query
|
||||
Cqw
|
||||
Cqh
|
||||
Cqi
|
||||
Cqb
|
||||
Cqmin
|
||||
Cqmax
|
||||
)
|
||||
|
||||
func (u RelativeLengthUnit) String() string {
|
||||
switch u {
|
||||
case Cap:
|
||||
return "cap"
|
||||
case Ch:
|
||||
return "ch"
|
||||
case Em:
|
||||
return "em"
|
||||
case Ex:
|
||||
return "ex"
|
||||
case Ic:
|
||||
return "ic"
|
||||
case Lh:
|
||||
return "lh"
|
||||
case Rcap:
|
||||
return "rcap"
|
||||
case Rch:
|
||||
return "rch"
|
||||
case Rem:
|
||||
return "rem"
|
||||
case Rex:
|
||||
return "rex"
|
||||
case Ric:
|
||||
return "ric"
|
||||
case Rlh:
|
||||
return "rlh"
|
||||
case Vh:
|
||||
return "vh"
|
||||
case Vw:
|
||||
return "vw"
|
||||
case Vmax:
|
||||
return "vmax"
|
||||
case Vmin:
|
||||
return "vmin"
|
||||
case Vb:
|
||||
return "vb"
|
||||
case Vi:
|
||||
return "vi"
|
||||
case Svh:
|
||||
return "svh"
|
||||
case Svw:
|
||||
return "svw"
|
||||
case Svmax:
|
||||
return "svmax"
|
||||
case Svmin:
|
||||
return "svmin"
|
||||
case Svb:
|
||||
return "svb"
|
||||
case Svi:
|
||||
return "svi"
|
||||
case Lvh:
|
||||
return "lvh"
|
||||
case Lvw:
|
||||
return "lvw"
|
||||
case Lvmax:
|
||||
return "lvmax"
|
||||
case Lvmin:
|
||||
return "lvmin"
|
||||
case Lvb:
|
||||
return "lvb"
|
||||
case Lvi:
|
||||
return "lvi"
|
||||
case Dvh:
|
||||
return "dvh"
|
||||
case Dvw:
|
||||
return "dvw"
|
||||
case Dvmax:
|
||||
return "dvmax"
|
||||
case Dvmin:
|
||||
return "dvmin"
|
||||
case Dvb:
|
||||
return "dvb"
|
||||
case Dvi:
|
||||
return "dvi"
|
||||
case Cqw:
|
||||
return "cqw"
|
||||
case Cqh:
|
||||
return "cqh"
|
||||
case Cqi:
|
||||
return "cqi"
|
||||
case Cqb:
|
||||
return "cqb"
|
||||
case Cqmin:
|
||||
return "cqmin"
|
||||
case Cqmax:
|
||||
return "cqmax"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Absolute length units
|
||||
type AbsoluteLengthUnit int
|
||||
|
||||
const (
|
||||
_ AbsoluteLengthUnit = iota
|
||||
Px
|
||||
Cm
|
||||
Mm
|
||||
Q
|
||||
In
|
||||
Pc
|
||||
Pt
|
||||
)
|
||||
|
||||
func (u AbsoluteLengthUnit) String() string {
|
||||
switch u {
|
||||
case Px:
|
||||
return "px"
|
||||
case Cm:
|
||||
return "cm"
|
||||
case Mm:
|
||||
return "mm"
|
||||
case Q:
|
||||
return "q"
|
||||
case In:
|
||||
return "in"
|
||||
case Pc:
|
||||
return "pc"
|
||||
case Pt:
|
||||
return "pt"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
LengthAdjust int
|
||||
)
|
||||
|
||||
const (
|
||||
LengthAdjustSpacing LengthAdjust = iota
|
||||
LengthAdjustSpacingAndGlyph
|
||||
)
|
||||
|
||||
func (LengthAdjust) PrintTag() string {
|
||||
return "lengthAdjust"
|
||||
}
|
||||
|
||||
func (l LengthAdjust) PrintValue() (string, bool) {
|
||||
switch l {
|
||||
case LengthAdjustSpacingAndGlyph:
|
||||
return "spacingAndGlyphs", true
|
||||
default:
|
||||
return "spacing", true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Line = VoidElement[LineTag, LineAttribute]
|
||||
|
||||
LineTag struct{}
|
||||
|
||||
LineAttribute interface {
|
||||
Attribute
|
||||
IsLineAttribute()
|
||||
}
|
||||
)
|
||||
|
||||
func (LineTag) PrintTag() string {
|
||||
return "line"
|
||||
}
|
||||
|
||||
func (X1) IsLineAttribute() {
|
||||
}
|
||||
|
||||
func (X2) IsLineAttribute() {
|
||||
}
|
||||
|
||||
func (Y1) IsLineAttribute() {
|
||||
}
|
||||
|
||||
func (Y2) IsLineAttribute() {
|
||||
}
|
||||
|
||||
func (PathLength) IsLineAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package svg
|
||||
|
||||
import "io"
|
||||
|
||||
type (
|
||||
// All SVG element models must satisfy the Marshaler interface.
|
||||
Marshaler interface {
|
||||
GetMarkup() string
|
||||
}
|
||||
|
||||
Reader interface {
|
||||
GetMarkupReader() io.Reader
|
||||
}
|
||||
|
||||
MarshalerReader interface {
|
||||
Marshaler
|
||||
Reader
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Path = VoidElement[PathTag, PathAttribute]
|
||||
|
||||
PathTag struct{}
|
||||
|
||||
PathAttribute interface {
|
||||
Attribute
|
||||
IsPathAttribute()
|
||||
}
|
||||
)
|
||||
|
||||
func (PathTag) PrintTag() string {
|
||||
return "path"
|
||||
}
|
||||
|
||||
func (D) IsPathAttribute() {
|
||||
}
|
||||
|
||||
func (PathLength) IsPathAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
PathLength float64
|
||||
)
|
||||
|
||||
func (PathLength) PrintKey() string {
|
||||
return "pathLength"
|
||||
}
|
||||
|
||||
func (l PathLength) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`"%v"`, l), true
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
Percentage float64
|
||||
|
||||
PercentageAttr[T Tag] Percentage
|
||||
)
|
||||
|
||||
func (p Percentage) String() string {
|
||||
return fmt.Sprintf("%v%%", float64(p))
|
||||
}
|
||||
|
||||
func (PercentageAttr[T]) PrintKey() string {
|
||||
var t T
|
||||
return t.PrintTag()
|
||||
}
|
||||
|
||||
func (a PercentageAttr[T]) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", Percentage(a)), true
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
Points []Point
|
||||
Point struct {
|
||||
X float64
|
||||
Y float64
|
||||
}
|
||||
)
|
||||
|
||||
func (Points) PrintKey() string {
|
||||
return "points"
|
||||
}
|
||||
|
||||
func (ps Points) PrintValue() (string, bool) {
|
||||
ss := make([]string, len(ps))
|
||||
for i, p := range ps {
|
||||
ss[i] = fmt.Sprintf("%v,%v", p.X, p.Y)
|
||||
}
|
||||
return fmt.Sprintf("%q", strings.Join(ss, " ")), true
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Polygon = VoidElement[PolygonTag, PolygonAttribute]
|
||||
|
||||
PolygonTag struct{}
|
||||
|
||||
PolygonAttribute interface {
|
||||
Attribute
|
||||
IsPolygonAttribute()
|
||||
}
|
||||
)
|
||||
|
||||
func (PolygonTag) PrintTag() string {
|
||||
return "path"
|
||||
}
|
||||
|
||||
func (Points) IsPolygonAttribute() {
|
||||
}
|
||||
|
||||
func (PathLength) IsPolygonAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Polyline = VoidElement[PolylineTag, PolylineAttribute]
|
||||
|
||||
PolylineTag struct{}
|
||||
|
||||
PolylineAttribute interface {
|
||||
Attribute
|
||||
IsPolylineAttribute()
|
||||
}
|
||||
)
|
||||
|
||||
func (PolylineTag) PrintTag() string {
|
||||
return "polyline"
|
||||
}
|
||||
|
||||
func (PathLength) IsPolylineAttribute() {
|
||||
}
|
||||
|
||||
func (Points) IsPolylineAttribute() {
|
||||
}
|
||||
|
||||
func (Stroke) IsPolylineAttribute() {
|
||||
}
|
||||
|
||||
func (Fill) IsPolylineAttribute() {
|
||||
}
|
||||
|
||||
func (StrokeWidth) IsPolylineAttribute() {
|
||||
}
|
||||
|
||||
func (StrokeWidthP) IsPolylineAttribute() {
|
||||
}
|
||||
|
||||
func (VectorEffect) IsPolylineAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
// TODO: consider reworking this WHOLE API/PACKAGE to make it more of a chaining API.
|
||||
|
||||
type (
|
||||
PreserveAspectRatio struct {
|
||||
Align *AlignValue
|
||||
MeetOrSlice MeetOrSliceValue
|
||||
}
|
||||
|
||||
AlignValue struct {
|
||||
X AlignValueComponent
|
||||
Y AlignValueComponent
|
||||
}
|
||||
AlignValueComponent int
|
||||
MeetOrSliceValue int
|
||||
)
|
||||
|
||||
const (
|
||||
AlignMid AlignValueComponent = iota
|
||||
AlignMin
|
||||
AlignMax
|
||||
|
||||
_ MeetOrSliceValue = iota
|
||||
Meet
|
||||
Slice
|
||||
)
|
||||
|
||||
func (r PreserveAspectRatio) PrintKey() string {
|
||||
return "preserveAspectRatio"
|
||||
}
|
||||
|
||||
func (r PreserveAspectRatio) PrintValue() (string, bool) {
|
||||
if mos := r.MeetOrSlice.String(); mos != "" {
|
||||
return fmt.Sprintf(`"%s %s"`, r.Align, mos), true
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%q", r.Align), true
|
||||
}
|
||||
|
||||
func (v *AlignValue) String() string {
|
||||
if v == nil {
|
||||
return "none"
|
||||
}
|
||||
return fmt.Sprintf("x%sY%s", v.X, v.Y)
|
||||
}
|
||||
|
||||
func (v AlignValueComponent) String() string {
|
||||
switch v {
|
||||
case AlignMin:
|
||||
return "Min"
|
||||
case AlignMax:
|
||||
return "Max"
|
||||
default:
|
||||
return "Mid"
|
||||
}
|
||||
}
|
||||
|
||||
func (v MeetOrSliceValue) String() string {
|
||||
switch v {
|
||||
case Meet:
|
||||
return "meet"
|
||||
case Slice:
|
||||
return "slice"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
R struct {
|
||||
*LengthAttr[RTag]
|
||||
}
|
||||
RP struct {
|
||||
PercentageAttr[RTag]
|
||||
}
|
||||
|
||||
RTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsR() R {
|
||||
return R{LengthAttr: (*LengthAttr[RTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsR() RP {
|
||||
return RP{PercentageAttr: (PercentageAttr[RTag])(p)}
|
||||
}
|
||||
|
||||
func (RTag) PrintTag() string {
|
||||
return "r"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
// Rect models a <rect> element
|
||||
Rect = VoidElement[RectTag, RectAttribute]
|
||||
RectTag struct{}
|
||||
|
||||
// SVGAttributes are Attributes allowed on SVGs
|
||||
RectAttribute interface {
|
||||
Attribute
|
||||
IsRectAttribute()
|
||||
}
|
||||
)
|
||||
|
||||
func (RectTag) PrintTag() string {
|
||||
return "rect"
|
||||
}
|
||||
|
||||
func (X) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (XP) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (Y) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (YP) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (Width) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (WidthP) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (Height) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (HeightP) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (RX) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (RXP) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (RY) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (RYP) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (PathLength) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (Class) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (Fill) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (Stroke) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (StrokeWidth) IsRectAttribute() {
|
||||
}
|
||||
|
||||
func (Style) IsRectAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
Rotate float64
|
||||
RotateAuto struct{}
|
||||
RotateAutoReverse struct{}
|
||||
)
|
||||
|
||||
func (r Rotate) PrintKey() string {
|
||||
return "rotate"
|
||||
}
|
||||
|
||||
func (r Rotate) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`"%v"`, float64(r)), true
|
||||
}
|
||||
|
||||
func (r RotateAuto) PrintKey() string {
|
||||
return "rotate"
|
||||
}
|
||||
|
||||
func (r RotateAuto) PrintValue() (string, bool) {
|
||||
return `"auto"`, true
|
||||
}
|
||||
|
||||
func (r RotateAutoReverse) PrintKey() string {
|
||||
return "rotate"
|
||||
}
|
||||
|
||||
func (r RotateAutoReverse) PrintValue() (string, bool) {
|
||||
return `"auto-reverse"`, true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
RX struct {
|
||||
*LengthAttr[RXTag]
|
||||
}
|
||||
RXP struct {
|
||||
PercentageAttr[RXTag]
|
||||
}
|
||||
|
||||
RXTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsRX() RX {
|
||||
return RX{LengthAttr: (*LengthAttr[RXTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsRX() RXP {
|
||||
return RXP{PercentageAttr: (PercentageAttr[RXTag])(p)}
|
||||
}
|
||||
|
||||
func (RXTag) PrintTag() string {
|
||||
return "rx"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
RY struct {
|
||||
*LengthAttr[RYTag]
|
||||
}
|
||||
RYP struct {
|
||||
PercentageAttr[RYTag]
|
||||
}
|
||||
|
||||
RYTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsRY() RY {
|
||||
return RY{LengthAttr: (*LengthAttr[RYTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsRY() RYP {
|
||||
return RYP{PercentageAttr: (PercentageAttr[RYTag])(p)}
|
||||
}
|
||||
|
||||
func (RYTag) PrintTag() string {
|
||||
return "ry"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
Stroke string
|
||||
)
|
||||
|
||||
func (Stroke) PrintKey() string {
|
||||
return "stroke"
|
||||
}
|
||||
|
||||
func (s Stroke) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`%q`, string(s)), true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
StrokeWidth struct {
|
||||
*LengthAttr[StrokeWidthTag]
|
||||
}
|
||||
StrokeWidthP struct {
|
||||
PercentageAttr[StrokeWidthTag]
|
||||
}
|
||||
|
||||
StrokeWidthTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsStrokeWidth() StrokeWidth {
|
||||
return StrokeWidth{LengthAttr: (*LengthAttr[StrokeWidthTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsStrokeWidth() StrokeWidthP {
|
||||
return StrokeWidthP{PercentageAttr: (PercentageAttr[StrokeWidthTag])(p)}
|
||||
}
|
||||
|
||||
func (StrokeWidthTag) PrintTag() string {
|
||||
return "stroke-width"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
Style map[string]string
|
||||
)
|
||||
|
||||
func (m Style) PrintKey() string {
|
||||
return "style"
|
||||
}
|
||||
|
||||
func (m Style) PrintValue() (string, bool) {
|
||||
keys := slices.Collect(maps.Keys(m))
|
||||
slices.Sort(keys)
|
||||
|
||||
parts := make([]string, len(m))
|
||||
for i, k := range keys {
|
||||
parts[i] = fmt.Sprintf(`%s: %s`, k, html.EscapeString(m[k]))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%q", strings.Join(parts, "; ")), true
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
// SVG models an <svg> element
|
||||
SVG struct {
|
||||
Element[SVGTag, SVGAttribute, SVGChildren]
|
||||
}
|
||||
SVGTag struct{}
|
||||
|
||||
// SVGAttributes are Attributes allowed on SVGs
|
||||
SVGAttribute interface {
|
||||
Attribute
|
||||
IsSVGAttribute()
|
||||
}
|
||||
|
||||
SVGChildren = MarshalerReader
|
||||
)
|
||||
|
||||
// NewSVG create a SVG with the common version 1.1 and svg namespace atributes
|
||||
func NewSVG() *SVG {
|
||||
return &SVG{
|
||||
Element: Element[SVGTag, SVGAttribute, MarshalerReader]{
|
||||
Attributes: []SVGAttribute{
|
||||
SVGVersion{
|
||||
Major: 1,
|
||||
Minor: 1,
|
||||
},
|
||||
XMLNSW32000SVG,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t SVGTag) PrintTag() string {
|
||||
return "svg"
|
||||
}
|
||||
|
||||
func (X) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (XP) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (Y) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (YP) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (Width) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (WidthP) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (Height) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (HeightP) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (ViewBox) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (Style) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (Class) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (PreserveAspectRatio) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (SVGVersion) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (XMLNS) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (Transform) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (TransformRotate) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (TransformRotateAbout) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (TransformTranslate) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (TransformSkewX) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (TransformSkewY) IsSVGAttribute() {
|
||||
}
|
||||
|
||||
func (TransformScale) IsSVGAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func ExampleSVG() {
|
||||
s := NewSVG().
|
||||
Attr(
|
||||
NewLength(10).
|
||||
AsX(),
|
||||
NewLength(20).
|
||||
AsY(),
|
||||
Style{
|
||||
"font-size": "8px",
|
||||
},
|
||||
).
|
||||
Child(
|
||||
Rect{
|
||||
Attributes: []RectAttribute{
|
||||
NewLength(30).
|
||||
AsX(),
|
||||
Percentage(40).
|
||||
AsY(),
|
||||
},
|
||||
},
|
||||
NewText("abc 123"),
|
||||
)
|
||||
|
||||
b, err := io.ReadAll(s.GetMarkupReader())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
// Output: <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="10" y="20" style="font-size: 8px"><rect x="30" y="40%"/><text>abc 123</text></svg>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Tag interface {
|
||||
PrintTag() string
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,130 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html"
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
// Text models an <text> element
|
||||
Text = Element[TextTag, TextAttribute, TextChild]
|
||||
TextTag struct{}
|
||||
TextAttribute interface {
|
||||
Attribute
|
||||
IsTextAttribute()
|
||||
}
|
||||
TextChild interface {
|
||||
MarshalerReader
|
||||
IsTextChild()
|
||||
}
|
||||
|
||||
RawText string
|
||||
)
|
||||
|
||||
func NewText(t string) *Text {
|
||||
return &Text{
|
||||
Children: []TextChild{
|
||||
RawText(t),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t TextTag) PrintTag() string {
|
||||
return "text"
|
||||
}
|
||||
|
||||
func (t RawText) GetMarkup() string {
|
||||
return html.EscapeString(string(t))
|
||||
}
|
||||
|
||||
func (t RawText) GetMarkupReader() io.Reader {
|
||||
return bytes.NewReader([]byte(t.GetMarkup()))
|
||||
}
|
||||
|
||||
func (t RawText) IsTextChild() {
|
||||
}
|
||||
|
||||
func (X) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (XP) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (Y) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (YP) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (DX) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (DXP) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (DY) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (DYP) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (Rotate) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (RotateAuto) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (RotateAutoReverse) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (Fill) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TextLength) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TextLengthP) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (LengthAdjust) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TextAnchor) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (FontSize) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (FontSizeP) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (Transform) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TransformRotate) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TransformRotateAbout) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TransformTranslate) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TransformSkewX) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TransformSkewY) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (TransformScale) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (DominantBaseline) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (Style) IsTextAttribute() {
|
||||
}
|
||||
|
||||
func (Class) IsTextAttribute() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
TextAnchor int
|
||||
)
|
||||
|
||||
const (
|
||||
TextAnchorStart TextAnchor = iota
|
||||
TextAnchorMiddle
|
||||
TextAnchorEnd
|
||||
)
|
||||
|
||||
func (TextAnchor) PrintKey() string {
|
||||
return "text-anchor"
|
||||
}
|
||||
|
||||
func (t TextAnchor) PrintValue() (string, bool) {
|
||||
switch t {
|
||||
case TextAnchorMiddle:
|
||||
return `"middle"`, true
|
||||
case TextAnchorEnd:
|
||||
return `"end"`, true
|
||||
default:
|
||||
return `"start"`, true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
TextLength struct {
|
||||
*LengthAttr[TextLengthTag]
|
||||
}
|
||||
TextLengthP struct {
|
||||
PercentageAttr[TextLengthTag]
|
||||
}
|
||||
|
||||
TextLengthTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsTextLength() TextLength {
|
||||
return TextLength{LengthAttr: (*LengthAttr[TextLengthTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsTextLength() TextLengthP {
|
||||
return TextLengthP{PercentageAttr: (PercentageAttr[TextLengthTag])(p)}
|
||||
}
|
||||
|
||||
func (TextLengthTag) PrintTag() string {
|
||||
return "textLength"
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO consider replacing the Transform matrices with a single 'matrix' type with different constructors and a builder/chaining api.
|
||||
|
||||
type (
|
||||
Transform []TransformExpression
|
||||
|
||||
// Transform is itself a Transform, meaning you could nest them, if you wanted.
|
||||
TransformExpression interface {
|
||||
PrintTransform() string
|
||||
}
|
||||
|
||||
// TransformExpression implementations
|
||||
// They can tehemselves be used as standalone 'transform' attributes.
|
||||
|
||||
TransformRotate float64
|
||||
TransformRotateAbout struct {
|
||||
A float64
|
||||
X, Y float64
|
||||
}
|
||||
TransformTranslate struct {
|
||||
X, Y float64
|
||||
}
|
||||
TransformSkewX float64
|
||||
TransformSkewY float64
|
||||
TransformScale struct {
|
||||
X, Y float64
|
||||
}
|
||||
)
|
||||
|
||||
func (Transform) PrintKey() string {
|
||||
return "transform"
|
||||
}
|
||||
|
||||
func (ts Transform) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", ts.PrintTransform()), true
|
||||
}
|
||||
|
||||
func (ts Transform) PrintTransform() string {
|
||||
ss := make([]string, len(ts))
|
||||
for i, t := range ts {
|
||||
ss[i] = t.PrintTransform()
|
||||
}
|
||||
|
||||
return strings.Join(ss, " ")
|
||||
}
|
||||
|
||||
func (ts Transform) Rotate(a float64) Transform {
|
||||
return append(ts, TransformRotate(a))
|
||||
}
|
||||
|
||||
func (ts Transform) RotateAbout(a, x, y float64) Transform {
|
||||
return append(ts, TransformRotateAbout{A: a, X: x, Y: y})
|
||||
}
|
||||
|
||||
func (ts Transform) Translate(x, y float64) Transform {
|
||||
return append(ts, TransformTranslate{X: x, Y: y})
|
||||
}
|
||||
|
||||
func (ts Transform) SkewX(x float64) Transform {
|
||||
return append(ts, TransformSkewX(x))
|
||||
}
|
||||
|
||||
func (ts Transform) SkewY(y float64) Transform {
|
||||
return append(ts, TransformSkewY(y))
|
||||
}
|
||||
|
||||
func (ts Transform) Scale(x, y float64) Transform {
|
||||
return append(ts, TransformScale{X: x, Y: y})
|
||||
}
|
||||
|
||||
func (TransformRotate) PrintKey() string {
|
||||
return "transform"
|
||||
}
|
||||
|
||||
func (t TransformRotate) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", t.PrintTransform()), true
|
||||
}
|
||||
|
||||
func (t TransformRotate) PrintTransform() string {
|
||||
return fmt.Sprintf("rotate(%v)", float64(t))
|
||||
}
|
||||
|
||||
func (t TransformRotate) About(x, y float64) TransformRotateAbout {
|
||||
return TransformRotateAbout{
|
||||
A: float64(t),
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
}
|
||||
|
||||
func (TransformRotateAbout) PrintKey() string {
|
||||
return "transform"
|
||||
}
|
||||
|
||||
func (t TransformRotateAbout) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", t.PrintTransform()), true
|
||||
}
|
||||
|
||||
func (t TransformRotateAbout) PrintTransform() string {
|
||||
return fmt.Sprintf("rotate(%v %v %v)", t.A, t.X, t.Y)
|
||||
}
|
||||
|
||||
func (TransformTranslate) PrintKey() string {
|
||||
return "transform"
|
||||
}
|
||||
|
||||
func (t TransformTranslate) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", t.PrintTransform()), true
|
||||
}
|
||||
|
||||
func (t TransformTranslate) PrintTransform() string {
|
||||
return fmt.Sprintf("translate(%v %v)", t.X, t.Y)
|
||||
}
|
||||
|
||||
func (TransformSkewX) PrintKey() string {
|
||||
return "transform"
|
||||
}
|
||||
|
||||
func (t TransformSkewX) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", t.PrintTransform()), true
|
||||
}
|
||||
|
||||
func (t TransformSkewX) PrintTransform() string {
|
||||
return fmt.Sprintf("skewX(%v)", float64(t))
|
||||
}
|
||||
|
||||
func (TransformSkewY) PrintKey() string {
|
||||
return "transform"
|
||||
}
|
||||
|
||||
func (t TransformSkewY) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", t.PrintTransform()), true
|
||||
}
|
||||
|
||||
func (t TransformSkewY) PrintTransform() string {
|
||||
return fmt.Sprintf("skewY(%v)", float64(t))
|
||||
}
|
||||
|
||||
func (TransformScale) PrintKey() string {
|
||||
return "transform"
|
||||
}
|
||||
|
||||
func (t TransformScale) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", t.PrintTransform()), true
|
||||
}
|
||||
|
||||
func (t TransformScale) PrintTransform() string {
|
||||
return fmt.Sprintf("scale(%v %v)", t.X, t.Y)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
VectorEffect int
|
||||
)
|
||||
|
||||
const (
|
||||
VectorEffectNone VectorEffect = iota
|
||||
VectorEffectNonScalingStroke
|
||||
// DO NOT USE - no browser support yet!
|
||||
VectorEffectNonScalingSize
|
||||
// DO NOT USE - no browser support yet!
|
||||
VectorEffectNonRotation
|
||||
// DO NOT USE - no browser support yet!
|
||||
VectorEffectFixedPosition
|
||||
)
|
||||
|
||||
func (VectorEffect) PrintKey() string {
|
||||
return "vector-effect"
|
||||
}
|
||||
|
||||
func (a VectorEffect) PrintValue() (string, bool) {
|
||||
switch a {
|
||||
case VectorEffectNonScalingStroke:
|
||||
return `"non-scaling-stroke"`, true
|
||||
case VectorEffectNonScalingSize:
|
||||
return `"non-scaling-size"`, true
|
||||
case VectorEffectNonRotation:
|
||||
return `"non-rotation"`, true
|
||||
case VectorEffectFixedPosition:
|
||||
return `"fixed-position"`, true
|
||||
default:
|
||||
return `"none"`, true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
SVGVersion struct {
|
||||
Major int
|
||||
Minor int
|
||||
}
|
||||
)
|
||||
|
||||
func (v SVGVersion) PrintKey() string {
|
||||
return "version"
|
||||
}
|
||||
|
||||
func (v SVGVersion) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`"%d.%d"`, v.Major, v.Minor), true
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
ViewBox struct {
|
||||
X, Y float64
|
||||
Width, Height float64
|
||||
}
|
||||
)
|
||||
|
||||
func (b ViewBox) PrintKey() string {
|
||||
return "viewBox"
|
||||
}
|
||||
|
||||
func (b ViewBox) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf(`"%v %v %v %v"`, b.X, b.Y, b.Width, b.Height), true
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// VoidElement is like Element but for void elements
|
||||
VoidElement[T Tag, A Attribute] struct {
|
||||
Attributes []A
|
||||
}
|
||||
)
|
||||
|
||||
func (e *VoidElement[T, A]) Attr(as ...A) *VoidElement[T, A] {
|
||||
e.Attributes = append(e.Attributes, as...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e VoidElement[T, A]) GetMarkup() string {
|
||||
attrs := make([]string, len(e.Attributes))
|
||||
for i, a := range e.Attributes {
|
||||
attrs[i] = PrintAttribute(a)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
`<%s %s/>`,
|
||||
e.PrintTag(),
|
||||
strings.Join(attrs, " "),
|
||||
)
|
||||
}
|
||||
|
||||
func (e VoidElement[T, A]) GetMarkupReader() io.Reader {
|
||||
return bytes.NewReader([]byte(e.GetMarkup()))
|
||||
}
|
||||
|
||||
func (e VoidElement[T, _]) PrintTag() string {
|
||||
var t T
|
||||
return t.PrintTag()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Width struct {
|
||||
*LengthAttr[WidthTag]
|
||||
}
|
||||
WidthP struct {
|
||||
PercentageAttr[WidthTag]
|
||||
}
|
||||
|
||||
WidthTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsWidth() Width {
|
||||
return Width{LengthAttr: (*LengthAttr[WidthTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsWidth() WidthP {
|
||||
return WidthP{PercentageAttr: (PercentageAttr[WidthTag])(p)}
|
||||
}
|
||||
|
||||
func (WidthTag) PrintTag() string {
|
||||
return "width"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
X struct {
|
||||
*LengthAttr[XTag]
|
||||
}
|
||||
XP struct {
|
||||
PercentageAttr[XTag]
|
||||
}
|
||||
|
||||
XTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsX() X {
|
||||
return X{LengthAttr: (*LengthAttr[XTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsX() XP {
|
||||
return XP{PercentageAttr: (PercentageAttr[XTag])(p)}
|
||||
}
|
||||
|
||||
func (XTag) PrintTag() string {
|
||||
return "x"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
X1 struct {
|
||||
*LengthAttr[X1Tag]
|
||||
}
|
||||
X1P struct {
|
||||
PercentageAttr[X1Tag]
|
||||
}
|
||||
|
||||
X1Tag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsX1() X1 {
|
||||
return X1{LengthAttr: (*LengthAttr[X1Tag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsX1() X1P {
|
||||
return X1P{PercentageAttr: (PercentageAttr[X1Tag])(p)}
|
||||
}
|
||||
|
||||
func (X1Tag) PrintTag() string {
|
||||
return "x1"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
X2 struct {
|
||||
*LengthAttr[X2Tag]
|
||||
}
|
||||
X2P struct {
|
||||
PercentageAttr[X2Tag]
|
||||
}
|
||||
|
||||
X2Tag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsX2() X2 {
|
||||
return X2{LengthAttr: (*LengthAttr[X2Tag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsX2() X2P {
|
||||
return X2P{PercentageAttr: (PercentageAttr[X2Tag])(p)}
|
||||
}
|
||||
|
||||
func (X2Tag) PrintTag() string {
|
||||
return "x2"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package svg
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
XMLNSW32000SVG XMLNS = "http://www.w3.org/2000/svg"
|
||||
)
|
||||
|
||||
type (
|
||||
XMLNS string
|
||||
)
|
||||
|
||||
func (v XMLNS) PrintKey() string {
|
||||
return "xmlns"
|
||||
}
|
||||
|
||||
func (v XMLNS) PrintValue() (string, bool) {
|
||||
return fmt.Sprintf("%q", string(v)), true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Y struct {
|
||||
*LengthAttr[YTag]
|
||||
}
|
||||
YP struct {
|
||||
PercentageAttr[YTag]
|
||||
}
|
||||
|
||||
YTag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsY() Y {
|
||||
return Y{LengthAttr: (*LengthAttr[YTag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsY() YP {
|
||||
return YP{PercentageAttr: (PercentageAttr[YTag])(p)}
|
||||
}
|
||||
|
||||
func (YTag) PrintTag() string {
|
||||
return "y"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Y1 struct {
|
||||
*LengthAttr[Y1Tag]
|
||||
}
|
||||
Y1P struct {
|
||||
PercentageAttr[Y1Tag]
|
||||
}
|
||||
|
||||
Y1Tag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsY1() Y1 {
|
||||
return Y1{LengthAttr: (*LengthAttr[Y1Tag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsY1() Y1P {
|
||||
return Y1P{PercentageAttr: (PercentageAttr[Y1Tag])(p)}
|
||||
}
|
||||
|
||||
func (Y1Tag) PrintTag() string {
|
||||
return "y1"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package svg
|
||||
|
||||
type (
|
||||
Y2 struct {
|
||||
*LengthAttr[Y2Tag]
|
||||
}
|
||||
Y2P struct {
|
||||
PercentageAttr[Y2Tag]
|
||||
}
|
||||
|
||||
Y2Tag struct{}
|
||||
)
|
||||
|
||||
func (l *Length) AsY2() Y2 {
|
||||
return Y2{LengthAttr: (*LengthAttr[Y2Tag])(l)}
|
||||
}
|
||||
|
||||
func (p Percentage) AsY2() Y2P {
|
||||
return Y2P{PercentageAttr: (PercentageAttr[Y2Tag])(p)}
|
||||
}
|
||||
|
||||
func (Y2Tag) PrintTag() string {
|
||||
return "y2"
|
||||
}
|
||||
Reference in New Issue
Block a user