support for prefers-color-scheme
This commit is contained in:
parent
bcb128962e
commit
a81ab2d73e
13 changed files with 1306 additions and 530 deletions
|
|
@ -7,14 +7,11 @@ import (
|
|||
"oss.terrastruct.com/d2/d2graph"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/d2themes"
|
||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/util-go/go2"
|
||||
)
|
||||
|
||||
func Export(ctx context.Context, g *d2graph.Graph, themeID int64, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) {
|
||||
theme := d2themescatalog.Find(themeID)
|
||||
|
||||
func Export(ctx context.Context, g *d2graph.Graph, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) {
|
||||
diagram := d2target.NewDiagram()
|
||||
if fontFamily == nil {
|
||||
fontFamily = go2.Pointer(d2fonts.SourceSansPro)
|
||||
|
|
@ -23,27 +20,27 @@ func Export(ctx context.Context, g *d2graph.Graph, themeID int64, fontFamily *d2
|
|||
|
||||
diagram.Shapes = make([]d2target.Shape, len(g.Objects))
|
||||
for i := range g.Objects {
|
||||
diagram.Shapes[i] = toShape(g.Objects[i], &theme)
|
||||
diagram.Shapes[i] = toShape(g.Objects[i])
|
||||
}
|
||||
|
||||
diagram.Connections = make([]d2target.Connection, len(g.Edges))
|
||||
for i := range g.Edges {
|
||||
diagram.Connections[i] = toConnection(g.Edges[i], &theme)
|
||||
diagram.Connections[i] = toConnection(g.Edges[i])
|
||||
}
|
||||
|
||||
return diagram, nil
|
||||
}
|
||||
|
||||
func applyTheme(shape *d2target.Shape, obj *d2graph.Object, theme *d2themes.Theme) {
|
||||
shape.Stroke = obj.GetStroke(theme, shape.StrokeDash)
|
||||
shape.Fill = obj.GetFill(theme)
|
||||
func applyTheme(shape *d2target.Shape, obj *d2graph.Object) {
|
||||
shape.Stroke = obj.GetStroke(shape.StrokeDash)
|
||||
shape.Fill = obj.GetFill()
|
||||
if obj.Attributes.Shape.Value == d2target.ShapeText {
|
||||
shape.Color = theme.Colors.Neutrals.N1
|
||||
shape.Color = color.N1
|
||||
}
|
||||
if obj.Attributes.Shape.Value == d2target.ShapeSQLTable || obj.Attributes.Shape.Value == d2target.ShapeClass {
|
||||
shape.PrimaryAccentColor = theme.Colors.B2
|
||||
shape.SecondaryAccentColor = theme.Colors.AA2
|
||||
shape.NeutralAccentColor = theme.Colors.Neutrals.N2
|
||||
shape.PrimaryAccentColor = color.B2
|
||||
shape.SecondaryAccentColor = color.AA2
|
||||
shape.NeutralAccentColor = color.N2
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +92,7 @@ func applyStyles(shape *d2target.Shape, obj *d2graph.Object) {
|
|||
}
|
||||
}
|
||||
|
||||
func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
|
||||
func toShape(obj *d2graph.Object) d2target.Shape {
|
||||
shape := d2target.BaseShape()
|
||||
shape.SetType(obj.Attributes.Shape.Value)
|
||||
shape.ID = obj.AbsID()
|
||||
|
|
@ -120,8 +117,8 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
|
|||
}
|
||||
|
||||
applyStyles(shape, obj)
|
||||
applyTheme(shape, obj, theme)
|
||||
shape.Color = text.GetColor(theme, shape.Italic)
|
||||
applyTheme(shape, obj)
|
||||
shape.Color = text.GetColor(shape.Italic)
|
||||
applyStyles(shape, obj)
|
||||
|
||||
switch obj.Attributes.Shape.Value {
|
||||
|
|
@ -153,7 +150,7 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
|
|||
return *shape
|
||||
}
|
||||
|
||||
func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection {
|
||||
func toConnection(edge *d2graph.Edge) d2target.Connection {
|
||||
connection := d2target.BaseConnection()
|
||||
connection.ID = edge.AbsID()
|
||||
connection.ZIndex = edge.ZIndex
|
||||
|
|
@ -202,7 +199,7 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
|
|||
if edge.Attributes.Style.StrokeDash != nil {
|
||||
connection.StrokeDash, _ = strconv.ParseFloat(edge.Attributes.Style.StrokeDash.Value, 64)
|
||||
}
|
||||
connection.Stroke = edge.GetStroke(theme, connection.StrokeDash)
|
||||
connection.Stroke = edge.GetStroke(connection.StrokeDash)
|
||||
if edge.Attributes.Style.Stroke != nil {
|
||||
connection.Stroke = edge.Attributes.Style.Stroke.Value
|
||||
}
|
||||
|
|
@ -231,7 +228,7 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
|
|||
connection.Italic, _ = strconv.ParseBool(edge.Attributes.Style.Italic.Value)
|
||||
}
|
||||
|
||||
connection.Color = text.GetColor(theme, connection.Italic)
|
||||
connection.Color = text.GetColor(connection.Italic)
|
||||
if edge.Attributes.Style.FontColor != nil {
|
||||
connection.Color = edge.Attributes.Style.FontColor.Value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/d2themes"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||
)
|
||||
|
|
@ -321,14 +321,14 @@ func (l ContainerLevel) LabelSize() int {
|
|||
return d2fonts.FONT_SIZE_M
|
||||
}
|
||||
|
||||
func (obj *Object) GetFill(theme *d2themes.Theme) string {
|
||||
func (obj *Object) GetFill() string {
|
||||
level := int(obj.Level())
|
||||
if obj.IsSequenceDiagramNote() {
|
||||
return theme.Colors.Neutrals.N7
|
||||
return color.N7
|
||||
} else if obj.IsSequenceDiagramGroup() {
|
||||
return theme.Colors.Neutrals.N5
|
||||
return color.N5
|
||||
} else if obj.Parent.IsSequenceDiagram() {
|
||||
return theme.Colors.B5
|
||||
return color.B5
|
||||
}
|
||||
|
||||
// fill for spans
|
||||
|
|
@ -336,19 +336,19 @@ func (obj *Object) GetFill(theme *d2themes.Theme) string {
|
|||
if sd != nil {
|
||||
level -= int(sd.Level())
|
||||
if level == 1 {
|
||||
return theme.Colors.B3
|
||||
return color.B3
|
||||
} else if level == 2 {
|
||||
return theme.Colors.B4
|
||||
return color.B4
|
||||
} else if level == 3 {
|
||||
return theme.Colors.B5
|
||||
return color.B5
|
||||
} else if level == 4 {
|
||||
return theme.Colors.Neutrals.N6
|
||||
return color.N6
|
||||
}
|
||||
return theme.Colors.Neutrals.N7
|
||||
return color.N7
|
||||
}
|
||||
|
||||
if obj.IsSequenceDiagram() {
|
||||
return theme.Colors.Neutrals.N7
|
||||
return color.N7
|
||||
}
|
||||
|
||||
shape := obj.Attributes.Shape.Value
|
||||
|
|
@ -356,65 +356,65 @@ func (obj *Object) GetFill(theme *d2themes.Theme) string {
|
|||
if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) {
|
||||
if level == 1 {
|
||||
if !obj.IsContainer() {
|
||||
return theme.Colors.B6
|
||||
return color.B6
|
||||
}
|
||||
return theme.Colors.B4
|
||||
return color.B4
|
||||
} else if level == 2 {
|
||||
return theme.Colors.B5
|
||||
return color.B5
|
||||
} else if level == 3 {
|
||||
return theme.Colors.B6
|
||||
return color.B6
|
||||
}
|
||||
return theme.Colors.Neutrals.N7
|
||||
return color.N7
|
||||
}
|
||||
|
||||
if strings.EqualFold(shape, d2target.ShapeCylinder) || strings.EqualFold(shape, d2target.ShapeStoredData) || strings.EqualFold(shape, d2target.ShapePackage) {
|
||||
if level == 1 {
|
||||
return theme.Colors.AA4
|
||||
return color.AA4
|
||||
}
|
||||
return theme.Colors.AA5
|
||||
return color.AA5
|
||||
}
|
||||
|
||||
if strings.EqualFold(shape, d2target.ShapeStep) || strings.EqualFold(shape, d2target.ShapePage) || strings.EqualFold(shape, d2target.ShapeDocument) {
|
||||
if level == 1 {
|
||||
return theme.Colors.AB4
|
||||
return color.AB4
|
||||
}
|
||||
return theme.Colors.AB5
|
||||
return color.AB5
|
||||
}
|
||||
|
||||
if strings.EqualFold(shape, d2target.ShapePerson) {
|
||||
return theme.Colors.B3
|
||||
return color.B3
|
||||
}
|
||||
if strings.EqualFold(shape, d2target.ShapeDiamond) {
|
||||
return theme.Colors.Neutrals.N4
|
||||
return color.N4
|
||||
}
|
||||
if strings.EqualFold(shape, d2target.ShapeCloud) || strings.EqualFold(shape, d2target.ShapeCallout) {
|
||||
return theme.Colors.Neutrals.N7
|
||||
return color.N7
|
||||
}
|
||||
if strings.EqualFold(shape, d2target.ShapeQueue) || strings.EqualFold(shape, d2target.ShapeParallelogram) || strings.EqualFold(shape, d2target.ShapeHexagon) {
|
||||
return theme.Colors.Neutrals.N5
|
||||
return color.N5
|
||||
}
|
||||
|
||||
if strings.EqualFold(shape, d2target.ShapeSQLTable) || strings.EqualFold(shape, d2target.ShapeClass) {
|
||||
return theme.Colors.Neutrals.N1
|
||||
return color.N1
|
||||
}
|
||||
|
||||
return theme.Colors.Neutrals.N7
|
||||
return color.N7
|
||||
}
|
||||
|
||||
func (obj *Object) GetStroke(theme *d2themes.Theme, dashGapSize interface{}) string {
|
||||
func (obj *Object) GetStroke(dashGapSize interface{}) string {
|
||||
shape := obj.Attributes.Shape.Value
|
||||
if strings.EqualFold(shape, d2target.ShapeCode) ||
|
||||
strings.EqualFold(shape, d2target.ShapeText) {
|
||||
return theme.Colors.Neutrals.N1
|
||||
return color.N1
|
||||
}
|
||||
if strings.EqualFold(shape, d2target.ShapeClass) ||
|
||||
strings.EqualFold(shape, d2target.ShapeSQLTable) {
|
||||
return theme.Colors.Neutrals.N7
|
||||
return color.N7
|
||||
}
|
||||
if dashGapSize != 0.0 {
|
||||
return theme.Colors.B2
|
||||
return color.B2
|
||||
}
|
||||
return theme.Colors.B1
|
||||
return color.B1
|
||||
}
|
||||
|
||||
func (obj *Object) Level() ContainerLevel {
|
||||
|
|
@ -867,11 +867,11 @@ type EdgeReference struct {
|
|||
ScopeObj *Object `json:"-"`
|
||||
}
|
||||
|
||||
func (e *Edge) GetStroke(theme *d2themes.Theme, dashGapSize interface{}) string {
|
||||
func (e *Edge) GetStroke(dashGapSize interface{}) string {
|
||||
if dashGapSize != 0.0 {
|
||||
return theme.Colors.B2
|
||||
return color.B2
|
||||
}
|
||||
return theme.Colors.B1
|
||||
return color.B1
|
||||
}
|
||||
|
||||
func (e *Edge) ArrowString() string {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ type CompileOptions struct {
|
|||
// - pre-measured (web setting)
|
||||
// TODO maybe some will want to configure code font too, but that's much lower priority
|
||||
FontFamily *d2fonts.FontFamily
|
||||
ThemeID int64
|
||||
}
|
||||
|
||||
func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) {
|
||||
|
|
@ -68,7 +67,7 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target
|
|||
}
|
||||
}
|
||||
|
||||
diagram, err := d2exporter.Export(ctx, g, opts.ThemeID, opts.FontFamily)
|
||||
diagram, err := d2exporter.Export(ctx, g, opts.FontFamily)
|
||||
return diagram, g, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@ package d2sketch
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/label"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
svg_style "oss.terrastruct.com/d2/lib/svg/style"
|
||||
"oss.terrastruct.com/util-go/go2"
|
||||
)
|
||||
|
||||
|
|
@ -63,43 +64,26 @@ func DefineFillPattern() string {
|
|||
</defs>`, fillPattern)
|
||||
}
|
||||
|
||||
func shapeStyle(shape d2target.Shape) string {
|
||||
out := ""
|
||||
|
||||
if shape.Type == d2target.ShapeSQLTable || shape.Type == d2target.ShapeClass {
|
||||
out += fmt.Sprintf(`fill:%s;`, shape.Stroke)
|
||||
out += fmt.Sprintf(`stroke:%s;`, shape.Fill)
|
||||
} else {
|
||||
out += fmt.Sprintf(`fill:%s;`, shape.Fill)
|
||||
out += fmt.Sprintf(`stroke:%s;`, shape.Stroke)
|
||||
}
|
||||
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
||||
if shape.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func Rect(r *Runner, shape d2target.Shape) (string, error) {
|
||||
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
||||
fill: "%s",
|
||||
stroke: "%s",
|
||||
fill: "#000",
|
||||
stroke: "#000",
|
||||
strokeWidth: %d,
|
||||
%s
|
||||
});`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
||||
});`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps)
|
||||
paths, err := computeRoughPaths(r, js)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output := ""
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.Transform = fmt.Sprintf("translate(%d %d)", shape.Pos.X, shape.Pos.Y)
|
||||
pathEl.Fill, pathEl.Stroke = svg_style.ShapeTheme(shape)
|
||||
pathEl.Class = "shape"
|
||||
pathEl.Style = svg_style.ShapeStyle(shape)
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
output += fmt.Sprintf(
|
||||
`<rect class="sketch-overlay" transform="translate(%d %d)" width="%d" height="%d" />`,
|
||||
|
|
@ -110,21 +94,24 @@ func Rect(r *Runner, shape d2target.Shape) (string, error) {
|
|||
|
||||
func Oval(r *Runner, shape d2target.Shape) (string, error) {
|
||||
js := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, {
|
||||
fill: "%s",
|
||||
stroke: "%s",
|
||||
fill: "#000",
|
||||
stroke: "#000",
|
||||
strokeWidth: %d,
|
||||
%s
|
||||
});`, shape.Width/2, shape.Height/2, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
||||
});`, shape.Width/2, shape.Height/2, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps)
|
||||
paths, err := computeRoughPaths(r, js)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output := ""
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.Transform = fmt.Sprintf("translate(%d %d)", shape.Pos.X, shape.Pos.Y)
|
||||
pathEl.Fill, pathEl.Stroke = svg_style.ShapeTheme(shape)
|
||||
pathEl.Class = "shape"
|
||||
pathEl.Style = svg_style.ShapeStyle(shape)
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
output += fmt.Sprintf(
|
||||
`<ellipse class="sketch-overlay" transform="translate(%d %d)" rx="%d" ry="%d" />`,
|
||||
|
|
@ -138,20 +125,22 @@ func Paths(r *Runner, shape d2target.Shape, paths []string) (string, error) {
|
|||
output := ""
|
||||
for _, path := range paths {
|
||||
js := fmt.Sprintf(`node = rc.path("%s", {
|
||||
fill: "%s",
|
||||
stroke: "%s",
|
||||
fill: "#000",
|
||||
stroke: "#000",
|
||||
strokeWidth: %d,
|
||||
%s
|
||||
});`, path, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
||||
});`, path, shape.StrokeWidth, baseRoughProps)
|
||||
sketchPaths, err := computeRoughPaths(r, js)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.Fill, pathEl.Stroke = svg_style.ShapeTheme(shape)
|
||||
pathEl.Class = "shape"
|
||||
pathEl.Style = svg_style.ShapeStyle(shape)
|
||||
for _, p := range sketchPaths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="shape" d="%s" style="%s" />`,
|
||||
p, shapeStyle(shape),
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
for _, p := range sketchPaths {
|
||||
output += fmt.Sprintf(
|
||||
|
|
@ -163,20 +152,6 @@ func Paths(r *Runner, shape d2target.Shape, paths []string) (string, error) {
|
|||
return output, nil
|
||||
}
|
||||
|
||||
func connectionStyle(connection d2target.Connection) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`stroke:%s;`, connection.Stroke)
|
||||
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||
if connection.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) {
|
||||
roughness := 1.0
|
||||
js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness)
|
||||
|
|
@ -185,11 +160,15 @@ func Connection(r *Runner, connection d2target.Connection, path, attrs string) (
|
|||
return "", err
|
||||
}
|
||||
output := ""
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.Fill = color.None
|
||||
pathEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||
pathEl.Class = "connection"
|
||||
pathEl.Style = svg_style.ConnectionStyle(connection)
|
||||
pathEl.Attributes = attrs
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="connection" fill="none" d="%s" style="%s" %s/>`,
|
||||
p, connectionStyle(connection), attrs,
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
|
@ -198,20 +177,23 @@ func Connection(r *Runner, connection d2target.Connection, path, attrs string) (
|
|||
func Table(r *Runner, shape d2target.Shape) (string, error) {
|
||||
output := ""
|
||||
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
||||
fill: "%s",
|
||||
stroke: "%s",
|
||||
fill: "#000",
|
||||
stroke: "#000",
|
||||
strokeWidth: %d,
|
||||
%s
|
||||
});`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
||||
});`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps)
|
||||
paths, err := computeRoughPaths(r, js)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.Transform = fmt.Sprintf("translate(%d %d)", shape.Pos.X, shape.Pos.Y)
|
||||
pathEl.Fill, pathEl.Stroke = svg_style.ShapeTheme(shape)
|
||||
pathEl.Class = "shape"
|
||||
pathEl.Style = svg_style.ShapeStyle(shape)
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
|
||||
box := geo.NewBox(
|
||||
|
|
@ -223,18 +205,20 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
|||
headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight)
|
||||
|
||||
js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
|
||||
fill: "%s",
|
||||
fill: "#000",
|
||||
%s
|
||||
});`, shape.Width, rowHeight, shape.Fill, baseRoughProps)
|
||||
});`, shape.Width, rowHeight, baseRoughProps)
|
||||
paths, err = computeRoughPaths(r, js)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pathEl = svg_style.NewThemableElement("path")
|
||||
pathEl.Transform = fmt.Sprintf("translate(%d %d)", shape.Pos.X, shape.Pos.Y)
|
||||
pathEl.Fill = shape.Fill
|
||||
pathEl.Class = "class_header"
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="class_header" transform="translate(%d %d)" d="%s" style="fill:%s" />`,
|
||||
shape.Pos.X, shape.Pos.Y, p, shape.Fill,
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
|
||||
if shape.Label != "" {
|
||||
|
|
@ -245,17 +229,16 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
|||
float64(shape.LabelHeight),
|
||||
)
|
||||
|
||||
output += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
||||
"text",
|
||||
tl.X,
|
||||
tl.Y+float64(shape.LabelHeight)*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s",
|
||||
"start",
|
||||
4+shape.FontSize,
|
||||
shape.Stroke,
|
||||
),
|
||||
svg.EscapeText(shape.Label),
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = tl.X
|
||||
textEl.Y = tl.Y + float64(shape.LabelHeight)*3/4
|
||||
textEl.Fill = shape.Stroke
|
||||
textEl.Class = "text"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
|
||||
"start", 4+shape.FontSize,
|
||||
)
|
||||
textEl.Content = svg.EscapeText(shape.Label)
|
||||
output += textEl.Render()
|
||||
}
|
||||
|
||||
var longestNameWidth int
|
||||
|
|
@ -279,26 +262,26 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
|||
float64(shape.FontSize),
|
||||
)
|
||||
|
||||
output += strings.Join([]string{
|
||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
||||
nameTL.X,
|
||||
nameTL.Y+float64(shape.FontSize)*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", float64(shape.FontSize), shape.PrimaryAccentColor),
|
||||
svg.EscapeText(f.Name.Label),
|
||||
),
|
||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
||||
nameTL.X+float64(longestNameWidth)+2*d2target.NamePadding,
|
||||
nameTL.Y+float64(shape.FontSize)*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", float64(shape.FontSize), shape.NeutralAccentColor),
|
||||
svg.EscapeText(f.Type.Label),
|
||||
),
|
||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
||||
constraintTR.X,
|
||||
constraintTR.Y+float64(shape.FontSize)*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;letter-spacing:2px;", "end", float64(shape.FontSize), shape.SecondaryAccentColor),
|
||||
f.ConstraintAbbr(),
|
||||
),
|
||||
}, "\n")
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = nameTL.X
|
||||
textEl.Y = nameTL.Y + float64(shape.FontSize)*3/4
|
||||
textEl.Fill = shape.PrimaryAccentColor
|
||||
textEl.Class = "text"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", float64(shape.FontSize))
|
||||
textEl.Content = svg.EscapeText(f.Name.Label)
|
||||
output += textEl.Render()
|
||||
|
||||
textEl.X = nameTL.X + float64(longestNameWidth) + 2*d2target.NamePadding
|
||||
textEl.Fill = shape.NeutralAccentColor
|
||||
textEl.Content = svg.EscapeText(f.Type.Label)
|
||||
output += textEl.Render()
|
||||
|
||||
textEl.X = constraintTR.X
|
||||
textEl.Y = constraintTR.Y + float64(shape.FontSize)*3/4
|
||||
textEl.Fill = shape.SecondaryAccentColor
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx;letter-spacing:2px", "end", float64(shape.FontSize))
|
||||
textEl.Content = f.ConstraintAbbr()
|
||||
output += textEl.Render()
|
||||
|
||||
rowBox.TopLeft.Y += rowHeight
|
||||
|
||||
|
|
@ -309,11 +292,11 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.Fill = shape.Fill
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path d="%s" style="fill:%s" />`,
|
||||
p, shape.Fill,
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
}
|
||||
output += fmt.Sprintf(
|
||||
|
|
@ -326,20 +309,22 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
|||
func Class(r *Runner, shape d2target.Shape) (string, error) {
|
||||
output := ""
|
||||
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
||||
fill: "%s",
|
||||
stroke: "%s",
|
||||
fill: "#000",
|
||||
stroke: "#000",
|
||||
strokeWidth: %d,
|
||||
%s
|
||||
});`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
||||
});`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps)
|
||||
paths, err := computeRoughPaths(r, js)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.Transform = fmt.Sprintf("translate(%d %d)", shape.Pos.X, shape.Pos.Y)
|
||||
pathEl.Fill, pathEl.Stroke = svg_style.ShapeTheme(shape)
|
||||
pathEl.Class = "shape"
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
|
||||
box := geo.NewBox(
|
||||
|
|
@ -352,18 +337,20 @@ func Class(r *Runner, shape d2target.Shape) (string, error) {
|
|||
headerBox := geo.NewBox(box.TopLeft, box.Width, 2*rowHeight)
|
||||
|
||||
js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
|
||||
fill: "%s",
|
||||
fill: "#000",
|
||||
%s
|
||||
});`, shape.Width, headerBox.Height, shape.Fill, baseRoughProps)
|
||||
});`, shape.Width, headerBox.Height, baseRoughProps)
|
||||
paths, err = computeRoughPaths(r, js)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pathEl = svg_style.NewThemableElement("path")
|
||||
pathEl.Transform = fmt.Sprintf("translate(%d %d)", shape.Pos.X, shape.Pos.Y)
|
||||
pathEl.Fill = shape.Fill
|
||||
pathEl.Class = "class_header"
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="class_header" transform="translate(%d %d)" d="%s" style="fill:%s" />`,
|
||||
shape.Pos.X, shape.Pos.Y, p, shape.Fill,
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
|
||||
output += fmt.Sprintf(
|
||||
|
|
@ -379,17 +366,17 @@ func Class(r *Runner, shape d2target.Shape) (string, error) {
|
|||
float64(shape.LabelHeight),
|
||||
)
|
||||
|
||||
output += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
||||
"text-mono",
|
||||
tl.X+float64(shape.LabelWidth)/2,
|
||||
tl.Y+float64(shape.LabelHeight)*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s",
|
||||
"middle",
|
||||
4+shape.FontSize,
|
||||
shape.Stroke,
|
||||
),
|
||||
svg.EscapeText(shape.Label),
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = tl.X + float64(shape.LabelWidth)/2
|
||||
textEl.Y = tl.Y + float64(shape.LabelHeight)*3/4
|
||||
textEl.Fill = shape.Stroke
|
||||
textEl.Class = "text-mono"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
|
||||
"middle",
|
||||
4+shape.FontSize,
|
||||
)
|
||||
textEl.Content = svg.EscapeText(shape.Label)
|
||||
output += textEl.Render()
|
||||
}
|
||||
|
||||
rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight)
|
||||
|
|
@ -406,11 +393,12 @@ func Class(r *Runner, shape d2target.Shape) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pathEl = svg_style.NewThemableElement("path")
|
||||
pathEl.Fill = shape.Fill
|
||||
pathEl.Class = "class_header"
|
||||
for _, p := range paths {
|
||||
output += fmt.Sprintf(
|
||||
`<path class="class_header" d="%s" style="fill:%s" />`,
|
||||
p, shape.Fill,
|
||||
)
|
||||
pathEl.D = p
|
||||
output += pathEl.Render()
|
||||
}
|
||||
|
||||
for _, m := range shape.Methods {
|
||||
|
|
@ -436,28 +424,27 @@ func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText str
|
|||
fontSize,
|
||||
)
|
||||
|
||||
output += strings.Join([]string{
|
||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
||||
prefixTL.X,
|
||||
prefixTL.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor),
|
||||
prefix,
|
||||
),
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = prefixTL.X
|
||||
textEl.Y = prefixTL.Y + fontSize*3/4
|
||||
textEl.Fill = shape.PrimaryAccentColor
|
||||
textEl.Class = "text-mono"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", fontSize)
|
||||
textEl.Content = prefix
|
||||
output += textEl.Render()
|
||||
|
||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
||||
prefixTL.X+d2target.PrefixWidth,
|
||||
prefixTL.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.Fill),
|
||||
svg.EscapeText(nameText),
|
||||
),
|
||||
textEl.X = prefixTL.X + d2target.PrefixWidth
|
||||
textEl.Fill = shape.Fill
|
||||
textEl.Content = svg.EscapeText(nameText)
|
||||
output += textEl.Render()
|
||||
|
||||
textEl.X = typeTR.X
|
||||
textEl.Y = typeTR.Y + fontSize*3/4
|
||||
textEl.Fill = shape.SecondaryAccentColor
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", fontSize)
|
||||
textEl.Content = svg.EscapeText(typeText)
|
||||
output += textEl.Render()
|
||||
|
||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
||||
typeTR.X,
|
||||
typeTR.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;", "end", fontSize, shape.SecondaryAccentColor),
|
||||
svg.EscapeText(typeText),
|
||||
),
|
||||
}, "\n")
|
||||
return output
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -313,7 +313,6 @@ func run(t *testing.T, tc testCase) {
|
|||
|
||||
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
|
||||
Ruler: ruler,
|
||||
ThemeID: 0,
|
||||
Layout: d2dagrelayout.DefaultLayout,
|
||||
FontFamily: go2.Pointer(d2fonts.HandDrawn),
|
||||
})
|
||||
|
|
@ -325,8 +324,9 @@ func run(t *testing.T, tc testCase) {
|
|||
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
|
||||
|
||||
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||
Pad: d2svg.DEFAULT_PADDING,
|
||||
Sketch: true,
|
||||
Pad: d2svg.DEFAULT_PADDING,
|
||||
Sketch: true,
|
||||
ThemeID: 0,
|
||||
})
|
||||
assert.Success(t, err)
|
||||
err = os.MkdirAll(dataPath, 0755)
|
||||
|
|
|
|||
|
|
@ -3,17 +3,21 @@ package d2svg
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/label"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
svg_style "oss.terrastruct.com/d2/lib/svg/style"
|
||||
)
|
||||
|
||||
func classHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string {
|
||||
str := fmt.Sprintf(`<rect class="class_header" x="%f" y="%f" width="%f" height="%f" fill="%s" />`,
|
||||
box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height, shape.Fill)
|
||||
rectEl := svg_style.NewThemableElement("rect")
|
||||
rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y
|
||||
rectEl.Width, rectEl.Height = box.Width, box.Height
|
||||
rectEl.Fill = shape.Fill
|
||||
rectEl.Class = "class_header"
|
||||
str := rectEl.Render()
|
||||
|
||||
if text != "" {
|
||||
tl := label.InsideMiddleCenter.GetPointOnBox(
|
||||
|
|
@ -23,17 +27,16 @@ func classHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, tex
|
|||
textHeight,
|
||||
)
|
||||
|
||||
str += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
||||
"text-mono",
|
||||
tl.X+textWidth/2,
|
||||
tl.Y+textHeight*3/4,
|
||||
fmt.Sprintf(`text-anchor:%s;font-size:%vpx;fill:%s`,
|
||||
"middle",
|
||||
4+fontSize,
|
||||
shape.Stroke,
|
||||
),
|
||||
svg.EscapeText(text),
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = tl.X + textWidth/2
|
||||
textEl.Y = tl.Y + textHeight*3/4
|
||||
textEl.Fill = shape.Stroke
|
||||
textEl.Class = "text-mono"
|
||||
textEl.Style = fmt.Sprintf(`text-anchor:%s;font-size:%vpx;`,
|
||||
"middle", 4+fontSize,
|
||||
)
|
||||
textEl.Content = svg.EscapeText(text)
|
||||
str += textEl.Render()
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
|
@ -54,33 +57,39 @@ func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText str
|
|||
fontSize,
|
||||
)
|
||||
|
||||
return strings.Join([]string{
|
||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
||||
prefixTL.X,
|
||||
prefixTL.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor),
|
||||
prefix,
|
||||
),
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = prefixTL.X
|
||||
textEl.Y = prefixTL.Y + fontSize*3/4
|
||||
textEl.Fill = shape.PrimaryAccentColor
|
||||
textEl.Class = "text-mono"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", fontSize)
|
||||
textEl.Content = prefix
|
||||
out := textEl.Render()
|
||||
|
||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
||||
prefixTL.X+d2target.PrefixWidth,
|
||||
prefixTL.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.Fill),
|
||||
svg.EscapeText(nameText),
|
||||
),
|
||||
textEl.X = prefixTL.X + d2target.PrefixWidth
|
||||
textEl.Fill = shape.Fill
|
||||
textEl.Content = svg.EscapeText(nameText)
|
||||
out += textEl.Render()
|
||||
|
||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
||||
typeTR.X,
|
||||
typeTR.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "end", fontSize, shape.SecondaryAccentColor),
|
||||
svg.EscapeText(typeText),
|
||||
),
|
||||
}, "\n")
|
||||
textEl.X = typeTR.X
|
||||
textEl.Y = typeTR.Y + fontSize*3/4
|
||||
textEl.Fill = shape.SecondaryAccentColor
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", fontSize)
|
||||
textEl.Content = svg.EscapeText(typeText)
|
||||
out += textEl.Render()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func drawClass(writer io.Writer, targetShape d2target.Shape) {
|
||||
fmt.Fprintf(writer, `<rect class="shape" x="%d" y="%d" width="%d" height="%d" style="%s"/>`,
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape))
|
||||
el := svg_style.NewThemableElement("rect")
|
||||
el.X = float64(targetShape.Pos.X)
|
||||
el.Y = float64(targetShape.Pos.Y)
|
||||
el.Width = float64(targetShape.Width)
|
||||
el.Height = float64(targetShape.Height)
|
||||
el.Fill, el.Stroke = svg_style.ShapeTheme(targetShape)
|
||||
el.Style = svg_style.ShapeStyle(targetShape)
|
||||
fmt.Fprint(writer, el.Render())
|
||||
|
||||
box := geo.NewBox(
|
||||
geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)),
|
||||
|
|
@ -103,10 +112,12 @@ func drawClass(writer io.Writer, targetShape d2target.Shape) {
|
|||
rowBox.TopLeft.Y += rowHeight
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, `<line x1="%f" y1="%f" x2="%f" y2="%f" style="%s" />`,
|
||||
rowBox.TopLeft.X, rowBox.TopLeft.Y,
|
||||
rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y,
|
||||
fmt.Sprintf("stroke-width:1;stroke:%v", targetShape.Fill))
|
||||
lineEl := svg_style.NewThemableElement("line")
|
||||
lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y
|
||||
lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y
|
||||
lineEl.Stroke = targetShape.Fill
|
||||
lineEl.Style = "stroke-width:1"
|
||||
fmt.Fprint(writer, lineEl.Render())
|
||||
|
||||
for _, m := range targetShape.Methods {
|
||||
fmt.Fprint(writer,
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ import (
|
|||
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2sketch"
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/label"
|
||||
"oss.terrastruct.com/d2/lib/shape"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
svg_style "oss.terrastruct.com/d2/lib/svg/style"
|
||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||
)
|
||||
|
||||
|
|
@ -68,21 +68,12 @@ type RenderOpts struct {
|
|||
ThemeID int64
|
||||
}
|
||||
|
||||
func setViewbox(writer io.Writer, diagram *d2target.Diagram, pad int, bgColor string, fgColor string) (width int, height int) {
|
||||
func dimensions(writer io.Writer, diagram *d2target.Diagram, pad int) (width, height int, topLeft, bottomRight d2target.Point) {
|
||||
tl, br := diagram.BoundingBox()
|
||||
w := br.X - tl.X + pad*2
|
||||
h := br.Y - tl.Y + pad*2
|
||||
// TODO minify
|
||||
|
||||
// TODO background stuff. e.g. dotted, grid, colors
|
||||
fmt.Fprintf(writer, `<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg
|
||||
id="d2-svg"
|
||||
style="background: %s; color: %s;"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="%d" height="%d" viewBox="%d %d %d %d">`, bgColor, fgColor, w, h, tl.X-pad, tl.Y-pad, w, h)
|
||||
|
||||
return w, h
|
||||
return w, h, tl, br
|
||||
}
|
||||
|
||||
func arrowheadMarkerID(isTarget bool, connection d2target.Connection) string {
|
||||
|
|
@ -94,7 +85,7 @@ func arrowheadMarkerID(isTarget bool, connection d2target.Connection) string {
|
|||
}
|
||||
|
||||
return fmt.Sprintf("mk-%s", hash(fmt.Sprintf("%s,%t,%d,%s",
|
||||
arrowhead, isTarget, connection.StrokeWidth, connection.Stroke,
|
||||
arrowhead, isTarget, connection.StrokeWidth, svg_style.ConnectionTheme(connection),
|
||||
)))
|
||||
}
|
||||
|
||||
|
|
@ -137,119 +128,136 @@ func arrowheadMarker(isTarget bool, id string, bgColor string, connection d2targ
|
|||
var path string
|
||||
switch arrowhead {
|
||||
case d2target.ArrowArrowhead:
|
||||
attrs := fmt.Sprintf(`class="connection" fill="%s" stroke-width="%d"`, connection.Stroke, connection.StrokeWidth)
|
||||
polygonEl := svg_style.NewThemableElement("polygon")
|
||||
polygonEl.Fill = svg_style.ConnectionTheme(connection)
|
||||
polygonEl.Attributes = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
|
||||
if isTarget {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||
0., 0.,
|
||||
width, height/2,
|
||||
0., height,
|
||||
width/4, height/2,
|
||||
)
|
||||
} else {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||
0., height/2,
|
||||
width, 0.,
|
||||
width*3/4, height/2,
|
||||
width, height,
|
||||
)
|
||||
}
|
||||
path = polygonEl.Render()
|
||||
case d2target.TriangleArrowhead:
|
||||
attrs := fmt.Sprintf(`class="connection" fill="%s" stroke-width="%d"`, connection.Stroke, connection.StrokeWidth)
|
||||
polygonEl := svg_style.NewThemableElement("polygon")
|
||||
polygonEl.Fill = svg_style.ConnectionTheme(connection)
|
||||
polygonEl.Attributes = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
|
||||
if isTarget {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||
0., 0.,
|
||||
width, height/2.0,
|
||||
0., height,
|
||||
)
|
||||
} else {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||
width, 0.,
|
||||
0., height/2.0,
|
||||
width, height,
|
||||
)
|
||||
}
|
||||
path = polygonEl.Render()
|
||||
case d2target.LineArrowhead:
|
||||
attrs := fmt.Sprintf(`class="connection" fill="none" stroke="%s" stroke-width="%d"`, connection.Stroke, connection.StrokeWidth)
|
||||
polylineEl := svg_style.NewThemableElement("polyline")
|
||||
polylineEl.Fill = color.None
|
||||
polylineEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||
polylineEl.Attributes = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
|
||||
if isTarget {
|
||||
path = fmt.Sprintf(`<polyline %s points="%f,%f %f,%f %f,%f"/>`,
|
||||
attrs,
|
||||
polylineEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||
strokeWidth/2, strokeWidth/2,
|
||||
width-strokeWidth/2, height/2,
|
||||
strokeWidth/2, height-strokeWidth/2,
|
||||
)
|
||||
} else {
|
||||
path = fmt.Sprintf(`<polyline %s points="%f,%f %f,%f %f,%f"/>`,
|
||||
attrs,
|
||||
polylineEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||
width-strokeWidth/2, strokeWidth/2,
|
||||
strokeWidth/2, height/2,
|
||||
width-strokeWidth/2, height-strokeWidth/2,
|
||||
)
|
||||
}
|
||||
path = polylineEl.Render()
|
||||
case d2target.FilledDiamondArrowhead:
|
||||
attrs := fmt.Sprintf(`class="connection" fill="%s" stroke-width="%d"`, connection.Stroke, connection.StrokeWidth)
|
||||
polygonEl := svg_style.NewThemableElement("polygon")
|
||||
polygonEl.Fill = svg_style.ConnectionTheme(connection)
|
||||
polygonEl.Attributes = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
|
||||
if isTarget {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||
0., height/2.0,
|
||||
width/2.0, 0.,
|
||||
width, height/2.0,
|
||||
width/2.0, height,
|
||||
)
|
||||
} else {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||
0., height/2.0,
|
||||
width/2.0, 0.,
|
||||
width, height/2.0,
|
||||
width/2.0, height,
|
||||
)
|
||||
}
|
||||
path = polygonEl.Render()
|
||||
case d2target.DiamondArrowhead:
|
||||
attrs := fmt.Sprintf(`class="connection" fill="%s" stroke="%s" stroke-width="%d"`, bgColor, connection.Stroke, connection.StrokeWidth)
|
||||
polygonEl := svg_style.NewThemableElement("polygon")
|
||||
polygonEl.Fill = bgColor
|
||||
polygonEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||
polygonEl.Attributes = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
|
||||
if isTarget {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||
0., height/2.0,
|
||||
width/2, height/8,
|
||||
width, height/2.0,
|
||||
width/2.0, height*0.9,
|
||||
)
|
||||
} else {
|
||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
||||
attrs,
|
||||
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||
width/8, height/2.0,
|
||||
width*0.6, height/8,
|
||||
width*1.1, height/2.0,
|
||||
width*0.6, height*7/8,
|
||||
)
|
||||
}
|
||||
path = polygonEl.Render()
|
||||
case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
|
||||
attrs := fmt.Sprintf(`class="connection" stroke="%s" stroke-width="%d" fill="%s"`, connection.Stroke, connection.StrokeWidth, bgColor)
|
||||
offset := 4.0 + float64(connection.StrokeWidth*2)
|
||||
var modifier string
|
||||
|
||||
var modifierEl *svg_style.ThemableElement
|
||||
if arrowhead == d2target.CfOneRequired || arrowhead == d2target.CfManyRequired {
|
||||
modifier = fmt.Sprintf(`<path %s d="M%f,%f %f,%f"/>`,
|
||||
attrs,
|
||||
modifierEl := svg_style.NewThemableElement("path")
|
||||
modifierEl.D = fmt.Sprintf("M%f,%f %f,%f",
|
||||
offset, 0.,
|
||||
offset, height,
|
||||
)
|
||||
modifierEl.Fill = bgColor
|
||||
modifierEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||
modifierEl.Class = "connection"
|
||||
modifierEl.Style = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
} else {
|
||||
modifier = fmt.Sprintf(`<circle %s cx="%f" cy="%f" r="%f"/>`,
|
||||
attrs,
|
||||
offset/2.0+1.0, height/2.0,
|
||||
offset/2.0,
|
||||
)
|
||||
}
|
||||
if !isTarget {
|
||||
attrs = fmt.Sprintf(`%s transform="scale(-1) translate(-%f, -%f)"`, attrs, width, height)
|
||||
modifierEl := svg_style.NewThemableElement("circle")
|
||||
modifierEl.Cx = offset/2.0 + 1.0
|
||||
modifierEl.Cy = height / 2.0
|
||||
modifierEl.R = offset / 2.0
|
||||
modifierEl.Fill = bgColor
|
||||
modifierEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||
modifierEl.Class = "connection"
|
||||
modifierEl.Style = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
}
|
||||
|
||||
childPathEl := svg_style.NewThemableElement("path")
|
||||
if arrowhead == d2target.CfMany || arrowhead == d2target.CfManyRequired {
|
||||
path = fmt.Sprintf(`<g %s>%s<path d="M%f,%f %f,%f M%f,%f %f,%f M%f,%f %f,%f"/></g>`,
|
||||
attrs, modifier,
|
||||
childPathEl.D = fmt.Sprintf("M%f,%f %f,%f M%f,%f %f,%f M%f,%f %f,%f",
|
||||
width-3.0, height/2.0,
|
||||
width+offset, height/2.0,
|
||||
offset+2.0, height/2.0,
|
||||
|
|
@ -258,14 +266,26 @@ func arrowheadMarker(isTarget bool, id string, bgColor string, connection d2targ
|
|||
width+offset, height,
|
||||
)
|
||||
} else {
|
||||
path = fmt.Sprintf(`<g %s>%s<path d="M%f,%f %f,%f M%f,%f %f,%f"/></g>`,
|
||||
attrs, modifier,
|
||||
childPathEl.D = fmt.Sprintf("M%f,%f %f,%f M%f,%f %f,%f",
|
||||
width-3.0, height/2.0,
|
||||
width+offset, height/2.0,
|
||||
offset*1.8, 0.,
|
||||
offset*1.8, height,
|
||||
)
|
||||
}
|
||||
|
||||
gEl := svg_style.NewThemableElement("g")
|
||||
gEl.Fill = bgColor
|
||||
gEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||
gEl.Class = "connection"
|
||||
gEl.Style = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||
if !isTarget {
|
||||
gEl.Transform = fmt.Sprintf("scale(-1) translate(-%f, -%f)", width, height)
|
||||
}
|
||||
gEl.Content = fmt.Sprintf("%s%s",
|
||||
modifierEl.Render(), childPathEl.Render(),
|
||||
)
|
||||
path = gEl.Render()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
@ -462,10 +482,16 @@ func drawConnection(writer io.Writer, bgColor string, fgColor string, labelMaskI
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
fmt.Fprintf(writer, `<path d="%s" class="connection" style="fill:none;%s" %s/>`,
|
||||
path, connectionStyle(connection), attrs)
|
||||
pathEl := svg_style.NewThemableElement("path")
|
||||
pathEl.D = path
|
||||
pathEl.Fill = color.None
|
||||
pathEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||
pathEl.Class = "connection"
|
||||
pathEl.Style = svg_style.ConnectionStyle(connection)
|
||||
pathEl.Attributes = attrs
|
||||
fmt.Fprint(writer, pathEl.Render())
|
||||
}
|
||||
|
||||
if connection.Label != "" {
|
||||
|
|
@ -475,24 +501,27 @@ func drawConnection(writer io.Writer, bgColor string, fgColor string, labelMaskI
|
|||
} else if connection.Italic {
|
||||
fontClass += "-italic"
|
||||
}
|
||||
fontColor := "black"
|
||||
if connection.Color != "" {
|
||||
fontColor := color.N1
|
||||
if connection.Color != color.Empty {
|
||||
fontColor = connection.Color
|
||||
}
|
||||
|
||||
if connection.Fill != "" {
|
||||
fmt.Fprintf(writer, `<rect x="%f" y="%f" width="%d" height="%d" style="fill:%s" />`,
|
||||
labelTL.X, labelTL.Y, connection.LabelWidth, connection.LabelHeight, connection.Fill)
|
||||
if connection.Fill != color.Empty {
|
||||
rectEl := svg_style.NewThemableElement("rect")
|
||||
rectEl.X, rectEl.Y = labelTL.X, labelTL.Y
|
||||
rectEl.Width, rectEl.Height = float64(connection.LabelWidth), float64(connection.LabelHeight)
|
||||
rectEl.Fill = connection.Fill
|
||||
fmt.Fprint(writer, rectEl.Render())
|
||||
}
|
||||
textStyle := fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "middle", connection.FontSize, fontColor)
|
||||
x := labelTL.X + float64(connection.LabelWidth)/2
|
||||
y := labelTL.Y + float64(connection.FontSize)
|
||||
fmt.Fprintf(writer, `<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
||||
fontClass,
|
||||
x, y,
|
||||
textStyle,
|
||||
RenderText(connection.Label, x, float64(connection.LabelHeight)),
|
||||
)
|
||||
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = labelTL.X + float64(connection.LabelWidth)/2
|
||||
textEl.Y = labelTL.Y + float64(connection.FontSize)
|
||||
textEl.Fill = fontColor
|
||||
textEl.Class = fontClass
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "middle", connection.FontSize)
|
||||
textEl.Content = RenderText(connection.Label, textEl.X, float64(connection.LabelHeight))
|
||||
fmt.Fprint(writer, textEl.Render())
|
||||
}
|
||||
|
||||
length := geo.Route(connection.Route).Length()
|
||||
|
|
@ -521,22 +550,25 @@ func drawConnection(writer io.Writer, bgColor string, fgColor string, labelMaskI
|
|||
func renderArrowheadLabel(fgColor string, connection d2target.Connection, text string, position, width, height float64) string {
|
||||
labelTL := label.UnlockedTop.GetPointOnRoute(connection.Route, float64(connection.StrokeWidth), position, width, height)
|
||||
|
||||
textStyle := fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "middle", connection.FontSize, fgColor)
|
||||
x := labelTL.X + width/2
|
||||
y := labelTL.Y + float64(connection.FontSize)
|
||||
return fmt.Sprintf(`<text class="text-italic" x="%f" y="%f" style="%s">%s</text>`,
|
||||
x, y,
|
||||
textStyle,
|
||||
RenderText(text, x, height),
|
||||
)
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = labelTL.X + width/2
|
||||
textEl.Y = labelTL.Y + float64(connection.FontSize)
|
||||
textEl.Fill = fgColor
|
||||
textEl.Class = "text-italic"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "middle", connection.FontSize)
|
||||
textEl.Content = RenderText(text, textEl.X, height)
|
||||
return textEl.Render()
|
||||
}
|
||||
|
||||
func renderOval(tl *geo.Point, width, height float64, style string) string {
|
||||
rx := width / 2
|
||||
ry := height / 2
|
||||
cx := tl.X + rx
|
||||
cy := tl.Y + ry
|
||||
return fmt.Sprintf(`<ellipse class="shape" cx="%f" cy="%f" rx="%f" ry="%f" style="%s" />`, cx, cy, rx, ry, style)
|
||||
func renderOval(tl *geo.Point, width, height float64, fill, stroke, style string) string {
|
||||
el := svg_style.NewThemableElement("ellipse")
|
||||
el.Rx = width / 2
|
||||
el.Ry = height / 2
|
||||
el.Cx = tl.X + el.Rx
|
||||
el.Cy = tl.Y + el.Ry
|
||||
el.Class = "shape"
|
||||
el.Style = style
|
||||
return el.Render()
|
||||
}
|
||||
|
||||
func defineShadowFilter(writer io.Writer) {
|
||||
|
|
@ -583,11 +615,13 @@ func render3dRect(targetShape d2target.Shape) string {
|
|||
borderSegments = append(borderSegments,
|
||||
lineTo(d2target.Point{X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset}),
|
||||
)
|
||||
border := targetShape
|
||||
border.Fill = "none"
|
||||
borderStyle := shapeStyle(border)
|
||||
renderedBorder := fmt.Sprintf(`<path d="%s" style="%s"/>`,
|
||||
strings.Join(borderSegments, " "), borderStyle)
|
||||
border := svg_style.NewThemableElement("path")
|
||||
border.D = strings.Join(borderSegments, " ")
|
||||
_, borderStroke := svg_style.ShapeTheme(targetShape)
|
||||
border.Stroke = borderStroke
|
||||
borderStyle := svg_style.ShapeStyle(targetShape)
|
||||
border.Style = borderStyle
|
||||
renderedBorder := border.Render()
|
||||
|
||||
// create mask from border stroke, to cut away from the shape fills
|
||||
maskID := fmt.Sprintf("border-mask-%v", svg.EscapeText(targetShape.ID))
|
||||
|
|
@ -603,11 +637,16 @@ func render3dRect(targetShape d2target.Shape) string {
|
|||
}, "\n")
|
||||
|
||||
// render the main rectangle without stroke and the border mask
|
||||
mainShape := targetShape
|
||||
mainShape.Stroke = "none"
|
||||
mainRect := fmt.Sprintf(`<rect x="%d" y="%d" width="%d" height="%d" style="%s" mask="url(#%s)"/>`,
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(mainShape), maskID,
|
||||
)
|
||||
mainShape := svg_style.NewThemableElement("rect")
|
||||
mainShape.X = float64(targetShape.Pos.X)
|
||||
mainShape.Y = float64(targetShape.Pos.Y)
|
||||
mainShape.Width = float64(targetShape.Width)
|
||||
mainShape.Height = float64(targetShape.Height)
|
||||
mainShape.Mask = fmt.Sprintf("url(#%s)", maskID)
|
||||
mainShapeFill, _ := svg_style.ShapeTheme(targetShape)
|
||||
mainShape.Fill = mainShapeFill
|
||||
mainShape.Style = svg_style.ShapeStyle(targetShape)
|
||||
mainShapeRendered := mainShape.Render()
|
||||
|
||||
// render the side shapes in the darkened color without stroke and the border mask
|
||||
var sidePoints []string
|
||||
|
|
@ -623,17 +662,20 @@ func render3dRect(targetShape d2target.Shape) string {
|
|||
fmt.Sprintf("%d,%d", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
|
||||
)
|
||||
}
|
||||
darkerColor, err := color.Darken(targetShape.Fill)
|
||||
if err != nil {
|
||||
darkerColor = targetShape.Fill
|
||||
}
|
||||
sideShape := targetShape
|
||||
// TODO make darker color part of the theme?
|
||||
darkerColor := targetShape.Fill
|
||||
// darkerColor, err := color.Darken(targetShape.Fill)
|
||||
// if err != nil {
|
||||
// darkerColor = targetShape.Fill
|
||||
// }
|
||||
sideShape := svg_style.NewThemableElement("polygon")
|
||||
sideShape.Fill = darkerColor
|
||||
sideShape.Stroke = "none"
|
||||
renderedSides := fmt.Sprintf(`<polygon points="%s" style="%s" mask="url(#%s)"/>`,
|
||||
strings.Join(sidePoints, " "), shapeStyle(sideShape), maskID)
|
||||
sideShape.Points = strings.Join(sidePoints, " ")
|
||||
sideShape.Mask = fmt.Sprintf("url(#%s)", maskID)
|
||||
sideShape.Style = svg_style.ShapeStyle(targetShape)
|
||||
renderedSides := mainShape.Render()
|
||||
|
||||
return borderMask + mainRect + renderedSides + renderedBorder
|
||||
return borderMask + mainShapeRendered + renderedSides + renderedBorder
|
||||
}
|
||||
|
||||
func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2sketch.Runner) (labelMask string, err error) {
|
||||
|
|
@ -646,7 +688,8 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y))
|
||||
width := float64(targetShape.Width)
|
||||
height := float64(targetShape.Height)
|
||||
style := shapeStyle(targetShape)
|
||||
fill, stroke := svg_style.ShapeTheme(targetShape)
|
||||
style := svg_style.ShapeStyle(targetShape)
|
||||
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[targetShape.Type]
|
||||
|
||||
s := shape.NewShape(shapeType, geo.NewBox(tl, width, height))
|
||||
|
|
@ -682,12 +725,12 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
drawClass(writer, targetShape)
|
||||
}
|
||||
fmt.Fprintf(writer, `</g>`)
|
||||
fmt.Fprintf(writer, closingTag)
|
||||
fmt.Fprint(writer, closingTag)
|
||||
return labelMask, nil
|
||||
case d2target.ShapeSQLTable:
|
||||
if sketchRunner != nil {
|
||||
|
|
@ -695,31 +738,38 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
drawTable(writer, targetShape)
|
||||
}
|
||||
fmt.Fprintf(writer, `</g>`)
|
||||
fmt.Fprintf(writer, closingTag)
|
||||
fmt.Fprint(writer, closingTag)
|
||||
return labelMask, nil
|
||||
case d2target.ShapeOval:
|
||||
if targetShape.Multiple {
|
||||
fmt.Fprint(writer, renderOval(multipleTL, width, height, style))
|
||||
fmt.Fprint(writer, renderOval(multipleTL, width, height, fill, stroke, style))
|
||||
}
|
||||
if sketchRunner != nil {
|
||||
out, err := d2sketch.Oval(sketchRunner, targetShape)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
fmt.Fprint(writer, renderOval(tl, width, height, style))
|
||||
fmt.Fprint(writer, renderOval(tl, width, height, fill, stroke, style))
|
||||
}
|
||||
|
||||
case d2target.ShapeImage:
|
||||
fmt.Fprintf(writer, `<image href="%s" x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||
html.EscapeString(targetShape.Icon.String()),
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
||||
el := svg_style.NewThemableElement("image")
|
||||
el.X = float64(targetShape.Pos.X)
|
||||
el.Y = float64(targetShape.Pos.Y)
|
||||
el.Width = float64(targetShape.Width)
|
||||
el.Height = float64(targetShape.Height)
|
||||
el.Href = html.EscapeString(targetShape.Icon.String())
|
||||
el.Fill = fill
|
||||
el.Stroke = stroke
|
||||
el.Style = style
|
||||
fmt.Fprint(writer, el.Render())
|
||||
|
||||
// TODO should standardize "" to rectangle
|
||||
case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, "":
|
||||
|
|
@ -727,26 +777,45 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
fmt.Fprint(writer, render3dRect(targetShape))
|
||||
} else {
|
||||
if targetShape.Multiple {
|
||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||
targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
|
||||
el := svg_style.NewThemableElement("rect")
|
||||
el.X = float64(targetShape.Pos.X + 10)
|
||||
el.Y = float64(targetShape.Pos.Y - 10)
|
||||
el.Width = float64(targetShape.Width)
|
||||
el.Height = float64(targetShape.Height)
|
||||
el.Fill = fill
|
||||
el.Stroke = stroke
|
||||
el.Style = style
|
||||
fmt.Fprint(writer, el.Render())
|
||||
}
|
||||
if sketchRunner != nil {
|
||||
out, err := d2sketch.Rect(sketchRunner, targetShape)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
||||
el := svg_style.NewThemableElement("rect")
|
||||
el.X = float64(targetShape.Pos.X)
|
||||
el.Y = float64(targetShape.Pos.Y)
|
||||
el.Width = float64(targetShape.Width)
|
||||
el.Height = float64(targetShape.Height)
|
||||
el.Fill = fill
|
||||
el.Stroke = stroke
|
||||
el.Style = style
|
||||
fmt.Fprint(writer, el.Render())
|
||||
}
|
||||
}
|
||||
case d2target.ShapeText, d2target.ShapeCode:
|
||||
default:
|
||||
if targetShape.Multiple {
|
||||
multiplePathData := shape.NewShape(shapeType, geo.NewBox(multipleTL, width, height)).GetSVGPathData()
|
||||
el := svg_style.NewThemableElement("path")
|
||||
el.Fill = fill
|
||||
el.Stroke = stroke
|
||||
el.Style = style
|
||||
for _, pathData := range multiplePathData {
|
||||
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
|
||||
el.D = pathData
|
||||
fmt.Fprint(writer, el.Render())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -755,10 +824,15 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
el := svg_style.NewThemableElement("path")
|
||||
el.Fill = fill
|
||||
el.Stroke = stroke
|
||||
el.Style = style
|
||||
for _, pathData := range s.GetSVGPathData() {
|
||||
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
|
||||
el.D = pathData
|
||||
fmt.Fprint(writer, el.Render())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -826,11 +900,14 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
}
|
||||
|
||||
svgStyles := styleToSVG(style)
|
||||
containerStyle := fmt.Sprintf(`stroke: %s;fill:%s`, targetShape.Stroke, style.Get(chroma.Background).Background.String())
|
||||
|
||||
fmt.Fprintf(writer, `<g transform="translate(%f %f)" style="opacity:%f">`, box.TopLeft.X, box.TopLeft.Y, targetShape.Opacity)
|
||||
fmt.Fprintf(writer, `<rect class="shape" width="%d" height="%d" style="%s" />`,
|
||||
targetShape.Width, targetShape.Height, containerStyle)
|
||||
rectEl := svg_style.NewThemableElement("rect")
|
||||
rectEl.Width = float64(targetShape.Width)
|
||||
rectEl.Height = float64(targetShape.Height)
|
||||
rectEl.Stroke = targetShape.Stroke
|
||||
rectEl.Class = "shape"
|
||||
rectEl.Style = fmt.Sprintf(`fill:%s`, style.Get(chroma.Background).Background.String())
|
||||
fmt.Fprint(writer, rectEl.Render())
|
||||
// Padding
|
||||
fmt.Fprintf(writer, `<g transform="translate(6 6)">`)
|
||||
|
||||
|
|
@ -867,31 +944,32 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
// we need the self closing form in this svg/xhtml context
|
||||
render = strings.ReplaceAll(render, "<hr>", "<hr />")
|
||||
|
||||
var mdStyle string
|
||||
if targetShape.Fill != "" {
|
||||
mdStyle = fmt.Sprintf("background-color:%s;", targetShape.Fill)
|
||||
mdEl := svg_style.NewThemableElement("div")
|
||||
mdEl.Xmlns = "http://www.w3.org/1999/xhtml"
|
||||
mdEl.Class = "md"
|
||||
mdEl.Content = render
|
||||
if targetShape.Fill != color.Empty {
|
||||
mdEl.BackgroundColor = targetShape.Fill
|
||||
}
|
||||
if targetShape.Stroke != "" {
|
||||
mdStyle += fmt.Sprintf("color:%s;", targetShape.Stroke)
|
||||
if targetShape.Stroke != color.Empty {
|
||||
mdEl.Color = targetShape.Stroke
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, `<div xmlns="http://www.w3.org/1999/xhtml" class="md" style="%s">%v</div>`, mdStyle, render)
|
||||
fmt.Fprint(writer, mdEl.Render())
|
||||
fmt.Fprint(writer, `</foreignObject></g>`)
|
||||
} else {
|
||||
fontColor := "black"
|
||||
if targetShape.Color != "" {
|
||||
fontColor := color.N1
|
||||
if targetShape.Color != color.Empty {
|
||||
fontColor = targetShape.Color
|
||||
}
|
||||
textStyle := fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "middle", targetShape.FontSize, fontColor)
|
||||
x := labelTL.X + float64(targetShape.LabelWidth)/2.
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = labelTL.X + float64(targetShape.LabelWidth)/2
|
||||
// text is vertically positioned at its baseline which is at labelTL+FontSize
|
||||
y := labelTL.Y + float64(targetShape.FontSize)
|
||||
fmt.Fprintf(writer, `<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
||||
fontClass,
|
||||
x, y,
|
||||
textStyle,
|
||||
RenderText(targetShape.Label, x, float64(targetShape.LabelHeight)),
|
||||
)
|
||||
textEl.Y = labelTL.Y + float64(targetShape.FontSize)
|
||||
textEl.Fill = fontColor
|
||||
textEl.Class = fontClass
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "middle", targetShape.FontSize)
|
||||
textEl.Content = RenderText(targetShape.Label, textEl.X, float64(targetShape.LabelHeight))
|
||||
fmt.Fprint(writer, textEl.Render())
|
||||
if targetShape.Blend {
|
||||
labelMask = makeLabelMask(labelTL, targetShape.LabelWidth, targetShape.LabelHeight-d2graph.INNER_LABEL_PADDING)
|
||||
}
|
||||
|
|
@ -917,7 +995,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
)
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, closingTag)
|
||||
fmt.Fprint(writer, closingTag)
|
||||
return labelMask, nil
|
||||
}
|
||||
|
||||
|
|
@ -942,46 +1020,9 @@ func RenderText(text string, x, height float64) string {
|
|||
return strings.Join(rendered, "")
|
||||
}
|
||||
|
||||
func shapeStyle(shape d2target.Shape) string {
|
||||
out := ""
|
||||
|
||||
if shape.Type == d2target.ShapeSQLTable || shape.Type == d2target.ShapeClass {
|
||||
// Fill is used for header fill in these types
|
||||
// This fill property is just background of rows
|
||||
out += fmt.Sprintf(`fill:%s;`, shape.Stroke)
|
||||
// Stroke (border) of these shapes should match the header fill
|
||||
out += fmt.Sprintf(`stroke:%s;`, shape.Fill)
|
||||
} else {
|
||||
out += fmt.Sprintf(`fill:%s;`, shape.Fill)
|
||||
out += fmt.Sprintf(`stroke:%s;`, shape.Stroke)
|
||||
}
|
||||
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
||||
if shape.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func connectionStyle(connection d2target.Connection) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`stroke:%s;`, connection.Stroke)
|
||||
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||
if connection.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||
func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) string {
|
||||
content := buf.String()
|
||||
buf.WriteString(`<style type="text/css"><![CDATA[`)
|
||||
out := `<style type="text/css"><![CDATA[`
|
||||
|
||||
triggers := []string{
|
||||
`class="text"`,
|
||||
|
|
@ -991,7 +1032,7 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
|||
|
||||
for _, t := range triggers {
|
||||
if strings.Contains(content, t) {
|
||||
fmt.Fprintf(buf, `
|
||||
out += fmt.Sprintf(`
|
||||
.text {
|
||||
font-family: "font-regular";
|
||||
}
|
||||
|
|
@ -1010,10 +1051,10 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
|||
|
||||
for _, t := range triggers {
|
||||
if strings.Contains(content, t) {
|
||||
buf.WriteString(`
|
||||
out += `
|
||||
.text-underline {
|
||||
text-decoration: underline;
|
||||
}`)
|
||||
}`
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -1024,23 +1065,23 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
|||
|
||||
for _, t := range triggers {
|
||||
if strings.Contains(content, t) {
|
||||
buf.WriteString(`
|
||||
out += `
|
||||
.appendix-icon {
|
||||
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
|
||||
}`)
|
||||
}`
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
triggers = []string{
|
||||
`class="text-bold"`,
|
||||
`class="text-bold`,
|
||||
`<b>`,
|
||||
`<strong>`,
|
||||
}
|
||||
|
||||
for _, t := range triggers {
|
||||
if strings.Contains(content, t) {
|
||||
fmt.Fprintf(buf, `
|
||||
out += fmt.Sprintf(`
|
||||
.text-bold {
|
||||
font-family: "font-bold";
|
||||
}
|
||||
|
|
@ -1054,14 +1095,14 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
|||
}
|
||||
|
||||
triggers = []string{
|
||||
`class="text-italic"`,
|
||||
`class="text-italic`,
|
||||
`<em>`,
|
||||
`<dfn>`,
|
||||
}
|
||||
|
||||
for _, t := range triggers {
|
||||
if strings.Contains(content, t) {
|
||||
fmt.Fprintf(buf, `
|
||||
out += fmt.Sprintf(`
|
||||
.text-italic {
|
||||
font-family: "font-italic";
|
||||
}
|
||||
|
|
@ -1075,7 +1116,7 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
|||
}
|
||||
|
||||
triggers = []string{
|
||||
`class="text-mono"`,
|
||||
`class="text-mono`,
|
||||
`<pre>`,
|
||||
`<code>`,
|
||||
`<kbd>`,
|
||||
|
|
@ -1084,7 +1125,7 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
|||
|
||||
for _, t := range triggers {
|
||||
if strings.Contains(content, t) {
|
||||
fmt.Fprintf(buf, `
|
||||
out += fmt.Sprintf(`
|
||||
.text-mono {
|
||||
font-family: "font-mono";
|
||||
}
|
||||
|
|
@ -1097,12 +1138,18 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
|||
}
|
||||
}
|
||||
|
||||
buf.WriteString(`]]></style>`)
|
||||
out += `]]></style>`
|
||||
return out
|
||||
}
|
||||
|
||||
//go:embed fitToScreen.js
|
||||
var fitToScreenScript string
|
||||
|
||||
const (
|
||||
BG_COLOR = color.N7
|
||||
FG_COLOR = color.N1
|
||||
)
|
||||
|
||||
// TODO minify output at end
|
||||
func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
||||
var sketchRunner *d2sketch.Runner
|
||||
|
|
@ -1120,36 +1167,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
|
||||
theme := d2themescatalog.Find(opts.ThemeID)
|
||||
bgColor := theme.Colors.Neutrals.N7
|
||||
fgColor := theme.Colors.Neutrals.N1
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
w, h := setViewbox(buf, diagram, pad, bgColor, fgColor)
|
||||
|
||||
styleCSS2 := ""
|
||||
if sketchRunner != nil {
|
||||
styleCSS2 = "\n" + sketchStyleCSS
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf(`<style type="text/css"><![CDATA[%s%s]]></style>`, styleCSS, styleCSS2))
|
||||
|
||||
// this script won't run in --watch mode because script tags are ignored when added via el.innerHTML = element
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
|
||||
buf.WriteString(fmt.Sprintf(`<script type="application/javascript"><![CDATA[%s]]></script>`, fitToScreenScript))
|
||||
|
||||
hasMarkdown := false
|
||||
for _, s := range diagram.Shapes {
|
||||
if s.Label != "" && s.Type == d2target.ShapeText {
|
||||
hasMarkdown = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasMarkdown {
|
||||
fmt.Fprintf(buf, `<style type="text/css">%s</style>`, mdCSS)
|
||||
}
|
||||
if sketchRunner != nil && sketchBg {
|
||||
fmt.Fprint(buf, d2sketch.DefineFillPattern())
|
||||
}
|
||||
|
||||
// only define shadow filter if a shape uses it
|
||||
for _, s := range diagram.Shapes {
|
||||
|
|
@ -1184,7 +1202,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
markers := map[string]struct{}{}
|
||||
for _, obj := range allObjects {
|
||||
if c, is := obj.(d2target.Connection); is {
|
||||
labelMask, err := drawConnection(buf, bgColor, fgColor, labelMaskID, c, markers, idToShape, sketchRunner)
|
||||
labelMask, err := drawConnection(buf, BG_COLOR, FG_COLOR, labelMaskID, c, markers, idToShape, sketchRunner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1204,6 +1222,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
}
|
||||
|
||||
// Note: we always want this since we reference it on connections even if there end up being no masked labels
|
||||
w, h, tl, _ := dimensions(buf, diagram, pad)
|
||||
fmt.Fprint(buf, strings.Join([]string{
|
||||
fmt.Sprintf(`<mask id="%s" maskUnits="userSpaceOnUse" x="%d" y="%d" width="%d" height="%d">`,
|
||||
labelMaskID, -pad, -pad, w, h,
|
||||
|
|
@ -1215,10 +1234,48 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
`</mask>`,
|
||||
}, "\n"))
|
||||
|
||||
embedFonts(buf, diagram.FontFamily)
|
||||
// TODO minify
|
||||
// TODO background stuff. e.g. dotted, grid, colors
|
||||
containerEl := svg_style.NewThemableElement("rect")
|
||||
containerEl.X = float64(tl.X - pad - 10) // TODO the background is not rendered all over the image
|
||||
containerEl.Y = float64(tl.Y - pad - 10) // so I had to add 10 to the size - someone smarter than me please fix this
|
||||
containerEl.Width = float64(w + 10*2)
|
||||
containerEl.Height = float64(h + 10*2)
|
||||
containerEl.Fill = color.N7
|
||||
// containerEl.Color = color.N1 TODO this is useless as this element has no children
|
||||
|
||||
buf.WriteString(`</svg>`)
|
||||
return buf.Bytes(), nil
|
||||
// generate elements that will be appended to the SVG tag
|
||||
styleCSS2 := ""
|
||||
if sketchRunner != nil {
|
||||
styleCSS2 = "\n" + sketchStyleCSS
|
||||
}
|
||||
svgOut := fmt.Sprintf(`<style type="text/css"><![CDATA[%s%s]]></style>`, styleCSS, styleCSS2)
|
||||
// this script won't run in --watch mode because script tags are ignored when added via el.innerHTML = element
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
|
||||
svgOut += fmt.Sprintf(`<script type="application/javascript"><![CDATA[%s]]></script>`, fitToScreenScript)
|
||||
hasMarkdown := false
|
||||
for _, s := range diagram.Shapes {
|
||||
if s.Label != "" && s.Type == d2target.ShapeText {
|
||||
hasMarkdown = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasMarkdown {
|
||||
svgOut += fmt.Sprintf(`<style type="text/css">%s</style>`, mdCSS)
|
||||
}
|
||||
if sketchRunner != nil && sketchBg {
|
||||
svgOut += d2sketch.DefineFillPattern()
|
||||
}
|
||||
svgOut += embedFonts(buf, diagram.FontFamily)
|
||||
|
||||
// render the document
|
||||
docRendered := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?><svg id="d2-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d" viewBox="%d %d %d %d">%s%s%s</svg>`,
|
||||
w, h, tl.X-pad, tl.Y-pad, w, h,
|
||||
svgOut,
|
||||
containerEl.Render(),
|
||||
buf.String(),
|
||||
)
|
||||
return []byte(docRendered), nil
|
||||
}
|
||||
|
||||
type DiagramObject interface {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,456 @@
|
|||
stroke-linejoin: round;
|
||||
}
|
||||
.blend {
|
||||
mix-blend-mode: multiply;
|
||||
mix-Blend-mode: multiply;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*
|
||||
.fill
|
||||
.stroke
|
||||
|
||||
.background-color
|
||||
.color
|
||||
*/
|
||||
|
||||
.fill-N1 {
|
||||
fill: #0A0F25;
|
||||
}
|
||||
.fill-N2 {
|
||||
fill: #676C7E;
|
||||
}
|
||||
.fill-N3 {
|
||||
fill: #9499AB;
|
||||
}
|
||||
.fill-N4 {
|
||||
fill: #CFD2DD;
|
||||
}
|
||||
.fill-N5 {
|
||||
fill: #DEE1EB;
|
||||
}
|
||||
.fill-N6 {
|
||||
fill: #EEF1F8;
|
||||
}
|
||||
.fill-N7 {
|
||||
fill: #FFFFFF;
|
||||
}
|
||||
.fill-B1 {
|
||||
fill: #0D32B2;
|
||||
}
|
||||
.fill-B2 {
|
||||
fill: #0D32B2;
|
||||
}
|
||||
.fill-B3 {
|
||||
fill: #E3E9FD;
|
||||
}
|
||||
.fill-B4 {
|
||||
fill: #E3E9FD;
|
||||
}
|
||||
.fill-B5 {
|
||||
fill: #EDF0FD;
|
||||
}
|
||||
.fill-B6 {
|
||||
fill: #F7F8FE;
|
||||
}
|
||||
.fill-AA2 {
|
||||
fill: #4A6FF3;
|
||||
}
|
||||
.fill-AA4 {
|
||||
fill: #EDF0FD;
|
||||
}
|
||||
.fill-AA5 {
|
||||
fill: #F7F8FE;
|
||||
}
|
||||
.fill-AB4 {
|
||||
fill: #DEE1EB;
|
||||
}
|
||||
.fill-AB5 {
|
||||
fill: #F7F8FE;
|
||||
}
|
||||
|
||||
.stroke-N1 {
|
||||
stroke: #0A0F25;
|
||||
}
|
||||
.stroke-N2 {
|
||||
stroke: #676C7E;
|
||||
}
|
||||
.stroke-N3 {
|
||||
stroke: #9499AB;
|
||||
}
|
||||
.stroke-N4 {
|
||||
stroke: #CFD2DD;
|
||||
}
|
||||
.stroke-N5 {
|
||||
stroke: #DEE1EB;
|
||||
}
|
||||
.stroke-N6 {
|
||||
stroke: #EEF1F8;
|
||||
}
|
||||
.stroke-N7 {
|
||||
stroke: #FFFFFF;
|
||||
}
|
||||
.stroke-B1 {
|
||||
stroke: #0D32B2;
|
||||
}
|
||||
.stroke-B2 {
|
||||
stroke: #0D32B2;
|
||||
}
|
||||
.stroke-B3 {
|
||||
stroke: #E3E9FD;
|
||||
}
|
||||
.stroke-B4 {
|
||||
stroke: #E3E9FD;
|
||||
}
|
||||
.stroke-B5 {
|
||||
stroke: #EDF0FD;
|
||||
}
|
||||
.stroke-B6 {
|
||||
stroke: #F7F8FE;
|
||||
}
|
||||
.stroke-AA2 {
|
||||
stroke: #4A6FF3;
|
||||
}
|
||||
.stroke-AA4 {
|
||||
stroke: #EDF0FD;
|
||||
}
|
||||
.stroke-AA5 {
|
||||
stroke: #F7F8FE;
|
||||
}
|
||||
.stroke-AB4 {
|
||||
stroke: #DEE1EB;
|
||||
}
|
||||
.stroke-AB5 {
|
||||
stroke: #F7F8FE;
|
||||
}
|
||||
|
||||
.background-color-N1 {
|
||||
background-color: #0A0F25;
|
||||
}
|
||||
.background-color-N2 {
|
||||
background-color: #676C7E;
|
||||
}
|
||||
.background-color-N3 {
|
||||
background-color: #9499AB;
|
||||
}
|
||||
.background-color-N4 {
|
||||
background-color: #CFD2DD;
|
||||
}
|
||||
.background-color-N5 {
|
||||
background-color: #DEE1EB;
|
||||
}
|
||||
.background-color-N6 {
|
||||
background-color: #EEF1F8;
|
||||
}
|
||||
.background-color-N7 {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.background-color-B1 {
|
||||
background-color: #0D32B2;
|
||||
}
|
||||
.background-color-B2 {
|
||||
background-color: #0D32B2;
|
||||
}
|
||||
.background-color-B3 {
|
||||
background-color: #E3E9FD;
|
||||
}
|
||||
.background-color-B4 {
|
||||
background-color: #E3E9FD;
|
||||
}
|
||||
.background-color-B5 {
|
||||
background-color: #EDF0FD;
|
||||
}
|
||||
.background-color-B6 {
|
||||
background-color: #F7F8FE;
|
||||
}
|
||||
.background-color-AA2 {
|
||||
background-color: #4A6FF3;
|
||||
}
|
||||
.background-color-AA4 {
|
||||
background-color: #EDF0FD;
|
||||
}
|
||||
.background-color-AA5 {
|
||||
background-color: #F7F8FE;
|
||||
}
|
||||
.background-color-AB4 {
|
||||
background-color: #DEE1EB;
|
||||
}
|
||||
.background-color-AB5 {
|
||||
background-color: #F7F8FE;
|
||||
}
|
||||
|
||||
.color-N1 {
|
||||
color: #0A0F25;
|
||||
}
|
||||
.color-N2 {
|
||||
color: #676C7E;
|
||||
}
|
||||
.color-N3 {
|
||||
color: #9499AB;
|
||||
}
|
||||
.color-N4 {
|
||||
color: #CFD2DD;
|
||||
}
|
||||
.color-N5 {
|
||||
color: #DEE1EB;
|
||||
}
|
||||
.color-N6 {
|
||||
color: #EEF1F8;
|
||||
}
|
||||
.color-N7 {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.color-B1 {
|
||||
color: #0D32B2;
|
||||
}
|
||||
.color-B2 {
|
||||
color: #0D32B2;
|
||||
}
|
||||
.color-B3 {
|
||||
color: #E3E9FD;
|
||||
}
|
||||
.color-B4 {
|
||||
color: #E3E9FD;
|
||||
}
|
||||
.color-B5 {
|
||||
color: #EDF0FD;
|
||||
}
|
||||
.color-B6 {
|
||||
color: #F7F8FE;
|
||||
}
|
||||
.color-AA2 {
|
||||
color: #4A6FF3;
|
||||
}
|
||||
.color-AA4 {
|
||||
color: #EDF0FD;
|
||||
}
|
||||
.color-AA5 {
|
||||
color: #F7F8FE;
|
||||
}
|
||||
.color-AB4 {
|
||||
color: #DEE1EB;
|
||||
}
|
||||
.color-AB5 {
|
||||
color: #F7F8FE;
|
||||
}
|
||||
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
.fill-N1 {
|
||||
fill: #cdd6f4;
|
||||
}
|
||||
.fill-N2 {
|
||||
fill: #bac2de;
|
||||
}
|
||||
.fill-N3 {
|
||||
fill: #a6adc8;
|
||||
}
|
||||
.fill-N4 {
|
||||
fill: #585b70;
|
||||
}
|
||||
.fill-N5 {
|
||||
fill: #45475a;
|
||||
}
|
||||
.fill-N6 {
|
||||
fill: #313244;
|
||||
}
|
||||
.fill-N7 {
|
||||
fill: #1e1e2e;
|
||||
}
|
||||
.fill-B1 {
|
||||
fill: #cba6f7;
|
||||
}
|
||||
.fill-B2 {
|
||||
fill: #cba6f7;
|
||||
}
|
||||
.fill-B3 {
|
||||
fill: #6c7086;
|
||||
}
|
||||
.fill-B4 {
|
||||
fill: #585b70;
|
||||
}
|
||||
.fill-B5 {
|
||||
fill: #45475a;
|
||||
}
|
||||
.fill-B6 {
|
||||
fill: #313244;
|
||||
}
|
||||
.fill-AA2 {
|
||||
fill: #f38ba8;
|
||||
}
|
||||
.fill-AA4 {
|
||||
fill: #45475a;
|
||||
}
|
||||
.fill-AA5 {
|
||||
fill: #313244;
|
||||
}
|
||||
.fill-AB4 {
|
||||
fill: #45475a;
|
||||
}
|
||||
.fill-AB5 {
|
||||
fill: #313244;
|
||||
}
|
||||
|
||||
.stroke-N1 {
|
||||
stroke: #cdd6f4;
|
||||
}
|
||||
.stroke-N2 {
|
||||
stroke: #bac2de;
|
||||
}
|
||||
.stroke-N3 {
|
||||
stroke: #a6adc8;
|
||||
}
|
||||
.stroke-N4 {
|
||||
stroke: #585b70;
|
||||
}
|
||||
.stroke-N5 {
|
||||
stroke: #45475a;
|
||||
}
|
||||
.stroke-N6 {
|
||||
stroke: #313244;
|
||||
}
|
||||
.stroke-N7 {
|
||||
stroke: #1e1e2e;
|
||||
}
|
||||
.stroke-B1 {
|
||||
stroke: #cba6f7;
|
||||
}
|
||||
.stroke-B2 {
|
||||
stroke: #cba6f7;
|
||||
}
|
||||
.stroke-B3 {
|
||||
stroke: #6c7086;
|
||||
}
|
||||
.stroke-B4 {
|
||||
stroke: #585b70;
|
||||
}
|
||||
.stroke-B5 {
|
||||
stroke: #45475a;
|
||||
}
|
||||
.stroke-B6 {
|
||||
stroke: #313244;
|
||||
}
|
||||
.stroke-AA2 {
|
||||
stroke: #f38ba8;
|
||||
}
|
||||
.stroke-AA4 {
|
||||
stroke: #45475a;
|
||||
}
|
||||
.stroke-AA5 {
|
||||
stroke: #313244;
|
||||
}
|
||||
.stroke-AB4 {
|
||||
stroke: #45475a;
|
||||
}
|
||||
.stroke-AB5 {
|
||||
stroke: #313244;
|
||||
}
|
||||
|
||||
.background-color-N1 {
|
||||
background-color: #cdd6f4;
|
||||
}
|
||||
.background-color-N2 {
|
||||
background-color: #bac2de;
|
||||
}
|
||||
.background-color-N3 {
|
||||
background-color: #a6adc8;
|
||||
}
|
||||
.background-color-N4 {
|
||||
background-color: #585b70;
|
||||
}
|
||||
.background-color-N5 {
|
||||
background-color: #45475a;
|
||||
}
|
||||
.background-color-N6 {
|
||||
background-color: #313244;
|
||||
}
|
||||
.background-color-N7 {
|
||||
background-color: #1e1e2e;
|
||||
}
|
||||
.background-color-B1 {
|
||||
background-color: #cba6f7;
|
||||
}
|
||||
.background-color-B2 {
|
||||
background-color: #cba6f7;
|
||||
}
|
||||
.background-color-B3 {
|
||||
background-color: #6c7086;
|
||||
}
|
||||
.background-color-B4 {
|
||||
background-color: #585b70;
|
||||
}
|
||||
.background-color-B5 {
|
||||
background-color: #45475a;
|
||||
}
|
||||
.background-color-B6 {
|
||||
background-color: #313244;
|
||||
}
|
||||
.background-color-AA2 {
|
||||
background-color: #f38ba8;
|
||||
}
|
||||
.background-color-AA4 {
|
||||
background-color: #45475a;
|
||||
}
|
||||
.background-color-AA5 {
|
||||
background-color: #313244;
|
||||
}
|
||||
.background-color-AB4 {
|
||||
background-color: #45475a;
|
||||
}
|
||||
.background-color-AB5 {
|
||||
background-color: #313244;
|
||||
}
|
||||
|
||||
.color-N1 {
|
||||
color: #cdd6f4;
|
||||
}
|
||||
.color-N2 {
|
||||
color: #bac2de;
|
||||
}
|
||||
.color-N3 {
|
||||
color: #a6adc8;
|
||||
}
|
||||
.color-N4 {
|
||||
color: #585b70;
|
||||
}
|
||||
.color-N5 {
|
||||
color: #45475a;
|
||||
}
|
||||
.color-N6 {
|
||||
color: #313244;
|
||||
}
|
||||
.color-N7 {
|
||||
color: #1e1e2e;
|
||||
}
|
||||
.color-B1 {
|
||||
color: #cba6f7;
|
||||
}
|
||||
.color-B2 {
|
||||
color: #cba6f7;
|
||||
}
|
||||
.color-B3 {
|
||||
color: #6c7086;
|
||||
}
|
||||
.color-B4 {
|
||||
color: #585b70;
|
||||
}
|
||||
.color-B5 {
|
||||
color: #45475a;
|
||||
}
|
||||
.color-B6 {
|
||||
color: #313244;
|
||||
}
|
||||
.color-AA2 {
|
||||
color: #f38ba8;
|
||||
}
|
||||
.color-AA4 {
|
||||
color: #45475a;
|
||||
}
|
||||
.color-AA5 {
|
||||
color: #313244;
|
||||
}
|
||||
.color-AB4 {
|
||||
color: #45475a;
|
||||
}
|
||||
.color-AB5 {
|
||||
color: #313244;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,22 @@ package d2svg
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/label"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
svg_style "oss.terrastruct.com/d2/lib/svg/style"
|
||||
"oss.terrastruct.com/util-go/go2"
|
||||
)
|
||||
|
||||
func tableHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string {
|
||||
str := fmt.Sprintf(`<rect class="class_header" x="%f" y="%f" width="%f" height="%f" fill="%s" />`,
|
||||
box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height, shape.Fill)
|
||||
rectEl := svg_style.NewThemableElement("rect")
|
||||
rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y
|
||||
rectEl.Width, rectEl.Height = box.Width, box.Height
|
||||
rectEl.Fill = shape.Fill
|
||||
rectEl.Class = "class_header"
|
||||
str := rectEl.Render()
|
||||
|
||||
if text != "" {
|
||||
tl := label.InsideMiddleLeft.GetPointOnBox(
|
||||
|
|
@ -24,17 +28,16 @@ func tableHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, tex
|
|||
textHeight,
|
||||
)
|
||||
|
||||
str += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
||||
"text",
|
||||
tl.X,
|
||||
tl.Y+textHeight*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s",
|
||||
"start",
|
||||
4+fontSize,
|
||||
shape.Stroke,
|
||||
),
|
||||
svg.EscapeText(text),
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = tl.X
|
||||
textEl.Y = tl.Y + textHeight*3/4
|
||||
textEl.Fill = shape.Stroke
|
||||
textEl.Class = "text"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
|
||||
"start", 4+fontSize,
|
||||
)
|
||||
textEl.Content = svg.EscapeText(text)
|
||||
str += textEl.Render()
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
|
@ -55,33 +58,40 @@ func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraint
|
|||
fontSize,
|
||||
)
|
||||
|
||||
return strings.Join([]string{
|
||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
||||
nameTL.X,
|
||||
nameTL.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor),
|
||||
svg.EscapeText(nameText),
|
||||
),
|
||||
textEl := svg_style.NewThemableElement("text")
|
||||
textEl.X = nameTL.X
|
||||
textEl.Y = nameTL.Y + fontSize*3/4
|
||||
textEl.Fill = shape.PrimaryAccentColor
|
||||
textEl.Class = "text"
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", fontSize)
|
||||
textEl.Content = svg.EscapeText(nameText)
|
||||
out := textEl.Render()
|
||||
|
||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
||||
nameTL.X+longestNameWidth+2*d2target.NamePadding,
|
||||
nameTL.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.NeutralAccentColor),
|
||||
svg.EscapeText(typeText),
|
||||
),
|
||||
textEl.X = nameTL.X + longestNameWidth + 2*d2target.NamePadding
|
||||
textEl.Fill = shape.NeutralAccentColor
|
||||
textEl.Content = svg.EscapeText(typeText)
|
||||
out += textEl.Render()
|
||||
|
||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
||||
constraintTR.X,
|
||||
constraintTR.Y+fontSize*3/4,
|
||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;letter-spacing:2px;", "end", fontSize, shape.SecondaryAccentColor),
|
||||
constraintText,
|
||||
),
|
||||
}, "\n")
|
||||
textEl.X = constraintTR.X
|
||||
textEl.Y = constraintTR.Y + fontSize*3/4
|
||||
textEl.Fill = shape.SecondaryAccentColor
|
||||
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx;letter-spacing:2px", "end", fontSize)
|
||||
textEl.Content = constraintText
|
||||
out += textEl.Render()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func drawTable(writer io.Writer, targetShape d2target.Shape) {
|
||||
fmt.Fprintf(writer, `<rect class="shape" x="%d" y="%d" width="%d" height="%d" style="%s"/>`,
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape))
|
||||
rectEl := svg_style.NewThemableElement("rect")
|
||||
rectEl.X = float64(targetShape.Pos.X)
|
||||
rectEl.Y = float64(targetShape.Pos.Y)
|
||||
rectEl.Width = float64(targetShape.Width)
|
||||
rectEl.Height = float64(targetShape.Height)
|
||||
rectEl.Fill, rectEl.Stroke = svg_style.ShapeTheme(targetShape)
|
||||
rectEl.Class = "shape"
|
||||
rectEl.Style = svg_style.ShapeStyle(targetShape)
|
||||
fmt.Fprint(writer, rectEl.Render())
|
||||
|
||||
box := geo.NewBox(
|
||||
geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)),
|
||||
|
|
@ -108,10 +118,12 @@ func drawTable(writer io.Writer, targetShape d2target.Shape) {
|
|||
tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth)),
|
||||
)
|
||||
rowBox.TopLeft.Y += rowHeight
|
||||
fmt.Fprintf(writer, `<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke-width:2;stroke:%s" />`,
|
||||
rowBox.TopLeft.X, rowBox.TopLeft.Y,
|
||||
rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y,
|
||||
targetShape.Fill,
|
||||
)
|
||||
|
||||
lineEl := svg_style.NewThemableElement("line")
|
||||
lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y
|
||||
lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y
|
||||
lineEl.Stroke = targetShape.Fill
|
||||
lineEl.Style = "stroke-width:2"
|
||||
fmt.Fprint(writer, lineEl.Render())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"oss.terrastruct.com/util-go/go2"
|
||||
|
||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||
"oss.terrastruct.com/d2/d2themes"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/label"
|
||||
"oss.terrastruct.com/d2/lib/shape"
|
||||
|
|
@ -416,11 +416,11 @@ func NewTextDimensions(w, h int) *TextDimensions {
|
|||
return &TextDimensions{Width: w, Height: h}
|
||||
}
|
||||
|
||||
func (text MText) GetColor(theme *d2themes.Theme, isItalic bool) string {
|
||||
func (text MText) GetColor(isItalic bool) string {
|
||||
if isItalic {
|
||||
return theme.Colors.Neutrals.N2
|
||||
return color.N2
|
||||
}
|
||||
return theme.Colors.Neutrals.N1
|
||||
return color.N1
|
||||
}
|
||||
|
||||
var DSL_SHAPE_TO_SHAPE_TYPE = map[string]string{
|
||||
|
|
|
|||
|
|
@ -14,3 +14,34 @@ func Darken(colorString string) (string, error) {
|
|||
// decrease luminance by 10%
|
||||
return colorful.Hsl(h, s, l-.1).Clamped().Hex(), nil
|
||||
}
|
||||
|
||||
const (
|
||||
N1 = "N1"
|
||||
N2 = "N2"
|
||||
N3 = "N3"
|
||||
N4 = "N4"
|
||||
N5 = "N5"
|
||||
N6 = "N6"
|
||||
N7 = "N7"
|
||||
|
||||
// Base Colors: used for containers
|
||||
B1 = "B1"
|
||||
B2 = "B2"
|
||||
B3 = "B3"
|
||||
B4 = "B4"
|
||||
B5 = "B5"
|
||||
B6 = "B6"
|
||||
|
||||
// Alternative colors A
|
||||
AA2 = "AA2"
|
||||
AA4 = "AA4"
|
||||
AA5 = "AA5"
|
||||
|
||||
// Alternative colors B
|
||||
AB4 = "AB4"
|
||||
AB5 = "AB4"
|
||||
|
||||
// Special
|
||||
Empty = ""
|
||||
None = "none"
|
||||
)
|
||||
|
|
|
|||
233
lib/svg/style/themable_element.go
Normal file
233
lib/svg/style/themable_element.go
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
package style
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
)
|
||||
|
||||
func ShapeStyle(shape d2target.Shape) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
||||
if shape.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ShapeTheme(shape d2target.Shape) (fill, stroke string) {
|
||||
if shape.Type == d2target.ShapeSQLTable || shape.Type == d2target.ShapeClass {
|
||||
// Fill is used for header fill in these types
|
||||
// This fill property is just background of rows
|
||||
fill = shape.Stroke
|
||||
// Stroke (border) of these shapes should match the header fill
|
||||
stroke = shape.Fill
|
||||
} else {
|
||||
fill = shape.Fill
|
||||
stroke = shape.Stroke
|
||||
}
|
||||
return fill, stroke
|
||||
}
|
||||
|
||||
func ConnectionStyle(connection d2target.Connection) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||
if connection.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ConnectionTheme(connection d2target.Connection) (stroke string) {
|
||||
return connection.Stroke
|
||||
}
|
||||
|
||||
// ThemableElement is a helper class for creating new XML elements.
|
||||
// This should be preffered 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
|
||||
BackgroundColor string
|
||||
Color string
|
||||
|
||||
Class string
|
||||
Style string
|
||||
Attributes string
|
||||
|
||||
Content string
|
||||
}
|
||||
|
||||
func NewThemableElement(tag string) *ThemableElement {
|
||||
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,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
color.Empty,
|
||||
color.Empty,
|
||||
color.Empty,
|
||||
color.Empty,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
}
|
||||
}
|
||||
|
||||
func (el *ThemableElement) Render() string {
|
||||
re := regexp.MustCompile(`^N[1-7]|B[1-6]|AA[245]|AB[45]$`)
|
||||
|
||||
out := "<" + el.tag
|
||||
|
||||
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"`, el.Rx)
|
||||
}
|
||||
if el.Ry != math.MaxFloat64 {
|
||||
out += fmt.Sprintf(` ry="%f"`, el.Ry)
|
||||
}
|
||||
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 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.Href) > 0 {
|
||||
out += fmt.Sprintf(` href="%s"`, el.Href)
|
||||
}
|
||||
if len(el.Xmlns) > 0 {
|
||||
out += fmt.Sprintf(` xmlns="%s"`, el.Xmlns)
|
||||
}
|
||||
|
||||
class := el.Class
|
||||
style := el.Style
|
||||
|
||||
// Add class {property}-{theme color} if the color is from a theme, set the property otherwise
|
||||
if re.MatchString(el.Stroke) {
|
||||
class += fmt.Sprintf(" stroke-%s", el.Stroke)
|
||||
} else if len(el.Stroke) > 0 {
|
||||
out += fmt.Sprintf(` stroke="%s"`, el.Stroke)
|
||||
}
|
||||
if re.MatchString(el.Fill) {
|
||||
class += fmt.Sprintf(" fill-%s", el.Fill)
|
||||
} else if len(el.Fill) > 0 {
|
||||
out += fmt.Sprintf(` fill="%s"`, el.Fill)
|
||||
}
|
||||
if re.MatchString(el.BackgroundColor) {
|
||||
class += fmt.Sprintf(" background-color-%s", el.BackgroundColor)
|
||||
} else if len(el.BackgroundColor) > 0 {
|
||||
out += fmt.Sprintf(` background-color="%s"`, el.BackgroundColor)
|
||||
}
|
||||
if re.MatchString(el.Color) {
|
||||
class += fmt.Sprintf(" color-%s", 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.Content) > 0 {
|
||||
return fmt.Sprintf("%s>%s</%s>", out, el.Content, el.tag)
|
||||
}
|
||||
return out + " />"
|
||||
}
|
||||
5
main.go
5
main.go
|
|
@ -238,9 +238,8 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketc
|
|||
|
||||
layout := plugin.Layout
|
||||
opts := &d2lib.CompileOptions{
|
||||
Layout: layout,
|
||||
Ruler: ruler,
|
||||
ThemeID: themeID,
|
||||
Layout: layout,
|
||||
Ruler: ruler,
|
||||
}
|
||||
if sketch {
|
||||
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
|
||||
|
|
|
|||
Loading…
Reference in a new issue