1 package d2svg
2
3 import (
4 "fmt"
5 "html"
6 "io"
7
8 "oss.terrastruct.com/d2/d2target"
9 "oss.terrastruct.com/d2/d2themes"
10 "oss.terrastruct.com/d2/lib/geo"
11 "oss.terrastruct.com/d2/lib/label"
12 "oss.terrastruct.com/d2/lib/svg"
13 "oss.terrastruct.com/util-go/go2"
14 )
15
16
17 func clipPathForBorderRadius(diagramHash string, shape d2target.Shape) string {
18 box := geo.NewBox(
19 geo.NewPoint(float64(shape.Pos.X), float64(shape.Pos.Y)),
20 float64(shape.Width),
21 float64(shape.Height),
22 )
23 topX, topY := box.TopLeft.X+box.Width, box.TopLeft.Y
24
25 out := fmt.Sprintf(`<clipPath id="%v-%v">`, diagramHash, shape.ID)
26 out += fmt.Sprintf(`<path d="M %f %f L %f %f S %f %f %f %f `, box.TopLeft.X, box.TopLeft.Y+float64(shape.BorderRadius), box.TopLeft.X, box.TopLeft.Y+float64(shape.BorderRadius), box.TopLeft.X, box.TopLeft.Y, box.TopLeft.X+float64(shape.BorderRadius), box.TopLeft.Y)
27 out += fmt.Sprintf(`L %f %f L %f %f `, box.TopLeft.X+box.Width-float64(shape.BorderRadius), box.TopLeft.Y, topX-float64(shape.BorderRadius), topY)
28
29 out += fmt.Sprintf(`S %f %f %f %f `, topX, topY, topX, topY+float64(shape.BorderRadius))
30 out += fmt.Sprintf(`L %f %f `, topX, topY+box.Height-float64(shape.BorderRadius))
31
32 if len(shape.Columns) != 0 {
33 out += fmt.Sprintf(`L %f %f L %f %f`, topX, topY+box.Height, box.TopLeft.X, box.TopLeft.Y+box.Height)
34 } else {
35 out += fmt.Sprintf(`S %f % f %f %f `, topX, topY+box.Height, topX-float64(shape.BorderRadius), topY+box.Height)
36 out += fmt.Sprintf(`L %f %f `, box.TopLeft.X+float64(shape.BorderRadius), box.TopLeft.Y+box.Height)
37 out += fmt.Sprintf(`S %f %f %f %f`, box.TopLeft.X, box.TopLeft.Y+box.Height, box.TopLeft.X, box.TopLeft.Y+box.Height-float64(shape.BorderRadius))
38 out += fmt.Sprintf(`L %f %f`, box.TopLeft.X, box.TopLeft.Y+float64(shape.BorderRadius))
39 }
40 out += fmt.Sprintf(`Z %f %f" `, box.TopLeft.X, box.TopLeft.Y)
41 return out + `fill="none" /> </clipPath>`
42 }
43
44 func tableHeader(diagramHash string, shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string {
45 rectEl := d2themes.NewThemableElement("rect")
46 rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y
47 rectEl.Width, rectEl.Height = box.Width, box.Height
48 rectEl.Fill = shape.Fill
49 rectEl.FillPattern = shape.FillPattern
50 rectEl.ClassName = "class_header"
51 if shape.BorderRadius != 0 {
52 rectEl.ClipPath = fmt.Sprintf("%v-%v", diagramHash, shape.ID)
53 }
54 str := rectEl.Render()
55
56 if text != "" {
57 tl := label.InsideMiddleLeft.GetPointOnBox(
58 box,
59 float64(d2target.HeaderPadding),
60 float64(shape.Width),
61 textHeight,
62 )
63
64 textEl := d2themes.NewThemableElement("text")
65 textEl.X = tl.X
66 textEl.Y = tl.Y + textHeight*3/4
67 textEl.Fill = shape.GetFontColor()
68 textEl.ClassName = "text"
69 textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
70 "start", 4+fontSize,
71 )
72 textEl.Content = svg.EscapeText(text)
73 str += textEl.Render()
74 }
75 return str
76 }
77
78 func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraintText string, fontSize, longestNameWidth, longestTypeWidth float64) string {
79
80
81 nameTL := label.InsideMiddleLeft.GetPointOnBox(
82 box,
83 d2target.NamePadding,
84 0,
85 fontSize,
86 )
87
88 textEl := d2themes.NewThemableElement("text")
89 textEl.X = nameTL.X
90 textEl.Y = nameTL.Y + fontSize*3/4
91 textEl.Fill = shape.PrimaryAccentColor
92 textEl.ClassName = "text"
93 textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", fontSize)
94 textEl.Content = svg.EscapeText(nameText)
95 out := textEl.Render()
96
97 textEl.X += longestNameWidth + d2target.TypePadding
98 textEl.Fill = shape.NeutralAccentColor
99 textEl.Content = svg.EscapeText(typeText)
100 out += textEl.Render()
101
102 textEl.X = box.TopLeft.X + (box.Width - d2target.NamePadding)
103 textEl.Fill = shape.SecondaryAccentColor
104 textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", fontSize)
105 textEl.Content = constraintText
106 out += textEl.Render()
107
108 return out
109 }
110
111 func drawTable(writer io.Writer, diagramHash string, targetShape d2target.Shape) {
112 rectEl := d2themes.NewThemableElement("rect")
113 rectEl.X = float64(targetShape.Pos.X)
114 rectEl.Y = float64(targetShape.Pos.Y)
115 rectEl.Width = float64(targetShape.Width)
116 rectEl.Height = float64(targetShape.Height)
117 rectEl.Fill, rectEl.Stroke = d2themes.ShapeTheme(targetShape)
118 rectEl.FillPattern = targetShape.FillPattern
119 rectEl.ClassName = "shape"
120 rectEl.Style = targetShape.CSSStyle()
121 if targetShape.BorderRadius != 0 {
122 rectEl.Rx = float64(targetShape.BorderRadius)
123 rectEl.Ry = float64(targetShape.BorderRadius)
124 }
125 fmt.Fprint(writer, rectEl.Render())
126
127 box := geo.NewBox(
128 geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)),
129 float64(targetShape.Width),
130 float64(targetShape.Height),
131 )
132 rowHeight := box.Height / float64(1+len(targetShape.SQLTable.Columns))
133 headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight)
134
135 fmt.Fprint(writer,
136 tableHeader(diagramHash, targetShape, headerBox, targetShape.Label,
137 float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)),
138 )
139
140 var longestNameWidth int
141 var longestTypeWidth int
142 for _, f := range targetShape.Columns {
143 longestNameWidth = go2.Max(longestNameWidth, f.Name.LabelWidth)
144 longestTypeWidth = go2.Max(longestTypeWidth, f.Type.LabelWidth)
145 }
146
147 rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight)
148 rowBox.TopLeft.Y += headerBox.Height
149 for idx, f := range targetShape.Columns {
150 fmt.Fprint(writer,
151 tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth), float64(longestTypeWidth)),
152 )
153 rowBox.TopLeft.Y += rowHeight
154
155 lineEl := d2themes.NewThemableElement("line")
156 if idx == len(targetShape.Columns)-1 && targetShape.BorderRadius != 0 {
157 lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X+float64(targetShape.BorderRadius), rowBox.TopLeft.Y
158 lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width-float64(targetShape.BorderRadius), rowBox.TopLeft.Y
159 } else {
160 lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y
161 lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y
162 }
163 lineEl.Stroke = targetShape.Fill
164 lineEl.Style = "stroke-width:2"
165 fmt.Fprint(writer, lineEl.Render())
166 }
167
168 if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage {
169 iconPosition := label.FromString(targetShape.IconPosition)
170 iconSize := d2target.GetIconSize(box, targetShape.IconPosition)
171
172 tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize))
173
174 fmt.Fprintf(writer, `<image href="%s" x="%f" y="%f" width="%d" height="%d" />`,
175 html.EscapeString(targetShape.Icon.String()),
176 tl.X,
177 tl.Y,
178 iconSize,
179 iconSize,
180 )
181 }
182 }
183
View as plain text