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 )
14
15 func classHeader(diagramHash string, shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string {
16 rectEl := d2themes.NewThemableElement("rect")
17 rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y
18 rectEl.Width, rectEl.Height = box.Width, box.Height
19 rectEl.Fill = shape.Fill
20 rectEl.FillPattern = shape.FillPattern
21 rectEl.ClassName = "class_header"
22 if shape.BorderRadius != 0 {
23 rectEl.ClipPath = fmt.Sprintf("%v-%v", diagramHash, shape.ID)
24 }
25 str := rectEl.Render()
26
27 if text != "" {
28 tl := label.InsideMiddleCenter.GetPointOnBox(
29 box,
30 0,
31 textWidth,
32 textHeight,
33 )
34
35 textEl := d2themes.NewThemableElement("text")
36 textEl.X = tl.X + textWidth/2
37 textEl.Y = tl.Y + textHeight*3/4
38 textEl.Fill = shape.GetFontColor()
39 textEl.ClassName = "text-mono"
40 textEl.Style = fmt.Sprintf(`text-anchor:%s;font-size:%vpx;`,
41 "middle", 4+fontSize,
42 )
43 textEl.Content = svg.EscapeText(text)
44 str += textEl.Render()
45 }
46 return str
47 }
48
49 func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText string, fontSize float64) string {
50
51
52 prefixTL := label.InsideMiddleLeft.GetPointOnBox(
53 box,
54 d2target.PrefixPadding,
55 box.Width,
56 fontSize,
57 )
58 typeTR := label.InsideMiddleRight.GetPointOnBox(
59 box,
60 d2target.TypePadding,
61 0,
62 fontSize,
63 )
64
65 textEl := d2themes.NewThemableElement("text")
66 textEl.X = prefixTL.X
67 textEl.Y = prefixTL.Y + fontSize*3/4
68 textEl.Fill = shape.PrimaryAccentColor
69 textEl.ClassName = "text-mono"
70 textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", fontSize)
71 textEl.Content = prefix
72 out := textEl.Render()
73
74 textEl.X = prefixTL.X + d2target.PrefixWidth
75 textEl.Fill = shape.Fill
76 textEl.Content = svg.EscapeText(nameText)
77 out += textEl.Render()
78
79 textEl.X = typeTR.X
80 textEl.Y = typeTR.Y + fontSize*3/4
81 textEl.Fill = shape.SecondaryAccentColor
82 textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", fontSize)
83 textEl.Content = svg.EscapeText(typeText)
84 out += textEl.Render()
85
86 return out
87 }
88
89 func drawClass(writer io.Writer, diagramHash string, targetShape d2target.Shape) {
90 el := d2themes.NewThemableElement("rect")
91 el.X = float64(targetShape.Pos.X)
92 el.Y = float64(targetShape.Pos.Y)
93 el.Width = float64(targetShape.Width)
94 el.Height = float64(targetShape.Height)
95 el.Fill, el.Stroke = d2themes.ShapeTheme(targetShape)
96 el.FillPattern = targetShape.FillPattern
97 el.Style = targetShape.CSSStyle()
98 if targetShape.BorderRadius != 0 {
99 el.Rx = float64(targetShape.BorderRadius)
100 el.Ry = float64(targetShape.BorderRadius)
101 }
102 fmt.Fprint(writer, el.Render())
103
104 box := geo.NewBox(
105 geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)),
106 float64(targetShape.Width),
107 float64(targetShape.Height),
108 )
109 rowHeight := box.Height / float64(2+len(targetShape.Class.Fields)+len(targetShape.Class.Methods))
110 headerBox := geo.NewBox(box.TopLeft, box.Width, 2*rowHeight)
111
112 fmt.Fprint(writer,
113 classHeader(diagramHash, targetShape, headerBox, targetShape.Label, float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)),
114 )
115
116 rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight)
117 rowBox.TopLeft.Y += headerBox.Height
118 for _, f := range targetShape.Fields {
119 fmt.Fprint(writer,
120 classRow(targetShape, rowBox, f.VisibilityToken(), f.Name, f.Type, float64(targetShape.FontSize)),
121 )
122 rowBox.TopLeft.Y += rowHeight
123 }
124
125 lineEl := d2themes.NewThemableElement("line")
126
127 if targetShape.BorderRadius != 0 && len(targetShape.Methods) == 0 {
128 lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X+float64(targetShape.BorderRadius), rowBox.TopLeft.Y
129 lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width-float64(targetShape.BorderRadius), rowBox.TopLeft.Y
130 } else {
131 lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y
132 lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y
133 }
134
135 lineEl.Stroke = targetShape.Fill
136 lineEl.Style = "stroke-width:1"
137 fmt.Fprint(writer, lineEl.Render())
138
139 for _, m := range targetShape.Methods {
140 fmt.Fprint(writer,
141 classRow(targetShape, rowBox, m.VisibilityToken(), m.Name, m.Return, float64(targetShape.FontSize)),
142 )
143 rowBox.TopLeft.Y += rowHeight
144 }
145
146 if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage {
147 iconPosition := label.FromString(targetShape.IconPosition)
148 iconSize := d2target.GetIconSize(box, targetShape.IconPosition)
149
150 tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize))
151
152 fmt.Fprintf(writer, `<image href="%s" x="%f" y="%f" width="%d" height="%d" />`,
153 html.EscapeString(targetShape.Icon.String()),
154 tl.X,
155 tl.Y,
156 iconSize,
157 iconSize,
158 )
159 }
160 }
161
View as plain text