d2/d2themes/element.go
2024-10-09 15:14:47 -06:00

256 lines
5.9 KiB
Go

package d2themes
import (
"fmt"
"math"
"oss.terrastruct.com/d2/lib/color"
)
// ThemableElement is a helper class for creating new XML elements.
// This should be preferred over formatting and must be used
// whenever Fill, Stroke, BackgroundColor or Color contains a color from a theme.
// i.e. N[1-7] | B[1-6] | AA[245] | AB[45]
type ThemableElement struct {
tag string
X float64
X1 float64
X2 float64
Y float64
Y1 float64
Y2 float64
Width float64
Height float64
R float64
Rx float64
Ry float64
Cx float64
Cy float64
D string
Mask string
Points string
Transform string
Href string
Xmlns string
Fill string
Stroke string
StrokeDashArray string
BackgroundColor string
Color string
ClassName string
Style string
Attributes string
Content string
ClipPath string
FillPattern string
inlineTheme *Theme
}
func NewThemableElement(tag string, inlineTheme *Theme) *ThemableElement {
xmlns := ""
if tag == "div" {
xmlns = "http://www.w3.org/1999/xhtml"
}
return &ThemableElement{
tag,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
math.MaxFloat64,
"",
"",
"",
"",
"",
xmlns,
color.Empty,
color.Empty,
color.Empty,
color.Empty,
"",
"",
"",
"",
"",
"",
"",
inlineTheme,
}
}
func (el *ThemableElement) Copy() *ThemableElement {
tmp := *el
return &tmp
}
func (el *ThemableElement) SetTranslate(x, y float64) {
el.Transform = fmt.Sprintf("translate(%f %f)", x, y)
}
func (el *ThemableElement) SetMaskUrl(url string) {
el.Mask = fmt.Sprintf("url(#%s)", url)
}
func (el *ThemableElement) Render() string {
out := "<" + el.tag
// href has to be at the top for the img bundler to detect <image> tags correctly
if len(el.Href) > 0 {
out += fmt.Sprintf(` href="%s"`, el.Href)
}
if el.X != math.MaxFloat64 {
out += fmt.Sprintf(` x="%f"`, el.X)
}
if el.X1 != math.MaxFloat64 {
out += fmt.Sprintf(` x1="%f"`, el.X1)
}
if el.X2 != math.MaxFloat64 {
out += fmt.Sprintf(` x2="%f"`, el.X2)
}
if el.Y != math.MaxFloat64 {
out += fmt.Sprintf(` y="%f"`, el.Y)
}
if el.Y1 != math.MaxFloat64 {
out += fmt.Sprintf(` y1="%f"`, el.Y1)
}
if el.Y2 != math.MaxFloat64 {
out += fmt.Sprintf(` y2="%f"`, el.Y2)
}
if el.Width != math.MaxFloat64 {
out += fmt.Sprintf(` width="%f"`, el.Width)
}
if el.Height != math.MaxFloat64 {
out += fmt.Sprintf(` height="%f"`, el.Height)
}
if el.R != math.MaxFloat64 {
out += fmt.Sprintf(` r="%f"`, el.R)
}
if el.Rx != math.MaxFloat64 {
out += fmt.Sprintf(` rx="%f"`, calculateAxisRadius(el.Rx, el.Width, el.Height))
}
if el.Ry != math.MaxFloat64 {
out += fmt.Sprintf(` ry="%f"`, calculateAxisRadius(el.Ry, el.Width, el.Height))
}
if el.Cx != math.MaxFloat64 {
out += fmt.Sprintf(` cx="%f"`, el.Cx)
}
if el.Cy != math.MaxFloat64 {
out += fmt.Sprintf(` cy="%f"`, el.Cy)
}
if el.StrokeDashArray != "" {
out += fmt.Sprintf(` stroke-dasharray="%s"`, el.StrokeDashArray)
}
if len(el.D) > 0 {
out += fmt.Sprintf(` d="%s"`, el.D)
}
if len(el.Mask) > 0 {
out += fmt.Sprintf(` mask="%s"`, el.Mask)
}
if len(el.Points) > 0 {
out += fmt.Sprintf(` points="%s"`, el.Points)
}
if len(el.Transform) > 0 {
out += fmt.Sprintf(` transform="%s"`, el.Transform)
}
if len(el.Xmlns) > 0 {
out += fmt.Sprintf(` xmlns="%s"`, el.Xmlns)
}
class := el.ClassName
style := el.Style
// Add class {property}-{theme color} if the color is from a theme, set the property otherwise
if color.IsThemeColor(el.Stroke) {
class += fmt.Sprintf(" stroke-%s", el.Stroke)
if el.inlineTheme != nil {
out += fmt.Sprintf(` stroke="%s"`, ResolveThemeColor(*el.inlineTheme, el.Stroke))
}
} else if len(el.Stroke) > 0 {
if color.IsGradient(el.Stroke) {
el.Stroke = fmt.Sprintf("url('#%s')", color.UniqueGradientID(el.Stroke))
}
out += fmt.Sprintf(` stroke="%s"`, el.Stroke)
}
if color.IsThemeColor(el.Fill) {
class += fmt.Sprintf(" fill-%s", el.Fill)
if el.inlineTheme != nil {
out += fmt.Sprintf(` fill="%s"`, ResolveThemeColor(*el.inlineTheme, el.Fill))
}
} else if len(el.Fill) > 0 {
if color.IsGradient(el.Fill) {
el.Fill = fmt.Sprintf("url('#%s')", color.UniqueGradientID(el.Fill))
}
out += fmt.Sprintf(` fill="%s"`, el.Fill)
}
if color.IsThemeColor(el.BackgroundColor) {
class += fmt.Sprintf(" background-color-%s", el.BackgroundColor)
if el.inlineTheme != nil {
out += fmt.Sprintf(` background-color="%s"`, ResolveThemeColor(*el.inlineTheme, el.BackgroundColor))
}
} else if len(el.BackgroundColor) > 0 {
out += fmt.Sprintf(` background-color="%s"`, el.BackgroundColor)
}
if color.IsThemeColor(el.Color) {
class += fmt.Sprintf(" color-%s", el.Color)
if el.inlineTheme != nil {
out += fmt.Sprintf(` color="%s"`, ResolveThemeColor(*el.inlineTheme, el.Color))
}
} else if len(el.Color) > 0 {
out += fmt.Sprintf(` color="%s"`, el.Color)
}
if len(class) > 0 {
out += fmt.Sprintf(` class="%s"`, class)
}
if len(style) > 0 {
out += fmt.Sprintf(` style="%s"`, style)
}
if len(el.Attributes) > 0 {
out += fmt.Sprintf(` %s`, el.Attributes)
}
if len(el.ClipPath) > 0 {
out += fmt.Sprintf(` clip-path="url(#%s)"`, el.ClipPath)
}
if len(el.Content) > 0 {
return fmt.Sprintf("%s>%s</%s>", out, el.Content, el.tag)
}
out += " />"
if el.FillPattern != "" && el.FillPattern != "none" {
patternEl := el.Copy()
patternEl.Fill = ""
patternEl.Stroke = ""
patternEl.BackgroundColor = ""
patternEl.Color = ""
patternEl.ClassName = fmt.Sprintf("%s-overlay", el.FillPattern)
patternEl.FillPattern = ""
out += patternEl.Render()
}
return out
}
func calculateAxisRadius(borderRadius, width, height float64) float64 {
minimumSideSize := math.Min(width, height)
maximumBorderRadiusValue := minimumSideSize / 2.0
return math.Min(borderRadius, maximumBorderRadiusValue)
}