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/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/d2themes"
|
"oss.terrastruct.com/d2/lib/color"
|
||||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
|
||||||
"oss.terrastruct.com/util-go/go2"
|
"oss.terrastruct.com/util-go/go2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Export(ctx context.Context, g *d2graph.Graph, themeID int64, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) {
|
func Export(ctx context.Context, g *d2graph.Graph, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) {
|
||||||
theme := d2themescatalog.Find(themeID)
|
|
||||||
|
|
||||||
diagram := d2target.NewDiagram()
|
diagram := d2target.NewDiagram()
|
||||||
if fontFamily == nil {
|
if fontFamily == nil {
|
||||||
fontFamily = go2.Pointer(d2fonts.SourceSansPro)
|
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))
|
diagram.Shapes = make([]d2target.Shape, len(g.Objects))
|
||||||
for i := range 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))
|
diagram.Connections = make([]d2target.Connection, len(g.Edges))
|
||||||
for i := range 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
|
return diagram, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyTheme(shape *d2target.Shape, obj *d2graph.Object, theme *d2themes.Theme) {
|
func applyTheme(shape *d2target.Shape, obj *d2graph.Object) {
|
||||||
shape.Stroke = obj.GetStroke(theme, shape.StrokeDash)
|
shape.Stroke = obj.GetStroke(shape.StrokeDash)
|
||||||
shape.Fill = obj.GetFill(theme)
|
shape.Fill = obj.GetFill()
|
||||||
if obj.Attributes.Shape.Value == d2target.ShapeText {
|
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 {
|
if obj.Attributes.Shape.Value == d2target.ShapeSQLTable || obj.Attributes.Shape.Value == d2target.ShapeClass {
|
||||||
shape.PrimaryAccentColor = theme.Colors.B2
|
shape.PrimaryAccentColor = color.B2
|
||||||
shape.SecondaryAccentColor = theme.Colors.AA2
|
shape.SecondaryAccentColor = color.AA2
|
||||||
shape.NeutralAccentColor = theme.Colors.Neutrals.N2
|
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 := d2target.BaseShape()
|
||||||
shape.SetType(obj.Attributes.Shape.Value)
|
shape.SetType(obj.Attributes.Shape.Value)
|
||||||
shape.ID = obj.AbsID()
|
shape.ID = obj.AbsID()
|
||||||
|
|
@ -120,8 +117,8 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyles(shape, obj)
|
applyStyles(shape, obj)
|
||||||
applyTheme(shape, obj, theme)
|
applyTheme(shape, obj)
|
||||||
shape.Color = text.GetColor(theme, shape.Italic)
|
shape.Color = text.GetColor(shape.Italic)
|
||||||
applyStyles(shape, obj)
|
applyStyles(shape, obj)
|
||||||
|
|
||||||
switch obj.Attributes.Shape.Value {
|
switch obj.Attributes.Shape.Value {
|
||||||
|
|
@ -153,7 +150,7 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
|
||||||
return *shape
|
return *shape
|
||||||
}
|
}
|
||||||
|
|
||||||
func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection {
|
func toConnection(edge *d2graph.Edge) d2target.Connection {
|
||||||
connection := d2target.BaseConnection()
|
connection := d2target.BaseConnection()
|
||||||
connection.ID = edge.AbsID()
|
connection.ID = edge.AbsID()
|
||||||
connection.ZIndex = edge.ZIndex
|
connection.ZIndex = edge.ZIndex
|
||||||
|
|
@ -202,7 +199,7 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
|
||||||
if edge.Attributes.Style.StrokeDash != nil {
|
if edge.Attributes.Style.StrokeDash != nil {
|
||||||
connection.StrokeDash, _ = strconv.ParseFloat(edge.Attributes.Style.StrokeDash.Value, 64)
|
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 {
|
if edge.Attributes.Style.Stroke != nil {
|
||||||
connection.Stroke = edge.Attributes.Style.Stroke.Value
|
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.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 {
|
if edge.Attributes.Style.FontColor != nil {
|
||||||
connection.Color = edge.Attributes.Style.FontColor.Value
|
connection.Color = edge.Attributes.Style.FontColor.Value
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"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/geo"
|
||||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
)
|
)
|
||||||
|
|
@ -321,14 +321,14 @@ func (l ContainerLevel) LabelSize() int {
|
||||||
return d2fonts.FONT_SIZE_M
|
return d2fonts.FONT_SIZE_M
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *Object) GetFill(theme *d2themes.Theme) string {
|
func (obj *Object) GetFill() string {
|
||||||
level := int(obj.Level())
|
level := int(obj.Level())
|
||||||
if obj.IsSequenceDiagramNote() {
|
if obj.IsSequenceDiagramNote() {
|
||||||
return theme.Colors.Neutrals.N7
|
return color.N7
|
||||||
} else if obj.IsSequenceDiagramGroup() {
|
} else if obj.IsSequenceDiagramGroup() {
|
||||||
return theme.Colors.Neutrals.N5
|
return color.N5
|
||||||
} else if obj.Parent.IsSequenceDiagram() {
|
} else if obj.Parent.IsSequenceDiagram() {
|
||||||
return theme.Colors.B5
|
return color.B5
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill for spans
|
// fill for spans
|
||||||
|
|
@ -336,19 +336,19 @@ func (obj *Object) GetFill(theme *d2themes.Theme) string {
|
||||||
if sd != nil {
|
if sd != nil {
|
||||||
level -= int(sd.Level())
|
level -= int(sd.Level())
|
||||||
if level == 1 {
|
if level == 1 {
|
||||||
return theme.Colors.B3
|
return color.B3
|
||||||
} else if level == 2 {
|
} else if level == 2 {
|
||||||
return theme.Colors.B4
|
return color.B4
|
||||||
} else if level == 3 {
|
} else if level == 3 {
|
||||||
return theme.Colors.B5
|
return color.B5
|
||||||
} else if level == 4 {
|
} else if level == 4 {
|
||||||
return theme.Colors.Neutrals.N6
|
return color.N6
|
||||||
}
|
}
|
||||||
return theme.Colors.Neutrals.N7
|
return color.N7
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.IsSequenceDiagram() {
|
if obj.IsSequenceDiagram() {
|
||||||
return theme.Colors.Neutrals.N7
|
return color.N7
|
||||||
}
|
}
|
||||||
|
|
||||||
shape := obj.Attributes.Shape.Value
|
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 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 level == 1 {
|
||||||
if !obj.IsContainer() {
|
if !obj.IsContainer() {
|
||||||
return theme.Colors.B6
|
return color.B6
|
||||||
}
|
}
|
||||||
return theme.Colors.B4
|
return color.B4
|
||||||
} else if level == 2 {
|
} else if level == 2 {
|
||||||
return theme.Colors.B5
|
return color.B5
|
||||||
} else if level == 3 {
|
} 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 strings.EqualFold(shape, d2target.ShapeCylinder) || strings.EqualFold(shape, d2target.ShapeStoredData) || strings.EqualFold(shape, d2target.ShapePackage) {
|
||||||
if level == 1 {
|
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 strings.EqualFold(shape, d2target.ShapeStep) || strings.EqualFold(shape, d2target.ShapePage) || strings.EqualFold(shape, d2target.ShapeDocument) {
|
||||||
if level == 1 {
|
if level == 1 {
|
||||||
return theme.Colors.AB4
|
return color.AB4
|
||||||
}
|
}
|
||||||
return theme.Colors.AB5
|
return color.AB5
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.EqualFold(shape, d2target.ShapePerson) {
|
if strings.EqualFold(shape, d2target.ShapePerson) {
|
||||||
return theme.Colors.B3
|
return color.B3
|
||||||
}
|
}
|
||||||
if strings.EqualFold(shape, d2target.ShapeDiamond) {
|
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) {
|
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) {
|
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) {
|
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
|
shape := obj.Attributes.Shape.Value
|
||||||
if strings.EqualFold(shape, d2target.ShapeCode) ||
|
if strings.EqualFold(shape, d2target.ShapeCode) ||
|
||||||
strings.EqualFold(shape, d2target.ShapeText) {
|
strings.EqualFold(shape, d2target.ShapeText) {
|
||||||
return theme.Colors.Neutrals.N1
|
return color.N1
|
||||||
}
|
}
|
||||||
if strings.EqualFold(shape, d2target.ShapeClass) ||
|
if strings.EqualFold(shape, d2target.ShapeClass) ||
|
||||||
strings.EqualFold(shape, d2target.ShapeSQLTable) {
|
strings.EqualFold(shape, d2target.ShapeSQLTable) {
|
||||||
return theme.Colors.Neutrals.N7
|
return color.N7
|
||||||
}
|
}
|
||||||
if dashGapSize != 0.0 {
|
if dashGapSize != 0.0 {
|
||||||
return theme.Colors.B2
|
return color.B2
|
||||||
}
|
}
|
||||||
return theme.Colors.B1
|
return color.B1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *Object) Level() ContainerLevel {
|
func (obj *Object) Level() ContainerLevel {
|
||||||
|
|
@ -867,11 +867,11 @@ type EdgeReference struct {
|
||||||
ScopeObj *Object `json:"-"`
|
ScopeObj *Object `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Edge) GetStroke(theme *d2themes.Theme, dashGapSize interface{}) string {
|
func (e *Edge) GetStroke(dashGapSize interface{}) string {
|
||||||
if dashGapSize != 0.0 {
|
if dashGapSize != 0.0 {
|
||||||
return theme.Colors.B2
|
return color.B2
|
||||||
}
|
}
|
||||||
return theme.Colors.B1
|
return color.B1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Edge) ArrowString() string {
|
func (e *Edge) ArrowString() string {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ type CompileOptions struct {
|
||||||
// - pre-measured (web setting)
|
// - pre-measured (web setting)
|
||||||
// TODO maybe some will want to configure code font too, but that's much lower priority
|
// TODO maybe some will want to configure code font too, but that's much lower priority
|
||||||
FontFamily *d2fonts.FontFamily
|
FontFamily *d2fonts.FontFamily
|
||||||
ThemeID int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) {
|
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
|
return diagram, g, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@ package d2sketch
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
|
"oss.terrastruct.com/d2/lib/color"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/svg"
|
"oss.terrastruct.com/d2/lib/svg"
|
||||||
|
svg_style "oss.terrastruct.com/d2/lib/svg/style"
|
||||||
"oss.terrastruct.com/util-go/go2"
|
"oss.terrastruct.com/util-go/go2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -63,43 +64,26 @@ func DefineFillPattern() string {
|
||||||
</defs>`, fillPattern)
|
</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) {
|
func Rect(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
||||||
fill: "%s",
|
fill: "#000",
|
||||||
stroke: "%s",
|
stroke: "#000",
|
||||||
strokeWidth: %d,
|
strokeWidth: %d,
|
||||||
%s
|
%s
|
||||||
});`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
});`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps)
|
||||||
paths, err := computeRoughPaths(r, js)
|
paths, err := computeRoughPaths(r, js)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
output := ""
|
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 {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
output += pathEl.Render()
|
||||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
output += fmt.Sprintf(
|
output += fmt.Sprintf(
|
||||||
`<rect class="sketch-overlay" transform="translate(%d %d)" width="%d" height="%d" />`,
|
`<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) {
|
func Oval(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
js := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, {
|
js := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, {
|
||||||
fill: "%s",
|
fill: "#000",
|
||||||
stroke: "%s",
|
stroke: "#000",
|
||||||
strokeWidth: %d,
|
strokeWidth: %d,
|
||||||
%s
|
%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)
|
paths, err := computeRoughPaths(r, js)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
output := ""
|
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 {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
output += pathEl.Render()
|
||||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
output += fmt.Sprintf(
|
output += fmt.Sprintf(
|
||||||
`<ellipse class="sketch-overlay" transform="translate(%d %d)" rx="%d" ry="%d" />`,
|
`<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 := ""
|
output := ""
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
js := fmt.Sprintf(`node = rc.path("%s", {
|
js := fmt.Sprintf(`node = rc.path("%s", {
|
||||||
fill: "%s",
|
fill: "#000",
|
||||||
stroke: "%s",
|
stroke: "#000",
|
||||||
strokeWidth: %d,
|
strokeWidth: %d,
|
||||||
%s
|
%s
|
||||||
});`, path, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
});`, path, shape.StrokeWidth, baseRoughProps)
|
||||||
sketchPaths, err := computeRoughPaths(r, js)
|
sketchPaths, err := computeRoughPaths(r, js)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
for _, p := range sketchPaths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="shape" d="%s" style="%s" />`,
|
output += pathEl.Render()
|
||||||
p, shapeStyle(shape),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
for _, p := range sketchPaths {
|
for _, p := range sketchPaths {
|
||||||
output += fmt.Sprintf(
|
output += fmt.Sprintf(
|
||||||
|
|
@ -163,20 +152,6 @@ func Paths(r *Runner, shape d2target.Shape, paths []string) (string, error) {
|
||||||
return output, nil
|
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) {
|
func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) {
|
||||||
roughness := 1.0
|
roughness := 1.0
|
||||||
js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness)
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
output := ""
|
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 {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="connection" fill="none" d="%s" style="%s" %s/>`,
|
output += pathEl.Render()
|
||||||
p, connectionStyle(connection), attrs,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return output, nil
|
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) {
|
func Table(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
output := ""
|
output := ""
|
||||||
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
||||||
fill: "%s",
|
fill: "#000",
|
||||||
stroke: "%s",
|
stroke: "#000",
|
||||||
strokeWidth: %d,
|
strokeWidth: %d,
|
||||||
%s
|
%s
|
||||||
});`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
});`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps)
|
||||||
paths, err := computeRoughPaths(r, js)
|
paths, err := computeRoughPaths(r, js)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
output += pathEl.Render()
|
||||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
box := geo.NewBox(
|
box := geo.NewBox(
|
||||||
|
|
@ -223,18 +205,20 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight)
|
headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight)
|
||||||
|
|
||||||
js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
|
js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
|
||||||
fill: "%s",
|
fill: "#000",
|
||||||
%s
|
%s
|
||||||
});`, shape.Width, rowHeight, shape.Fill, baseRoughProps)
|
});`, shape.Width, rowHeight, baseRoughProps)
|
||||||
paths, err = computeRoughPaths(r, js)
|
paths, err = computeRoughPaths(r, js)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="class_header" transform="translate(%d %d)" d="%s" style="fill:%s" />`,
|
output += pathEl.Render()
|
||||||
shape.Pos.X, shape.Pos.Y, p, shape.Fill,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if shape.Label != "" {
|
if shape.Label != "" {
|
||||||
|
|
@ -245,17 +229,16 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
float64(shape.LabelHeight),
|
float64(shape.LabelHeight),
|
||||||
)
|
)
|
||||||
|
|
||||||
output += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
textEl := svg_style.NewThemableElement("text")
|
||||||
"text",
|
textEl.X = tl.X
|
||||||
tl.X,
|
textEl.Y = tl.Y + float64(shape.LabelHeight)*3/4
|
||||||
tl.Y+float64(shape.LabelHeight)*3/4,
|
textEl.Fill = shape.Stroke
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s",
|
textEl.Class = "text"
|
||||||
"start",
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
|
||||||
4+shape.FontSize,
|
"start", 4+shape.FontSize,
|
||||||
shape.Stroke,
|
|
||||||
),
|
|
||||||
svg.EscapeText(shape.Label),
|
|
||||||
)
|
)
|
||||||
|
textEl.Content = svg.EscapeText(shape.Label)
|
||||||
|
output += textEl.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
var longestNameWidth int
|
var longestNameWidth int
|
||||||
|
|
@ -279,26 +262,26 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
float64(shape.FontSize),
|
float64(shape.FontSize),
|
||||||
)
|
)
|
||||||
|
|
||||||
output += strings.Join([]string{
|
textEl := svg_style.NewThemableElement("text")
|
||||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.X = nameTL.X
|
||||||
nameTL.X,
|
textEl.Y = nameTL.Y + float64(shape.FontSize)*3/4
|
||||||
nameTL.Y+float64(shape.FontSize)*3/4,
|
textEl.Fill = shape.PrimaryAccentColor
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", float64(shape.FontSize), shape.PrimaryAccentColor),
|
textEl.Class = "text"
|
||||||
svg.EscapeText(f.Name.Label),
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", float64(shape.FontSize))
|
||||||
),
|
textEl.Content = svg.EscapeText(f.Name.Label)
|
||||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
output += textEl.Render()
|
||||||
nameTL.X+float64(longestNameWidth)+2*d2target.NamePadding,
|
|
||||||
nameTL.Y+float64(shape.FontSize)*3/4,
|
textEl.X = nameTL.X + float64(longestNameWidth) + 2*d2target.NamePadding
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", float64(shape.FontSize), shape.NeutralAccentColor),
|
textEl.Fill = shape.NeutralAccentColor
|
||||||
svg.EscapeText(f.Type.Label),
|
textEl.Content = svg.EscapeText(f.Type.Label)
|
||||||
),
|
output += textEl.Render()
|
||||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
|
||||||
constraintTR.X,
|
textEl.X = constraintTR.X
|
||||||
constraintTR.Y+float64(shape.FontSize)*3/4,
|
textEl.Y = 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),
|
textEl.Fill = shape.SecondaryAccentColor
|
||||||
f.ConstraintAbbr(),
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx;letter-spacing:2px", "end", float64(shape.FontSize))
|
||||||
),
|
textEl.Content = f.ConstraintAbbr()
|
||||||
}, "\n")
|
output += textEl.Render()
|
||||||
|
|
||||||
rowBox.TopLeft.Y += rowHeight
|
rowBox.TopLeft.Y += rowHeight
|
||||||
|
|
||||||
|
|
@ -309,11 +292,11 @@ func Table(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
pathEl := svg_style.NewThemableElement("path")
|
||||||
|
pathEl.Fill = shape.Fill
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path d="%s" style="fill:%s" />`,
|
output += pathEl.Render()
|
||||||
p, shape.Fill,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output += fmt.Sprintf(
|
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) {
|
func Class(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
output := ""
|
output := ""
|
||||||
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
|
||||||
fill: "%s",
|
fill: "#000",
|
||||||
stroke: "%s",
|
stroke: "#000",
|
||||||
strokeWidth: %d,
|
strokeWidth: %d,
|
||||||
%s
|
%s
|
||||||
});`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
});`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps)
|
||||||
paths, err := computeRoughPaths(r, js)
|
paths, err := computeRoughPaths(r, js)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
output += pathEl.Render()
|
||||||
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
box := geo.NewBox(
|
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)
|
headerBox := geo.NewBox(box.TopLeft, box.Width, 2*rowHeight)
|
||||||
|
|
||||||
js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
|
js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
|
||||||
fill: "%s",
|
fill: "#000",
|
||||||
%s
|
%s
|
||||||
});`, shape.Width, headerBox.Height, shape.Fill, baseRoughProps)
|
});`, shape.Width, headerBox.Height, baseRoughProps)
|
||||||
paths, err = computeRoughPaths(r, js)
|
paths, err = computeRoughPaths(r, js)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="class_header" transform="translate(%d %d)" d="%s" style="fill:%s" />`,
|
output += pathEl.Render()
|
||||||
shape.Pos.X, shape.Pos.Y, p, shape.Fill,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output += fmt.Sprintf(
|
output += fmt.Sprintf(
|
||||||
|
|
@ -379,17 +366,17 @@ func Class(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
float64(shape.LabelHeight),
|
float64(shape.LabelHeight),
|
||||||
)
|
)
|
||||||
|
|
||||||
output += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
textEl := svg_style.NewThemableElement("text")
|
||||||
"text-mono",
|
textEl.X = tl.X + float64(shape.LabelWidth)/2
|
||||||
tl.X+float64(shape.LabelWidth)/2,
|
textEl.Y = tl.Y + float64(shape.LabelHeight)*3/4
|
||||||
tl.Y+float64(shape.LabelHeight)*3/4,
|
textEl.Fill = shape.Stroke
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s",
|
textEl.Class = "text-mono"
|
||||||
"middle",
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
|
||||||
4+shape.FontSize,
|
"middle",
|
||||||
shape.Stroke,
|
4+shape.FontSize,
|
||||||
),
|
|
||||||
svg.EscapeText(shape.Label),
|
|
||||||
)
|
)
|
||||||
|
textEl.Content = svg.EscapeText(shape.Label)
|
||||||
|
output += textEl.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight)
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
pathEl = svg_style.NewThemableElement("path")
|
||||||
|
pathEl.Fill = shape.Fill
|
||||||
|
pathEl.Class = "class_header"
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
output += fmt.Sprintf(
|
pathEl.D = p
|
||||||
`<path class="class_header" d="%s" style="fill:%s" />`,
|
output += pathEl.Render()
|
||||||
p, shape.Fill,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range shape.Methods {
|
for _, m := range shape.Methods {
|
||||||
|
|
@ -436,28 +424,27 @@ func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText str
|
||||||
fontSize,
|
fontSize,
|
||||||
)
|
)
|
||||||
|
|
||||||
output += strings.Join([]string{
|
textEl := svg_style.NewThemableElement("text")
|
||||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.X = prefixTL.X
|
||||||
prefixTL.X,
|
textEl.Y = prefixTL.Y + fontSize*3/4
|
||||||
prefixTL.Y+fontSize*3/4,
|
textEl.Fill = shape.PrimaryAccentColor
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor),
|
textEl.Class = "text-mono"
|
||||||
prefix,
|
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>`,
|
textEl.X = prefixTL.X + d2target.PrefixWidth
|
||||||
prefixTL.X+d2target.PrefixWidth,
|
textEl.Fill = shape.Fill
|
||||||
prefixTL.Y+fontSize*3/4,
|
textEl.Content = svg.EscapeText(nameText)
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.Fill),
|
output += textEl.Render()
|
||||||
svg.EscapeText(nameText),
|
|
||||||
),
|
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
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,6 @@ func run(t *testing.T, tc testCase) {
|
||||||
|
|
||||||
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
|
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
|
||||||
Ruler: ruler,
|
Ruler: ruler,
|
||||||
ThemeID: 0,
|
|
||||||
Layout: d2dagrelayout.DefaultLayout,
|
Layout: d2dagrelayout.DefaultLayout,
|
||||||
FontFamily: go2.Pointer(d2fonts.HandDrawn),
|
FontFamily: go2.Pointer(d2fonts.HandDrawn),
|
||||||
})
|
})
|
||||||
|
|
@ -325,8 +324,9 @@ func run(t *testing.T, tc testCase) {
|
||||||
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
|
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
|
||||||
|
|
||||||
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
|
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||||
Pad: d2svg.DEFAULT_PADDING,
|
Pad: d2svg.DEFAULT_PADDING,
|
||||||
Sketch: true,
|
Sketch: true,
|
||||||
|
ThemeID: 0,
|
||||||
})
|
})
|
||||||
assert.Success(t, err)
|
assert.Success(t, err)
|
||||||
err = os.MkdirAll(dataPath, 0755)
|
err = os.MkdirAll(dataPath, 0755)
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,21 @@ package d2svg
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/svg"
|
"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 {
|
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" />`,
|
rectEl := svg_style.NewThemableElement("rect")
|
||||||
box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height, shape.Fill)
|
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 != "" {
|
if text != "" {
|
||||||
tl := label.InsideMiddleCenter.GetPointOnBox(
|
tl := label.InsideMiddleCenter.GetPointOnBox(
|
||||||
|
|
@ -23,17 +27,16 @@ func classHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, tex
|
||||||
textHeight,
|
textHeight,
|
||||||
)
|
)
|
||||||
|
|
||||||
str += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
textEl := svg_style.NewThemableElement("text")
|
||||||
"text-mono",
|
textEl.X = tl.X + textWidth/2
|
||||||
tl.X+textWidth/2,
|
textEl.Y = tl.Y + textHeight*3/4
|
||||||
tl.Y+textHeight*3/4,
|
textEl.Fill = shape.Stroke
|
||||||
fmt.Sprintf(`text-anchor:%s;font-size:%vpx;fill:%s`,
|
textEl.Class = "text-mono"
|
||||||
"middle",
|
textEl.Style = fmt.Sprintf(`text-anchor:%s;font-size:%vpx;`,
|
||||||
4+fontSize,
|
"middle", 4+fontSize,
|
||||||
shape.Stroke,
|
|
||||||
),
|
|
||||||
svg.EscapeText(text),
|
|
||||||
)
|
)
|
||||||
|
textEl.Content = svg.EscapeText(text)
|
||||||
|
str += textEl.Render()
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
@ -54,33 +57,39 @@ func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText str
|
||||||
fontSize,
|
fontSize,
|
||||||
)
|
)
|
||||||
|
|
||||||
return strings.Join([]string{
|
textEl := svg_style.NewThemableElement("text")
|
||||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.X = prefixTL.X
|
||||||
prefixTL.X,
|
textEl.Y = prefixTL.Y + fontSize*3/4
|
||||||
prefixTL.Y+fontSize*3/4,
|
textEl.Fill = shape.PrimaryAccentColor
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor),
|
textEl.Class = "text-mono"
|
||||||
prefix,
|
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>`,
|
textEl.X = prefixTL.X + d2target.PrefixWidth
|
||||||
prefixTL.X+d2target.PrefixWidth,
|
textEl.Fill = shape.Fill
|
||||||
prefixTL.Y+fontSize*3/4,
|
textEl.Content = svg.EscapeText(nameText)
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.Fill),
|
out += textEl.Render()
|
||||||
svg.EscapeText(nameText),
|
|
||||||
),
|
|
||||||
|
|
||||||
fmt.Sprintf(`<text class="text-mono" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.X = typeTR.X
|
||||||
typeTR.X,
|
textEl.Y = typeTR.Y + fontSize*3/4
|
||||||
typeTR.Y+fontSize*3/4,
|
textEl.Fill = shape.SecondaryAccentColor
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "end", fontSize, shape.SecondaryAccentColor),
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", fontSize)
|
||||||
svg.EscapeText(typeText),
|
textEl.Content = svg.EscapeText(typeText)
|
||||||
),
|
out += textEl.Render()
|
||||||
}, "\n")
|
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawClass(writer io.Writer, targetShape d2target.Shape) {
|
func drawClass(writer io.Writer, targetShape d2target.Shape) {
|
||||||
fmt.Fprintf(writer, `<rect class="shape" x="%d" y="%d" width="%d" height="%d" style="%s"/>`,
|
el := svg_style.NewThemableElement("rect")
|
||||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape))
|
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(
|
box := geo.NewBox(
|
||||||
geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)),
|
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
|
rowBox.TopLeft.Y += rowHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(writer, `<line x1="%f" y1="%f" x2="%f" y2="%f" style="%s" />`,
|
lineEl := svg_style.NewThemableElement("line")
|
||||||
rowBox.TopLeft.X, rowBox.TopLeft.Y,
|
lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y
|
||||||
rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y,
|
lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y
|
||||||
fmt.Sprintf("stroke-width:1;stroke:%v", targetShape.Fill))
|
lineEl.Stroke = targetShape.Fill
|
||||||
|
lineEl.Style = "stroke-width:1"
|
||||||
|
fmt.Fprint(writer, lineEl.Render())
|
||||||
|
|
||||||
for _, m := range targetShape.Methods {
|
for _, m := range targetShape.Methods {
|
||||||
fmt.Fprint(writer,
|
fmt.Fprint(writer,
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ import (
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2sketch"
|
"oss.terrastruct.com/d2/d2renderers/d2sketch"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
|
||||||
"oss.terrastruct.com/d2/lib/color"
|
"oss.terrastruct.com/d2/lib/color"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/shape"
|
"oss.terrastruct.com/d2/lib/shape"
|
||||||
"oss.terrastruct.com/d2/lib/svg"
|
"oss.terrastruct.com/d2/lib/svg"
|
||||||
|
svg_style "oss.terrastruct.com/d2/lib/svg/style"
|
||||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -68,21 +68,12 @@ type RenderOpts struct {
|
||||||
ThemeID int64
|
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()
|
tl, br := diagram.BoundingBox()
|
||||||
w := br.X - tl.X + pad*2
|
w := br.X - tl.X + pad*2
|
||||||
h := br.Y - tl.Y + pad*2
|
h := br.Y - tl.Y + pad*2
|
||||||
// TODO minify
|
|
||||||
|
|
||||||
// TODO background stuff. e.g. dotted, grid, colors
|
return w, h, tl, br
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func arrowheadMarkerID(isTarget bool, connection d2target.Connection) string {
|
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",
|
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
|
var path string
|
||||||
switch arrowhead {
|
switch arrowhead {
|
||||||
case d2target.ArrowArrowhead:
|
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 {
|
if isTarget {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
0., 0.,
|
0., 0.,
|
||||||
width, height/2,
|
width, height/2,
|
||||||
0., height,
|
0., height,
|
||||||
width/4, height/2,
|
width/4, height/2,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
0., height/2,
|
0., height/2,
|
||||||
width, 0.,
|
width, 0.,
|
||||||
width*3/4, height/2,
|
width*3/4, height/2,
|
||||||
width, height,
|
width, height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
path = polygonEl.Render()
|
||||||
case d2target.TriangleArrowhead:
|
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 {
|
if isTarget {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
0., 0.,
|
0., 0.,
|
||||||
width, height/2.0,
|
width, height/2.0,
|
||||||
0., height,
|
0., height,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
width, 0.,
|
width, 0.,
|
||||||
0., height/2.0,
|
0., height/2.0,
|
||||||
width, height,
|
width, height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
path = polygonEl.Render()
|
||||||
case d2target.LineArrowhead:
|
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 {
|
if isTarget {
|
||||||
path = fmt.Sprintf(`<polyline %s points="%f,%f %f,%f %f,%f"/>`,
|
polylineEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
strokeWidth/2, strokeWidth/2,
|
strokeWidth/2, strokeWidth/2,
|
||||||
width-strokeWidth/2, height/2,
|
width-strokeWidth/2, height/2,
|
||||||
strokeWidth/2, height-strokeWidth/2,
|
strokeWidth/2, height-strokeWidth/2,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
path = fmt.Sprintf(`<polyline %s points="%f,%f %f,%f %f,%f"/>`,
|
polylineEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
width-strokeWidth/2, strokeWidth/2,
|
width-strokeWidth/2, strokeWidth/2,
|
||||||
strokeWidth/2, height/2,
|
strokeWidth/2, height/2,
|
||||||
width-strokeWidth/2, height-strokeWidth/2,
|
width-strokeWidth/2, height-strokeWidth/2,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
path = polylineEl.Render()
|
||||||
case d2target.FilledDiamondArrowhead:
|
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 {
|
if isTarget {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
0., height/2.0,
|
0., height/2.0,
|
||||||
width/2.0, 0.,
|
width/2.0, 0.,
|
||||||
width, height/2.0,
|
width, height/2.0,
|
||||||
width/2.0, height,
|
width/2.0, height,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
0., height/2.0,
|
0., height/2.0,
|
||||||
width/2.0, 0.,
|
width/2.0, 0.,
|
||||||
width, height/2.0,
|
width, height/2.0,
|
||||||
width/2.0, height,
|
width/2.0, height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
path = polygonEl.Render()
|
||||||
case d2target.DiamondArrowhead:
|
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 {
|
if isTarget {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
0., height/2.0,
|
0., height/2.0,
|
||||||
width/2, height/8,
|
width/2, height/8,
|
||||||
width, height/2.0,
|
width, height/2.0,
|
||||||
width/2.0, height*0.9,
|
width/2.0, height*0.9,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
path = fmt.Sprintf(`<polygon %s points="%f,%f %f,%f %f,%f %f,%f" />`,
|
polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f",
|
||||||
attrs,
|
|
||||||
width/8, height/2.0,
|
width/8, height/2.0,
|
||||||
width*0.6, height/8,
|
width*0.6, height/8,
|
||||||
width*1.1, height/2.0,
|
width*1.1, height/2.0,
|
||||||
width*0.6, height*7/8,
|
width*0.6, height*7/8,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
path = polygonEl.Render()
|
||||||
case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
|
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)
|
offset := 4.0 + float64(connection.StrokeWidth*2)
|
||||||
var modifier string
|
|
||||||
|
var modifierEl *svg_style.ThemableElement
|
||||||
if arrowhead == d2target.CfOneRequired || arrowhead == d2target.CfManyRequired {
|
if arrowhead == d2target.CfOneRequired || arrowhead == d2target.CfManyRequired {
|
||||||
modifier = fmt.Sprintf(`<path %s d="M%f,%f %f,%f"/>`,
|
modifierEl := svg_style.NewThemableElement("path")
|
||||||
attrs,
|
modifierEl.D = fmt.Sprintf("M%f,%f %f,%f",
|
||||||
offset, 0.,
|
offset, 0.,
|
||||||
offset, height,
|
offset, height,
|
||||||
)
|
)
|
||||||
|
modifierEl.Fill = bgColor
|
||||||
|
modifierEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||||
|
modifierEl.Class = "connection"
|
||||||
|
modifierEl.Style = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||||
} else {
|
} else {
|
||||||
modifier = fmt.Sprintf(`<circle %s cx="%f" cy="%f" r="%f"/>`,
|
modifierEl := svg_style.NewThemableElement("circle")
|
||||||
attrs,
|
modifierEl.Cx = offset/2.0 + 1.0
|
||||||
offset/2.0+1.0, height/2.0,
|
modifierEl.Cy = height / 2.0
|
||||||
offset/2.0,
|
modifierEl.R = offset / 2.0
|
||||||
)
|
modifierEl.Fill = bgColor
|
||||||
}
|
modifierEl.Stroke = svg_style.ConnectionTheme(connection)
|
||||||
if !isTarget {
|
modifierEl.Class = "connection"
|
||||||
attrs = fmt.Sprintf(`%s transform="scale(-1) translate(-%f, -%f)"`, attrs, width, height)
|
modifierEl.Style = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
childPathEl := svg_style.NewThemableElement("path")
|
||||||
if arrowhead == d2target.CfMany || arrowhead == d2target.CfManyRequired {
|
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>`,
|
childPathEl.D = fmt.Sprintf("M%f,%f %f,%f M%f,%f %f,%f M%f,%f %f,%f",
|
||||||
attrs, modifier,
|
|
||||||
width-3.0, height/2.0,
|
width-3.0, height/2.0,
|
||||||
width+offset, height/2.0,
|
width+offset, height/2.0,
|
||||||
offset+2.0, 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,
|
width+offset, height,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
path = fmt.Sprintf(`<g %s>%s<path d="M%f,%f %f,%f M%f,%f %f,%f"/></g>`,
|
childPathEl.D = fmt.Sprintf("M%f,%f %f,%f M%f,%f %f,%f",
|
||||||
attrs, modifier,
|
|
||||||
width-3.0, height/2.0,
|
width-3.0, height/2.0,
|
||||||
width+offset, height/2.0,
|
width+offset, height/2.0,
|
||||||
offset*1.8, 0.,
|
offset*1.8, 0.,
|
||||||
offset*1.8, height,
|
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:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -462,10 +482,16 @@ func drawConnection(writer io.Writer, bgColor string, fgColor string, labelMaskI
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, out)
|
fmt.Fprint(writer, out)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(writer, `<path d="%s" class="connection" style="fill:none;%s" %s/>`,
|
pathEl := svg_style.NewThemableElement("path")
|
||||||
path, connectionStyle(connection), attrs)
|
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 != "" {
|
if connection.Label != "" {
|
||||||
|
|
@ -475,24 +501,27 @@ func drawConnection(writer io.Writer, bgColor string, fgColor string, labelMaskI
|
||||||
} else if connection.Italic {
|
} else if connection.Italic {
|
||||||
fontClass += "-italic"
|
fontClass += "-italic"
|
||||||
}
|
}
|
||||||
fontColor := "black"
|
fontColor := color.N1
|
||||||
if connection.Color != "" {
|
if connection.Color != color.Empty {
|
||||||
fontColor = connection.Color
|
fontColor = connection.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
if connection.Fill != "" {
|
if connection.Fill != color.Empty {
|
||||||
fmt.Fprintf(writer, `<rect x="%f" y="%f" width="%d" height="%d" style="fill:%s" />`,
|
rectEl := svg_style.NewThemableElement("rect")
|
||||||
labelTL.X, labelTL.Y, connection.LabelWidth, connection.LabelHeight, connection.Fill)
|
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
|
textEl := svg_style.NewThemableElement("text")
|
||||||
y := labelTL.Y + float64(connection.FontSize)
|
textEl.X = labelTL.X + float64(connection.LabelWidth)/2
|
||||||
fmt.Fprintf(writer, `<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.Y = labelTL.Y + float64(connection.FontSize)
|
||||||
fontClass,
|
textEl.Fill = fontColor
|
||||||
x, y,
|
textEl.Class = fontClass
|
||||||
textStyle,
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "middle", connection.FontSize)
|
||||||
RenderText(connection.Label, x, float64(connection.LabelHeight)),
|
textEl.Content = RenderText(connection.Label, textEl.X, float64(connection.LabelHeight))
|
||||||
)
|
fmt.Fprint(writer, textEl.Render())
|
||||||
}
|
}
|
||||||
|
|
||||||
length := geo.Route(connection.Route).Length()
|
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 {
|
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)
|
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)
|
textEl := svg_style.NewThemableElement("text")
|
||||||
x := labelTL.X + width/2
|
textEl.X = labelTL.X + width/2
|
||||||
y := labelTL.Y + float64(connection.FontSize)
|
textEl.Y = labelTL.Y + float64(connection.FontSize)
|
||||||
return fmt.Sprintf(`<text class="text-italic" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.Fill = fgColor
|
||||||
x, y,
|
textEl.Class = "text-italic"
|
||||||
textStyle,
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "middle", connection.FontSize)
|
||||||
RenderText(text, x, height),
|
textEl.Content = RenderText(text, textEl.X, height)
|
||||||
)
|
return textEl.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderOval(tl *geo.Point, width, height float64, style string) string {
|
func renderOval(tl *geo.Point, width, height float64, fill, stroke, style string) string {
|
||||||
rx := width / 2
|
el := svg_style.NewThemableElement("ellipse")
|
||||||
ry := height / 2
|
el.Rx = width / 2
|
||||||
cx := tl.X + rx
|
el.Ry = height / 2
|
||||||
cy := tl.Y + ry
|
el.Cx = tl.X + el.Rx
|
||||||
return fmt.Sprintf(`<ellipse class="shape" cx="%f" cy="%f" rx="%f" ry="%f" style="%s" />`, cx, cy, rx, ry, style)
|
el.Cy = tl.Y + el.Ry
|
||||||
|
el.Class = "shape"
|
||||||
|
el.Style = style
|
||||||
|
return el.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func defineShadowFilter(writer io.Writer) {
|
func defineShadowFilter(writer io.Writer) {
|
||||||
|
|
@ -583,11 +615,13 @@ func render3dRect(targetShape d2target.Shape) string {
|
||||||
borderSegments = append(borderSegments,
|
borderSegments = append(borderSegments,
|
||||||
lineTo(d2target.Point{X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset}),
|
lineTo(d2target.Point{X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset}),
|
||||||
)
|
)
|
||||||
border := targetShape
|
border := svg_style.NewThemableElement("path")
|
||||||
border.Fill = "none"
|
border.D = strings.Join(borderSegments, " ")
|
||||||
borderStyle := shapeStyle(border)
|
_, borderStroke := svg_style.ShapeTheme(targetShape)
|
||||||
renderedBorder := fmt.Sprintf(`<path d="%s" style="%s"/>`,
|
border.Stroke = borderStroke
|
||||||
strings.Join(borderSegments, " "), borderStyle)
|
borderStyle := svg_style.ShapeStyle(targetShape)
|
||||||
|
border.Style = borderStyle
|
||||||
|
renderedBorder := border.Render()
|
||||||
|
|
||||||
// create mask from border stroke, to cut away from the shape fills
|
// create mask from border stroke, to cut away from the shape fills
|
||||||
maskID := fmt.Sprintf("border-mask-%v", svg.EscapeText(targetShape.ID))
|
maskID := fmt.Sprintf("border-mask-%v", svg.EscapeText(targetShape.ID))
|
||||||
|
|
@ -603,11 +637,16 @@ func render3dRect(targetShape d2target.Shape) string {
|
||||||
}, "\n")
|
}, "\n")
|
||||||
|
|
||||||
// render the main rectangle without stroke and the border mask
|
// render the main rectangle without stroke and the border mask
|
||||||
mainShape := targetShape
|
mainShape := svg_style.NewThemableElement("rect")
|
||||||
mainShape.Stroke = "none"
|
mainShape.X = float64(targetShape.Pos.X)
|
||||||
mainRect := fmt.Sprintf(`<rect x="%d" y="%d" width="%d" height="%d" style="%s" mask="url(#%s)"/>`,
|
mainShape.Y = float64(targetShape.Pos.Y)
|
||||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(mainShape), maskID,
|
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
|
// render the side shapes in the darkened color without stroke and the border mask
|
||||||
var sidePoints []string
|
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),
|
fmt.Sprintf("%d,%d", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
darkerColor, err := color.Darken(targetShape.Fill)
|
// TODO make darker color part of the theme?
|
||||||
if err != nil {
|
darkerColor := targetShape.Fill
|
||||||
darkerColor = targetShape.Fill
|
// darkerColor, err := color.Darken(targetShape.Fill)
|
||||||
}
|
// if err != nil {
|
||||||
sideShape := targetShape
|
// darkerColor = targetShape.Fill
|
||||||
|
// }
|
||||||
|
sideShape := svg_style.NewThemableElement("polygon")
|
||||||
sideShape.Fill = darkerColor
|
sideShape.Fill = darkerColor
|
||||||
sideShape.Stroke = "none"
|
sideShape.Points = strings.Join(sidePoints, " ")
|
||||||
renderedSides := fmt.Sprintf(`<polygon points="%s" style="%s" mask="url(#%s)"/>`,
|
sideShape.Mask = fmt.Sprintf("url(#%s)", maskID)
|
||||||
strings.Join(sidePoints, " "), shapeStyle(sideShape), 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) {
|
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))
|
tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y))
|
||||||
width := float64(targetShape.Width)
|
width := float64(targetShape.Width)
|
||||||
height := float64(targetShape.Height)
|
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]
|
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[targetShape.Type]
|
||||||
|
|
||||||
s := shape.NewShape(shapeType, geo.NewBox(tl, width, height))
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, out)
|
fmt.Fprint(writer, out)
|
||||||
} else {
|
} else {
|
||||||
drawClass(writer, targetShape)
|
drawClass(writer, targetShape)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, `</g>`)
|
fmt.Fprintf(writer, `</g>`)
|
||||||
fmt.Fprintf(writer, closingTag)
|
fmt.Fprint(writer, closingTag)
|
||||||
return labelMask, nil
|
return labelMask, nil
|
||||||
case d2target.ShapeSQLTable:
|
case d2target.ShapeSQLTable:
|
||||||
if sketchRunner != nil {
|
if sketchRunner != nil {
|
||||||
|
|
@ -695,31 +738,38 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, out)
|
fmt.Fprint(writer, out)
|
||||||
} else {
|
} else {
|
||||||
drawTable(writer, targetShape)
|
drawTable(writer, targetShape)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, `</g>`)
|
fmt.Fprintf(writer, `</g>`)
|
||||||
fmt.Fprintf(writer, closingTag)
|
fmt.Fprint(writer, closingTag)
|
||||||
return labelMask, nil
|
return labelMask, nil
|
||||||
case d2target.ShapeOval:
|
case d2target.ShapeOval:
|
||||||
if targetShape.Multiple {
|
if targetShape.Multiple {
|
||||||
fmt.Fprint(writer, renderOval(multipleTL, width, height, style))
|
fmt.Fprint(writer, renderOval(multipleTL, width, height, fill, stroke, style))
|
||||||
}
|
}
|
||||||
if sketchRunner != nil {
|
if sketchRunner != nil {
|
||||||
out, err := d2sketch.Oval(sketchRunner, targetShape)
|
out, err := d2sketch.Oval(sketchRunner, targetShape)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, out)
|
fmt.Fprint(writer, out)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(writer, renderOval(tl, width, height, style))
|
fmt.Fprint(writer, renderOval(tl, width, height, fill, stroke, style))
|
||||||
}
|
}
|
||||||
|
|
||||||
case d2target.ShapeImage:
|
case d2target.ShapeImage:
|
||||||
fmt.Fprintf(writer, `<image href="%s" x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
el := svg_style.NewThemableElement("image")
|
||||||
html.EscapeString(targetShape.Icon.String()),
|
el.X = float64(targetShape.Pos.X)
|
||||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
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
|
// TODO should standardize "" to rectangle
|
||||||
case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, "":
|
case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, "":
|
||||||
|
|
@ -727,26 +777,45 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
||||||
fmt.Fprint(writer, render3dRect(targetShape))
|
fmt.Fprint(writer, render3dRect(targetShape))
|
||||||
} else {
|
} else {
|
||||||
if targetShape.Multiple {
|
if targetShape.Multiple {
|
||||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
el := svg_style.NewThemableElement("rect")
|
||||||
targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
|
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 {
|
if sketchRunner != nil {
|
||||||
out, err := d2sketch.Rect(sketchRunner, targetShape)
|
out, err := d2sketch.Rect(sketchRunner, targetShape)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, out)
|
fmt.Fprint(writer, out)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
el := svg_style.NewThemableElement("rect")
|
||||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
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:
|
case d2target.ShapeText, d2target.ShapeCode:
|
||||||
default:
|
default:
|
||||||
if targetShape.Multiple {
|
if targetShape.Multiple {
|
||||||
multiplePathData := shape.NewShape(shapeType, geo.NewBox(multipleTL, width, height)).GetSVGPathData()
|
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 {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, out)
|
fmt.Fprint(writer, out)
|
||||||
} else {
|
} else {
|
||||||
|
el := svg_style.NewThemableElement("path")
|
||||||
|
el.Fill = fill
|
||||||
|
el.Stroke = stroke
|
||||||
|
el.Style = style
|
||||||
for _, pathData := range s.GetSVGPathData() {
|
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)
|
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, `<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" />`,
|
rectEl := svg_style.NewThemableElement("rect")
|
||||||
targetShape.Width, targetShape.Height, containerStyle)
|
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
|
// Padding
|
||||||
fmt.Fprintf(writer, `<g transform="translate(6 6)">`)
|
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
|
// we need the self closing form in this svg/xhtml context
|
||||||
render = strings.ReplaceAll(render, "<hr>", "<hr />")
|
render = strings.ReplaceAll(render, "<hr>", "<hr />")
|
||||||
|
|
||||||
var mdStyle string
|
mdEl := svg_style.NewThemableElement("div")
|
||||||
if targetShape.Fill != "" {
|
mdEl.Xmlns = "http://www.w3.org/1999/xhtml"
|
||||||
mdStyle = fmt.Sprintf("background-color:%s;", targetShape.Fill)
|
mdEl.Class = "md"
|
||||||
|
mdEl.Content = render
|
||||||
|
if targetShape.Fill != color.Empty {
|
||||||
|
mdEl.BackgroundColor = targetShape.Fill
|
||||||
}
|
}
|
||||||
if targetShape.Stroke != "" {
|
if targetShape.Stroke != color.Empty {
|
||||||
mdStyle += fmt.Sprintf("color:%s;", targetShape.Stroke)
|
mdEl.Color = targetShape.Stroke
|
||||||
}
|
}
|
||||||
|
fmt.Fprint(writer, mdEl.Render())
|
||||||
fmt.Fprintf(writer, `<div xmlns="http://www.w3.org/1999/xhtml" class="md" style="%s">%v</div>`, mdStyle, render)
|
|
||||||
fmt.Fprint(writer, `</foreignObject></g>`)
|
fmt.Fprint(writer, `</foreignObject></g>`)
|
||||||
} else {
|
} else {
|
||||||
fontColor := "black"
|
fontColor := color.N1
|
||||||
if targetShape.Color != "" {
|
if targetShape.Color != color.Empty {
|
||||||
fontColor = targetShape.Color
|
fontColor = targetShape.Color
|
||||||
}
|
}
|
||||||
textStyle := fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "middle", targetShape.FontSize, fontColor)
|
textEl := svg_style.NewThemableElement("text")
|
||||||
x := labelTL.X + float64(targetShape.LabelWidth)/2.
|
textEl.X = labelTL.X + float64(targetShape.LabelWidth)/2
|
||||||
// text is vertically positioned at its baseline which is at labelTL+FontSize
|
// text is vertically positioned at its baseline which is at labelTL+FontSize
|
||||||
y := labelTL.Y + float64(targetShape.FontSize)
|
textEl.Y = labelTL.Y + float64(targetShape.FontSize)
|
||||||
fmt.Fprintf(writer, `<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.Fill = fontColor
|
||||||
fontClass,
|
textEl.Class = fontClass
|
||||||
x, y,
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "middle", targetShape.FontSize)
|
||||||
textStyle,
|
textEl.Content = RenderText(targetShape.Label, textEl.X, float64(targetShape.LabelHeight))
|
||||||
RenderText(targetShape.Label, x, float64(targetShape.LabelHeight)),
|
fmt.Fprint(writer, textEl.Render())
|
||||||
)
|
|
||||||
if targetShape.Blend {
|
if targetShape.Blend {
|
||||||
labelMask = makeLabelMask(labelTL, targetShape.LabelWidth, targetShape.LabelHeight-d2graph.INNER_LABEL_PADDING)
|
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
|
return labelMask, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -942,46 +1020,9 @@ func RenderText(text string, x, height float64) string {
|
||||||
return strings.Join(rendered, "")
|
return strings.Join(rendered, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func shapeStyle(shape d2target.Shape) string {
|
func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) 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) {
|
|
||||||
content := buf.String()
|
content := buf.String()
|
||||||
buf.WriteString(`<style type="text/css"><![CDATA[`)
|
out := `<style type="text/css"><![CDATA[`
|
||||||
|
|
||||||
triggers := []string{
|
triggers := []string{
|
||||||
`class="text"`,
|
`class="text"`,
|
||||||
|
|
@ -991,7 +1032,7 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||||
|
|
||||||
for _, t := range triggers {
|
for _, t := range triggers {
|
||||||
if strings.Contains(content, t) {
|
if strings.Contains(content, t) {
|
||||||
fmt.Fprintf(buf, `
|
out += fmt.Sprintf(`
|
||||||
.text {
|
.text {
|
||||||
font-family: "font-regular";
|
font-family: "font-regular";
|
||||||
}
|
}
|
||||||
|
|
@ -1010,10 +1051,10 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||||
|
|
||||||
for _, t := range triggers {
|
for _, t := range triggers {
|
||||||
if strings.Contains(content, t) {
|
if strings.Contains(content, t) {
|
||||||
buf.WriteString(`
|
out += `
|
||||||
.text-underline {
|
.text-underline {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}`)
|
}`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1024,23 +1065,23 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||||
|
|
||||||
for _, t := range triggers {
|
for _, t := range triggers {
|
||||||
if strings.Contains(content, t) {
|
if strings.Contains(content, t) {
|
||||||
buf.WriteString(`
|
out += `
|
||||||
.appendix-icon {
|
.appendix-icon {
|
||||||
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
|
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
|
||||||
}`)
|
}`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
triggers = []string{
|
triggers = []string{
|
||||||
`class="text-bold"`,
|
`class="text-bold`,
|
||||||
`<b>`,
|
`<b>`,
|
||||||
`<strong>`,
|
`<strong>`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range triggers {
|
for _, t := range triggers {
|
||||||
if strings.Contains(content, t) {
|
if strings.Contains(content, t) {
|
||||||
fmt.Fprintf(buf, `
|
out += fmt.Sprintf(`
|
||||||
.text-bold {
|
.text-bold {
|
||||||
font-family: "font-bold";
|
font-family: "font-bold";
|
||||||
}
|
}
|
||||||
|
|
@ -1054,14 +1095,14 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||||
}
|
}
|
||||||
|
|
||||||
triggers = []string{
|
triggers = []string{
|
||||||
`class="text-italic"`,
|
`class="text-italic`,
|
||||||
`<em>`,
|
`<em>`,
|
||||||
`<dfn>`,
|
`<dfn>`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range triggers {
|
for _, t := range triggers {
|
||||||
if strings.Contains(content, t) {
|
if strings.Contains(content, t) {
|
||||||
fmt.Fprintf(buf, `
|
out += fmt.Sprintf(`
|
||||||
.text-italic {
|
.text-italic {
|
||||||
font-family: "font-italic";
|
font-family: "font-italic";
|
||||||
}
|
}
|
||||||
|
|
@ -1075,7 +1116,7 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||||
}
|
}
|
||||||
|
|
||||||
triggers = []string{
|
triggers = []string{
|
||||||
`class="text-mono"`,
|
`class="text-mono`,
|
||||||
`<pre>`,
|
`<pre>`,
|
||||||
`<code>`,
|
`<code>`,
|
||||||
`<kbd>`,
|
`<kbd>`,
|
||||||
|
|
@ -1084,7 +1125,7 @@ func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||||
|
|
||||||
for _, t := range triggers {
|
for _, t := range triggers {
|
||||||
if strings.Contains(content, t) {
|
if strings.Contains(content, t) {
|
||||||
fmt.Fprintf(buf, `
|
out += fmt.Sprintf(`
|
||||||
.text-mono {
|
.text-mono {
|
||||||
font-family: "font-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
|
//go:embed fitToScreen.js
|
||||||
var fitToScreenScript string
|
var fitToScreenScript string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BG_COLOR = color.N7
|
||||||
|
FG_COLOR = color.N1
|
||||||
|
)
|
||||||
|
|
||||||
// TODO minify output at end
|
// TODO minify output at end
|
||||||
func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
||||||
var sketchRunner *d2sketch.Runner
|
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{}
|
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
|
// only define shadow filter if a shape uses it
|
||||||
for _, s := range diagram.Shapes {
|
for _, s := range diagram.Shapes {
|
||||||
|
|
@ -1184,7 +1202,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
||||||
markers := map[string]struct{}{}
|
markers := map[string]struct{}{}
|
||||||
for _, obj := range allObjects {
|
for _, obj := range allObjects {
|
||||||
if c, is := obj.(d2target.Connection); is {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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.Fprint(buf, strings.Join([]string{
|
||||||
fmt.Sprintf(`<mask id="%s" maskUnits="userSpaceOnUse" x="%d" y="%d" width="%d" height="%d">`,
|
fmt.Sprintf(`<mask id="%s" maskUnits="userSpaceOnUse" x="%d" y="%d" width="%d" height="%d">`,
|
||||||
labelMaskID, -pad, -pad, w, h,
|
labelMaskID, -pad, -pad, w, h,
|
||||||
|
|
@ -1215,10 +1234,48 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
||||||
`</mask>`,
|
`</mask>`,
|
||||||
}, "\n"))
|
}, "\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>`)
|
// generate elements that will be appended to the SVG tag
|
||||||
return buf.Bytes(), nil
|
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 {
|
type DiagramObject interface {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,456 @@
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
}
|
}
|
||||||
.blend {
|
.blend {
|
||||||
mix-blend-mode: multiply;
|
mix-Blend-mode: multiply;
|
||||||
opacity: 0.5;
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/svg"
|
"oss.terrastruct.com/d2/lib/svg"
|
||||||
|
svg_style "oss.terrastruct.com/d2/lib/svg/style"
|
||||||
"oss.terrastruct.com/util-go/go2"
|
"oss.terrastruct.com/util-go/go2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tableHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string {
|
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" />`,
|
rectEl := svg_style.NewThemableElement("rect")
|
||||||
box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height, shape.Fill)
|
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 != "" {
|
if text != "" {
|
||||||
tl := label.InsideMiddleLeft.GetPointOnBox(
|
tl := label.InsideMiddleLeft.GetPointOnBox(
|
||||||
|
|
@ -24,17 +28,16 @@ func tableHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, tex
|
||||||
textHeight,
|
textHeight,
|
||||||
)
|
)
|
||||||
|
|
||||||
str += fmt.Sprintf(`<text class="%s" x="%f" y="%f" style="%s">%s</text>`,
|
textEl := svg_style.NewThemableElement("text")
|
||||||
"text",
|
textEl.X = tl.X
|
||||||
tl.X,
|
textEl.Y = tl.Y + textHeight*3/4
|
||||||
tl.Y+textHeight*3/4,
|
textEl.Fill = shape.Stroke
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s",
|
textEl.Class = "text"
|
||||||
"start",
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
|
||||||
4+fontSize,
|
"start", 4+fontSize,
|
||||||
shape.Stroke,
|
|
||||||
),
|
|
||||||
svg.EscapeText(text),
|
|
||||||
)
|
)
|
||||||
|
textEl.Content = svg.EscapeText(text)
|
||||||
|
str += textEl.Render()
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
@ -55,33 +58,40 @@ func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraint
|
||||||
fontSize,
|
fontSize,
|
||||||
)
|
)
|
||||||
|
|
||||||
return strings.Join([]string{
|
textEl := svg_style.NewThemableElement("text")
|
||||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.X = nameTL.X
|
||||||
nameTL.X,
|
textEl.Y = nameTL.Y + fontSize*3/4
|
||||||
nameTL.Y+fontSize*3/4,
|
textEl.Fill = shape.PrimaryAccentColor
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor),
|
textEl.Class = "text"
|
||||||
svg.EscapeText(nameText),
|
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>`,
|
textEl.X = nameTL.X + longestNameWidth + 2*d2target.NamePadding
|
||||||
nameTL.X+longestNameWidth+2*d2target.NamePadding,
|
textEl.Fill = shape.NeutralAccentColor
|
||||||
nameTL.Y+fontSize*3/4,
|
textEl.Content = svg.EscapeText(typeText)
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.NeutralAccentColor),
|
out += textEl.Render()
|
||||||
svg.EscapeText(typeText),
|
|
||||||
),
|
|
||||||
|
|
||||||
fmt.Sprintf(`<text class="text" x="%f" y="%f" style="%s">%s</text>`,
|
textEl.X = constraintTR.X
|
||||||
constraintTR.X,
|
textEl.Y = constraintTR.Y + fontSize*3/4
|
||||||
constraintTR.Y+fontSize*3/4,
|
textEl.Fill = shape.SecondaryAccentColor
|
||||||
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;letter-spacing:2px;", "end", fontSize, shape.SecondaryAccentColor),
|
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx;letter-spacing:2px", "end", fontSize)
|
||||||
constraintText,
|
textEl.Content = constraintText
|
||||||
),
|
out += textEl.Render()
|
||||||
}, "\n")
|
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawTable(writer io.Writer, targetShape d2target.Shape) {
|
func drawTable(writer io.Writer, targetShape d2target.Shape) {
|
||||||
fmt.Fprintf(writer, `<rect class="shape" x="%d" y="%d" width="%d" height="%d" style="%s"/>`,
|
rectEl := svg_style.NewThemableElement("rect")
|
||||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape))
|
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(
|
box := geo.NewBox(
|
||||||
geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)),
|
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)),
|
tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth)),
|
||||||
)
|
)
|
||||||
rowBox.TopLeft.Y += rowHeight
|
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,
|
lineEl := svg_style.NewThemableElement("line")
|
||||||
rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y,
|
lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y
|
||||||
targetShape.Fill,
|
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/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
"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/geo"
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/shape"
|
"oss.terrastruct.com/d2/lib/shape"
|
||||||
|
|
@ -416,11 +416,11 @@ func NewTextDimensions(w, h int) *TextDimensions {
|
||||||
return &TextDimensions{Width: w, Height: h}
|
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 {
|
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{
|
var DSL_SHAPE_TO_SHAPE_TYPE = map[string]string{
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,34 @@ func Darken(colorString string) (string, error) {
|
||||||
// decrease luminance by 10%
|
// decrease luminance by 10%
|
||||||
return colorful.Hsl(h, s, l-.1).Clamped().Hex(), nil
|
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
|
layout := plugin.Layout
|
||||||
opts := &d2lib.CompileOptions{
|
opts := &d2lib.CompileOptions{
|
||||||
Layout: layout,
|
Layout: layout,
|
||||||
Ruler: ruler,
|
Ruler: ruler,
|
||||||
ThemeID: themeID,
|
|
||||||
}
|
}
|
||||||
if sketch {
|
if sketch {
|
||||||
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
|
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue