1 package shape
2
3 import (
4 "math"
5
6 "oss.terrastruct.com/d2/lib/geo"
7 "oss.terrastruct.com/d2/lib/svg"
8 "oss.terrastruct.com/util-go/go2"
9 )
10
11 const (
12 SQUARE_TYPE = "Square"
13 REAL_SQUARE_TYPE = "RealSquare"
14 PARALLELOGRAM_TYPE = "Parallelogram"
15 DOCUMENT_TYPE = "Document"
16 CYLINDER_TYPE = "Cylinder"
17 QUEUE_TYPE = "Queue"
18 PAGE_TYPE = "Page"
19 PACKAGE_TYPE = "Package"
20 STEP_TYPE = "Step"
21 CALLOUT_TYPE = "Callout"
22 STORED_DATA_TYPE = "StoredData"
23 PERSON_TYPE = "Person"
24 DIAMOND_TYPE = "Diamond"
25 OVAL_TYPE = "Oval"
26 CIRCLE_TYPE = "Circle"
27 HEXAGON_TYPE = "Hexagon"
28 CLOUD_TYPE = "Cloud"
29
30 TABLE_TYPE = "Table"
31 CLASS_TYPE = "Class"
32 TEXT_TYPE = "Text"
33 CODE_TYPE = "Code"
34 IMAGE_TYPE = "Image"
35
36 defaultPadding = 40.
37 )
38
39 type Shape interface {
40 Is(shape string) bool
41 GetType() string
42
43 AspectRatio1() bool
44 IsRectangular() bool
45
46 GetBox() *geo.Box
47 GetInnerBox() *geo.Box
48
49 GetInnerBoxForContent(width, height float64) *geo.Box
50 SetInnerBoxAspectRatio(aspectRatio float64)
51
52
53 GetInsidePlacement(width, height, paddingX, paddingY float64) geo.Point
54
55 GetDimensionsToFit(width, height, paddingX, paddingY float64) (float64, float64)
56 GetDefaultPadding() (paddingX, paddingY float64)
57
58
59 Perimeter() []geo.Intersectable
60
61 GetSVGPathData() []string
62 }
63
64 type baseShape struct {
65 Type string
66 Box *geo.Box
67 FullShape *Shape
68 }
69
70 func (s baseShape) Is(shapeType string) bool {
71 return s.Type == shapeType
72 }
73
74 func (s baseShape) GetType() string {
75 return s.Type
76 }
77
78 func (s baseShape) AspectRatio1() bool {
79 return false
80 }
81
82 func (s baseShape) IsRectangular() bool {
83 return false
84 }
85
86 func (s baseShape) GetBox() *geo.Box {
87 return s.Box
88 }
89
90 func (s baseShape) GetInnerBox() *geo.Box {
91 return s.Box
92 }
93
94
95 func (s baseShape) GetInnerBoxForContent(width, height float64) *geo.Box {
96 return nil
97 }
98
99 func (s baseShape) SetInnerBoxAspectRatio(aspectRatio float64) {
100
101 }
102
103 func (s baseShape) GetInsidePlacement(_, _, paddingX, paddingY float64) geo.Point {
104 innerTL := (*s.FullShape).GetInnerBox().TopLeft
105 return *geo.NewPoint(innerTL.X+paddingX/2, innerTL.Y+paddingY/2)
106 }
107
108
109
110 func (s baseShape) GetDimensionsToFit(width, height, paddingX, paddingY float64) (float64, float64) {
111 return math.Ceil(width + paddingX), math.Ceil(height + paddingY)
112 }
113
114 func (s baseShape) GetDefaultPadding() (paddingX, paddingY float64) {
115 return defaultPadding, defaultPadding
116 }
117
118 func (s baseShape) Perimeter() []geo.Intersectable {
119 return nil
120 }
121
122 func (s baseShape) GetSVGPathData() []string {
123 return nil
124 }
125
126 func NewShape(shapeType string, box *geo.Box) Shape {
127 switch shapeType {
128 case CALLOUT_TYPE:
129 return NewCallout(box)
130 case CIRCLE_TYPE:
131 return NewCircle(box)
132 case CLASS_TYPE:
133 return NewClass(box)
134 case CLOUD_TYPE:
135 return NewCloud(box)
136 case CODE_TYPE:
137 return NewCode(box)
138 case CYLINDER_TYPE:
139 return NewCylinder(box)
140 case DIAMOND_TYPE:
141 return NewDiamond(box)
142 case DOCUMENT_TYPE:
143 return NewDocument(box)
144 case HEXAGON_TYPE:
145 return NewHexagon(box)
146 case IMAGE_TYPE:
147 return NewImage(box)
148 case OVAL_TYPE:
149 return NewOval(box)
150 case PACKAGE_TYPE:
151 return NewPackage(box)
152 case PAGE_TYPE:
153 return NewPage(box)
154 case PARALLELOGRAM_TYPE:
155 return NewParallelogram(box)
156 case PERSON_TYPE:
157 return NewPerson(box)
158 case QUEUE_TYPE:
159 return NewQueue(box)
160 case REAL_SQUARE_TYPE:
161 return NewRealSquare(box)
162 case STEP_TYPE:
163 return NewStep(box)
164 case STORED_DATA_TYPE:
165 return NewStoredData(box)
166 case SQUARE_TYPE:
167 return NewSquare(box)
168 case TABLE_TYPE:
169 return NewTable(box)
170 case TEXT_TYPE:
171 return NewText(box)
172
173 default:
174 shape := shapeSquare{
175 baseShape: &baseShape{
176 Type: shapeType,
177 Box: box,
178 },
179 }
180 shape.FullShape = go2.Pointer(Shape(shape))
181 return shape
182 }
183 }
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 func TraceToShapeBorder(shape Shape, rectBorderPoint, prevPoint *geo.Point) *geo.Point {
207 if shape.Is("") || shape.IsRectangular() {
208 return rectBorderPoint
209 }
210
211
212 scaleSize := shape.GetBox().Width
213 if prevPoint.X == rectBorderPoint.X {
214 scaleSize = shape.GetBox().Height
215 }
216 vector := prevPoint.VectorTo(rectBorderPoint)
217 vector = vector.AddLength(scaleSize)
218 extendedSegment := geo.Segment{Start: prevPoint, End: prevPoint.AddVector(vector)}
219
220 closestD := math.Inf(1)
221 closestPoint := rectBorderPoint
222
223 for _, perimeterSegment := range shape.Perimeter() {
224 for _, intersectingPoint := range perimeterSegment.Intersections(extendedSegment) {
225 d := geo.EuclideanDistance(rectBorderPoint.X, rectBorderPoint.Y, intersectingPoint.X, intersectingPoint.Y)
226 if d < closestD {
227 closestD = d
228 closestPoint = intersectingPoint
229 }
230 }
231 }
232
233 closestPoint.TruncateFloat32()
234 return geo.NewPoint(math.Round(closestPoint.X), math.Round(closestPoint.Y))
235 }
236
237 func boxPath(box *geo.Box) *svg.SvgPathContext {
238 pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
239 pc.StartAt(pc.Absolute(0, 0))
240 pc.L(false, box.Width, 0)
241 pc.L(false, box.Width, box.Height)
242 pc.L(false, 0, box.Height)
243 pc.Z()
244 return pc
245 }
246
247 func LimitAR(width, height, aspectRatio float64) (float64, float64) {
248 if width > aspectRatio*height {
249 height = math.Round(width / aspectRatio)
250 } else if height > aspectRatio*width {
251 width = math.Round(height / aspectRatio)
252 }
253 return width, height
254 }
255
View as plain text