• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyberZHG / SVGDiagram / 21571400724

01 Feb 2026 10:25PM UTC coverage: 99.621% (-0.03%) from 99.653%
21571400724

push

github

web-flow
Add orientation attribute for polygons (#63)

* Add orientation attribute for polygons

* Fix vertical margin

53 of 53 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

2628 of 2638 relevant lines covered (99.62%)

2357.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.57
/src/diagram/svg_node.cpp
1
#include "svg_nodes.h"
2

3
#include <format>
4
#include <cmath>
5
#include <functional>
6
#include <numbers>
7

8
#include "attribute_utils.h"
9
#include "svg_text_size.h"
10
#include "geometry_utils.h"
11
using namespace std;
12
using namespace svg_diagram;
13

14
SVGNode::SVGNode(const double cx, const double cy) {
60✔
15
    _cx = cx;
60✔
16
    _cy = cy;
60✔
17
}
60✔
18

19
void SVGNode::setAttributeIfNotExist(const string_view &key, const string &value) {
8,222✔
20
    if (attributes().contains(key)) {
8,222✔
21
        return;
5,171✔
22
    }
23
    if (parent() != nullptr) {
3,051✔
24
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
3,051✔
25
            return;
134✔
26
        }
27
    }
28
    setAttribute(key, value);
2,917✔
29
}
30

31
const string& SVGNode::getAttribute(const std::string_view& key) const {
22,244✔
32
    static const string EMPTY_STRING;
22,244✔
33
    if (const auto it = attributes().find(key); it != attributes().end()) {
22,244✔
34
        return it->second;
18,649✔
35
    }
36
    if (parent() != nullptr) {
3,595✔
37
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
3,595✔
38
            return ret.value();
253✔
39
        }
40
    }
41
    return EMPTY_STRING;
3,342✔
42
}
43

44
void SVGNode::setShape(const string& shape) {
300✔
45
    setAttribute(ATTR_KEY_SHAPE, shape);
300✔
46
}
300✔
47

48
void SVGNode::setShape(const string_view& shape) {
300✔
49
    setShape(string(shape));
300✔
50
}
300✔
51

52
void SVGNode::setSides(const int sides) {
144✔
53
    setAttribute(ATTR_KEY_SIDES, sides);
144✔
54
}
144✔
55

56
void SVGNode::setSkew(const double skew) {
7✔
57
    setAttribute(ATTR_KEY_SKEW, skew);
7✔
58
}
7✔
59

60
void SVGNode::setDistortion(const double distortion) {
19✔
61
    setAttribute(ATTR_KEY_DISTORTION, distortion);
19✔
62
}
19✔
63

64
void SVGNode::setOrientation(const double orientation) {
19✔
65
    setAttribute(ATTR_KEY_ORIENTATION, orientation);
19✔
66
}
19✔
67

68
void SVGNode::setCenter(const double cx, const double cy) {
264✔
69
    _cx = cx;
264✔
70
    _cy = cy;
264✔
71
}
264✔
72

73
pair<double, double> SVGNode::center() const {
1,319✔
74
    return {_cx, _cy};
1,319✔
75
}
76

77
void SVGNode::adjustNodeSize() {
563✔
78
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,126✔
79
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
1,126✔
80
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
563✔
81
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
563✔
82
    if (shape == SHAPE_CIRCLE) {
563✔
83
        adjustNodeSizeCircle();
144✔
84
    } else if (shape == SHAPE_DOUBLE_CIRCLE) {
419✔
85
        adjustNodeSizeDoubleCircle();
15✔
86
    } else if (shape == SHAPE_NONE || shape == SHAPE_RECT) {
404✔
87
        adjustNodeSizeRect();
59✔
88
    } else if (shape == SHAPE_ELLIPSE) {
345✔
89
        adjustNodeSizeEllipse();
157✔
90
    } else if (shape == SHAPE_DOUBLE_ELLIPSE) {
188✔
91
        adjustNodeSizeDoubleEllipse();
24✔
92
    } else if (shape == SHAPE_POLYGON) {
164✔
93
        adjustNodeSizePolygon();
55✔
94
    } else if (shape == SHAPE_TRIANGLE) {
109✔
95
        setSides(3);
5✔
96
        adjustNodeSizePolygon();
5✔
97
    } else if (shape == SHAPE_PENTAGON) {
104✔
98
        setSides(5);
5✔
99
        adjustNodeSizePolygon();
5✔
100
    } else if (shape == SHAPE_HEXAGON) {
99✔
101
        setSides(6);
6✔
102
        adjustNodeSizePolygon();
6✔
103
    } else if (shape == SHAPE_SEPTAGON) {
93✔
104
        setSides(7);
5✔
105
        adjustNodeSizePolygon();
5✔
106
    } else if (shape == SHAPE_OCTAGON) {
88✔
107
        setSides(8);
5✔
108
        adjustNodeSizePolygon();
5✔
109
    } else if (shape == SHAPE_TRAPEZIUM) {
83✔
110
        setSides(4);
4✔
111
        setDistortion(-0.4);
4✔
112
        adjustNodeSizePolygon();
4✔
113
    } else if (shape == SHAPE_PARALLELOGRAM) {
79✔
114
        setSides(4);
4✔
115
        setSkew(0.6);
4✔
116
        adjustNodeSizePolygon();
4✔
117
    } else if (shape == SHAPE_HOUSE) {
75✔
118
        setSides(5);
4✔
119
        setDistortion(-0.64);
4✔
120
        adjustNodeSizePolygon();
4✔
121
    } else if (shape == SHAPE_DIAMOND) {
71✔
122
        setSides(4);
4✔
123
        setOrientation(45);
4✔
124
        adjustNodeSizePolygon();
4✔
125
    } else if (shape == SHAPE_INV_TRIANGLE) {
67✔
126
        setSides(3);
4✔
127
        setOrientation(180);
4✔
128
        adjustNodeSizePolygon();
4✔
129
    } else if (shape == SHAPE_INV_TRAPEZIUM) {
63✔
130
        setSides(4);
4✔
131
        setDistortion(-0.4);
4✔
132
        setOrientation(180);
4✔
133
        adjustNodeSizePolygon();
4✔
134
    } else if (shape == SHAPE_INV_HOUSE) {
59✔
135
        setSides(5);
4✔
136
        setDistortion(-0.64);
4✔
137
        setOrientation(180);
4✔
138
        adjustNodeSizePolygon();
4✔
139
    } else if (shape == SHAPE_RECORD) {
55✔
140
        adjustNodeSizeRecord();
55✔
141
    }
142
}
563✔
143

144
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
563✔
145
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,126✔
146
    setAttributeIfNotExist(ATTR_KEY_COLOR, string(ATTR_DEF_COLOR));
1,126✔
147
    setAttributeIfNotExist(ATTR_KEY_FILL_COLOR, string(ATTR_DEF_FILL_COLOR));
1,126✔
148
    setAttributeIfNotExist(ATTR_KEY_FONT_COLOR, string(ATTR_DEF_FONT_COLOR));
1,126✔
149
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
1,126✔
150
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
1,126✔
151
    setAttributeIfNotExist(ATTR_KEY_STYLE, string(ATTR_DEF_STYLE));
563✔
152
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
563✔
153
    if (shape == SHAPE_CIRCLE) {
563✔
154
        return produceSVGDrawsCircle();
144✔
155
    }
156
    if (shape == SHAPE_DOUBLE_CIRCLE) {
419✔
157
        return produceSVGDrawsDoubleCircle();
15✔
158
    }
159
    if (shape == SHAPE_RECT) {
404✔
160
        return produceSVGDrawsRect();
56✔
161
    }
162
    if (shape == SHAPE_ELLIPSE) {
348✔
163
        return produceSVGDrawsEllipse();
157✔
164
    }
165
    if (shape == SHAPE_DOUBLE_ELLIPSE) {
191✔
166
        return produceSVGDrawsDoubleEllipse();
24✔
167
    }
168
    if (shape == SHAPE_POLYGON || shape == SHAPE_TRIANGLE || shape == SHAPE_PENTAGON ||
381✔
169
        shape == SHAPE_HEXAGON || shape == SHAPE_SEPTAGON || shape == SHAPE_OCTAGON ||
284✔
170
        shape == SHAPE_TRAPEZIUM || shape == SHAPE_PARALLELOGRAM || shape == SHAPE_HOUSE ||
242✔
171
        shape == SHAPE_DIAMOND || shape == SHAPE_INV_TRIANGLE || shape == SHAPE_INV_TRAPEZIUM ||
415✔
172
        shape == SHAPE_INV_HOUSE) {
229✔
173
        return produceSVGDrawsPolygon();
109✔
174
    }
175
    if (shape == SHAPE_RECORD) {
58✔
176
        return produceSVGDrawsRecord();
55✔
177
    }
178
    return produceSVGDrawsNone();
3✔
179
}
563✔
180

181
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
1,209✔
182
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,209✔
183
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,209✔
184
    if (shape == SHAPE_CIRCLE) {
1,209✔
185
        return computeConnectionPointCircle(angle);
319✔
186
    }
187
    if (shape == SHAPE_DOUBLE_CIRCLE) {
890✔
188
        return computeConnectionPointDoubleCircle(angle);
6✔
189
    }
190
    if (shape == SHAPE_RECT || shape == SHAPE_NONE) {
884✔
191
        return computeConnectionPointRect(angle);
240✔
192
    }
193
    if (shape == SHAPE_RECORD) {
644✔
194
        return computeConnectionPointRecord(angle);
33✔
195
    }
196
    if (shape == SHAPE_DOUBLE_ELLIPSE) {
611✔
197
        return computeConnectionPointDoubleEllipse(angle);
144✔
198
    }
199
    if (shape == SHAPE_POLYGON || shape == SHAPE_TRIANGLE || shape == SHAPE_PENTAGON ||
1,187✔
200
        shape == SHAPE_HEXAGON || shape == SHAPE_SEPTAGON || shape == SHAPE_OCTAGON ||
1,061✔
201
        shape == SHAPE_TRAPEZIUM || shape == SHAPE_PARALLELOGRAM || shape == SHAPE_HOUSE ||
1,050✔
202
        shape == SHAPE_DIAMOND || shape == SHAPE_INV_TRIANGLE || shape == SHAPE_INV_TRAPEZIUM ||
1,528✔
203
        shape == SHAPE_INV_HOUSE) {
817✔
204
        return computeConnectionPointPolygon(angle);
117✔
205
    }
206
    return computeConnectionPointEllipse(angle);
350✔
207
}
1,209✔
208

209
pair<double, double> SVGNode::computeFieldConnectionPoint(const string& fieldId, const double angle) {
1,229✔
210
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,229✔
211
    if (shape != SHAPE_RECORD || !_recordPositions.contains(fieldId)) {
1,229✔
212
        return computeConnectionPoint(angle);
1,209✔
213
    }
214
    const auto& [cx, cy, width, height] = _recordPositions.at(fieldId);
20✔
215
    return computeConnectionPointRect(cx, cy, width, height, angle);
20✔
216
}
1,229✔
217

218
double SVGNode::computeAngle(const double x, const double y) const {
1,140✔
219
    return atan2(y - _cy, x - _cx);
1,140✔
220
}
221

222
double SVGNode::computeAngle(const pair<double, double>& p) const {
1,140✔
223
    return computeAngle(p.first, p.second);
1,140✔
224
}
225

226
double SVGNode::computeFieldAngle(const string &fieldId, const pair<double, double>& p) const {
1,154✔
227
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,154✔
228
    if (shape != SHAPE_RECORD || !_recordPositions.contains(fieldId)) {
1,154✔
229
        return computeAngle(p);
1,140✔
230
    }
231
    const auto& [cx, cy, width, height] = _recordPositions.at(fieldId);
14✔
232
    return atan2(p.second - cy, p.first - cx);
14✔
233
}
1,154✔
234

235
bool SVGNode::isFixedSize() const {
563✔
236
    return AttributeUtils::parseBool(getAttribute(ATTR_KEY_FIXED_SIZE));
563✔
237
}
238

239
void SVGNode::updateNodeSize(const double width, const double height) {
563✔
240
    if (isFixedSize()) {
563✔
241
        setDoubleAttributeIfNotExist(ATTR_KEY_WIDTH, AttributeUtils::pointToInch(width));
48✔
242
        setDoubleAttributeIfNotExist(ATTR_KEY_HEIGHT, AttributeUtils::pointToInch(height));
48✔
243
    } else {
244
        setWidth(width);
515✔
245
        setHeight(height);
515✔
246
    }
247
}
563✔
248

249
void SVGNode::updateNodeSize(const pair<double, double>& size) {
59✔
250
    updateNodeSize(size.first, size.second);
59✔
251
}
59✔
252

253
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
508✔
254
    appendSVGDrawsLabelWithLocation(svgDraws, _cx, _cy);
508✔
255
}
508✔
256

257
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
3✔
258
    vector<unique_ptr<SVGDraw>> svgDraws;
3✔
259
    appendSVGDrawsLabel(svgDraws);
3✔
260
    return svgDraws;
6✔
261
}
3✔
262

263
void SVGNode::adjustNodeSizeCircle() {
159✔
264
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
159✔
265
    updateNodeSize(diameter, diameter);
159✔
266
}
159✔
267

268
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
144✔
269
    vector<unique_ptr<SVGDraw>> svgDraws;
144✔
270
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, max(width(), height()) / 2.0);
144✔
271
    setStrokeStyles(circle.get());
144✔
272
    setFillStyles(circle.get(), svgDraws);
144✔
273
    svgDraws.emplace_back(std::move(circle));
144✔
274
    appendSVGDrawsLabel(svgDraws);
144✔
275
    return svgDraws;
288✔
276
}
144✔
277

278
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
319✔
279
    const double radius = (width() + penWidth()) / 2.0;
319✔
280
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
319✔
281
}
282

283
void SVGNode::adjustNodeSizeDoubleCircle() {
15✔
284
    adjustNodeSizeCircle();
15✔
285
}
15✔
286

287
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsDoubleCircle() {
15✔
288
    vector<unique_ptr<SVGDraw>> svgDraws;
15✔
289
    const double radius = max(width(), height()) / 2.0;
15✔
290
    auto circleInner = make_unique<SVGDrawCircle>(_cx, _cy, radius);
15✔
291
    setStrokeStyles(circleInner.get());
15✔
292
    setFillStyles(circleInner.get(), svgDraws);
15✔
293
    svgDraws.emplace_back(std::move(circleInner));
15✔
294
    auto circleOuter = make_unique<SVGDrawCircle>(_cx, _cy, radius + DOUBLE_BORDER_MARGIN);
15✔
295
    setStrokeStyles(circleOuter.get());
15✔
296
    circleOuter->setFill("none");
30✔
297
    svgDraws.emplace_back(std::move(circleOuter));
15✔
298
    appendSVGDrawsLabel(svgDraws);
15✔
299
    return svgDraws;
30✔
300
}
15✔
301

302
std::pair<double, double> SVGNode::computeConnectionPointDoubleCircle(const double angle) const {
6✔
303
    const double radius = (width() + penWidth()) / 2.0 + DOUBLE_BORDER_MARGIN;
6✔
304
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
6✔
305
}
306

307
void SVGNode::adjustNodeSizeRect() {
59✔
308
    updateNodeSize(computeTextSizeWithMargin());
59✔
309
}
59✔
310

311
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
56✔
312
    vector<unique_ptr<SVGDraw>> svgDraws;
56✔
313
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width(), height());
56✔
314
    setStrokeStyles(rect.get());
56✔
315
    setFillStyles(rect.get(), svgDraws);
56✔
316
    svgDraws.emplace_back(std::move(rect));
56✔
317
    appendSVGDrawsLabel(svgDraws);
56✔
318
    return svgDraws;
112✔
319
}
56✔
320

321
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
273✔
322
    return computeConnectionPointRect(_cx, _cy, width(), height(), angle);
273✔
323
}
324

325
pair<double, double> SVGNode::computeConnectionPointRect(const double cx, const double cy, const double width, const double height, const double angle) const {
293✔
326
    const double strokeWidth = penWidth();
293✔
327
    const double totalWidth = width + strokeWidth;
293✔
328
    const double totalHeight = height + strokeWidth;
293✔
329
    double x1 = -totalWidth / 2, y1 = -totalHeight / 2;
293✔
330
    double x2 = totalWidth / 2, y2 = totalHeight / 2;
293✔
331
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
586✔
332
    for (const auto& [x, y] : vertices) {
1,453✔
333
        if (GeometryUtils::isSameAngle(angle, x, y)) {
1,164✔
334
            return {cx + x, cy + y};
4✔
335
        }
336
    }
337
    double x = 0.0, y = 0.0;
289✔
338
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
1,445✔
339
        x1 = vertices[i].first;
1,156✔
340
        y1 = vertices[i].second;
1,156✔
341
        x2 = vertices[(i + 1) % vertices.size()].first;
1,156✔
342
        y2 = vertices[(i + 1) % vertices.size()].second;
1,156✔
343
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
1,156✔
344
            x = _cx + intersect.value().first;
289✔
345
            y = _cy + intersect.value().second;
289✔
346
        }
347
    }
348
    return {x, y};
289✔
349
}
293✔
350

351
void SVGNode::adjustNodeSizeEllipse() {
181✔
352
    const auto [textWidth, textHeight] = computeTextSize();
181✔
353
    const auto [marginX, marginY] = computeMargin();
181✔
354
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
181✔
355
}
181✔
356

357
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
157✔
358
    vector<unique_ptr<SVGDraw>> svgDraws;
157✔
359
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
157✔
360
    setStrokeStyles(ellipse.get());
157✔
361
    setFillStyles(ellipse.get(), svgDraws);
157✔
362
    svgDraws.emplace_back(std::move(ellipse));
157✔
363
    appendSVGDrawsLabel(svgDraws);
157✔
364
    return svgDraws;
314✔
365
}
157✔
366

367
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
350✔
368
    const double strokeWidth = penWidth();
350✔
369
    const double totalWidth = width() + strokeWidth;
350✔
370
    const double totalHeight = height() + strokeWidth;
350✔
371
    const double rx = totalWidth / 2, ry = totalHeight / 2;
350✔
372
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
350✔
373
    const double x = rx * ry * cos(angle) / base;
350✔
374
    const double y = rx * ry * sin(angle) / base;
350✔
375
    return {_cx + x, _cy + y};
350✔
376
}
377

378
void SVGNode::adjustNodeSizeDoubleEllipse() {
24✔
379
    adjustNodeSizeEllipse();
24✔
380
}
24✔
381

382
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsDoubleEllipse() {
24✔
383
    vector<unique_ptr<SVGDraw>> svgDraws;
24✔
384
    auto ellipseInner = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
24✔
385
    setStrokeStyles(ellipseInner.get());
24✔
386
    setFillStyles(ellipseInner.get(), svgDraws);
24✔
387
    svgDraws.emplace_back(std::move(ellipseInner));
24✔
388
    auto ellipseOuter = make_unique<SVGDrawEllipse>(_cx, _cy, width() + DOUBLE_BORDER_MARGIN * 2, height() + DOUBLE_BORDER_MARGIN * 2);
24✔
389
    setStrokeStyles(ellipseOuter.get());
24✔
390
    ellipseOuter->setFill("none");
48✔
391
    svgDraws.emplace_back(std::move(ellipseOuter));
24✔
392
    appendSVGDrawsLabel(svgDraws);
24✔
393
    return svgDraws;
48✔
394
}
24✔
395

396
std::pair<double, double> SVGNode::computeConnectionPointDoubleEllipse(const double angle) const {
144✔
397
    const double strokeWidth = penWidth();
144✔
398
    const double totalWidth = width() + strokeWidth + DOUBLE_BORDER_MARGIN * 2;
144✔
399
    const double totalHeight = height() + strokeWidth + DOUBLE_BORDER_MARGIN * 2;
144✔
400
    const double rx = totalWidth / 2, ry = totalHeight / 2;
144✔
401
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
144✔
402
    const double x = rx * ry * cos(angle) / base;
144✔
403
    const double y = rx * ry * sin(angle) / base;
144✔
404
    return {_cx + x, _cy + y};
144✔
405
}
406

407
void SVGNode::adjustNodeSizePolygon() {
109✔
408
    setAttributeIfNotExist(ATTR_KEY_SIDES, string(ATTR_DEF_SIDES));
218✔
409
    setAttributeIfNotExist(ATTR_KEY_SKEW, string(ATTR_DEF_SKEW));
218✔
410
    setAttributeIfNotExist(ATTR_KEY_DISTORTION, string(ATTR_DEF_DISTORTION));
218✔
411
    setAttributeIfNotExist(ATTR_KEY_ORIENTATION, string(ATTR_DEF_ORIENTATION));
109✔
412
    const auto [textWidth, textHeight] = computeTextSize();
109✔
413
    const auto [marginX, marginY] = computeMargin();
109✔
414
    const double boxWidth = textWidth + marginX * 2;
109✔
415
    const double boxHeight = textHeight + marginY * 2;
109✔
416
    updateNodeSize(boxWidth * numbers::sqrt2, boxHeight * numbers::sqrt2);
109✔
417
}
109✔
418

419
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsPolygon() {
109✔
420
    vector<unique_ptr<SVGDraw>> svgDraws;
109✔
421
    if (enabledDebug()) {
109✔
422
        auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
61✔
423
        ellipse->setStroke("green");
122✔
424
        ellipse->setFill("none");
122✔
425
        svgDraws.emplace_back(std::move(ellipse));
61✔
426
    }
61✔
427
    const auto vertices = computePolygonVertices();
109✔
428
    auto polygon = make_unique<SVGDrawPolygon>(vertices);
109✔
429
    setStrokeStyles(polygon.get());
109✔
430
    setFillStyles(polygon.get(), svgDraws);
109✔
431
    svgDraws.emplace_back(std::move(polygon));
109✔
432
    appendSVGDrawsLabel(svgDraws);
109✔
433
    return svgDraws;
218✔
434
}
109✔
435

436
vector<pair<double, double>> SVGNode::computePolygonVertices() const {
226✔
437
    auto sides = stoi(getAttribute(ATTR_KEY_SIDES));
226✔
438
    const auto skew = stod(getAttribute(ATTR_KEY_SKEW));
226✔
439
    const auto distortion = stod(getAttribute(ATTR_KEY_DISTORTION));
226✔
440
    const auto orientation = stod(getAttribute(ATTR_KEY_ORIENTATION));
226✔
441
    if (sides < 3) {
226✔
442
        sides = 3;
2✔
443
    }
444
    const double rx = width() / 2.0;
226✔
445
    const double ry = height() / 2.0;
226✔
446
    const double sectorAngle = 2.0 * numbers::pi / sides;
226✔
447
    const double skewDist = hypot(fabs(distortion) + fabs(skew), 1.0);
226✔
448
    const double gDistortion = -distortion * numbers::sqrt2 / cos(sectorAngle / 2.0) / 2.0;
226✔
449
    const double gSkew = -skew / 2.0;
226✔
450
    const double orientationRad = -orientation * numbers::pi / 180.0;
226✔
451
    const double cosOri = cos(orientationRad);
226✔
452
    const double sinOri = sin(orientationRad);
226✔
453

454
    vector<pair<double, double>> vertices;
226✔
455
    vertices.reserve(sides);
226✔
456
    const double angleOffset = (sides % 2 == 0) ? sectorAngle / 2.0 : 0.0;
226✔
457
    for (int i = 0; i < sides; ++i) {
1,366✔
458
        const double baseAngle = -numbers::pi / 2.0 + angleOffset + sectorAngle * i;
1,140✔
459
        const double unitX = cos(baseAngle);
1,140✔
460
        const double unitY = sin(baseAngle);
1,140✔
461
        const double x = unitX * (skewDist + unitY * gDistortion) + unitY * gSkew;
1,140✔
462
        const double y = unitY;
1,140✔
463

464
        const double rotX = x * cosOri - y * sinOri;
1,140✔
465
        const double rotY = x * sinOri + y * cosOri;
1,140✔
466
        vertices.emplace_back(_cx + rotX * rx, _cy + rotY * ry);
1,140✔
467
    }
468
    return vertices;
452✔
469
}
226✔
470

471
pair<double, double> SVGNode::computeConnectionPointPolygon(const double angle) const {
117✔
472
    const auto vertices = computePolygonVertices();
117✔
473
    const int n = static_cast<int>(vertices.size());
117✔
474
    for (int i = 0; i < n; ++i) {
368✔
475
        const auto& [x1, y1] = vertices[i];
368✔
476
        const auto& [x2, y2] = vertices[(i + 1) % n];
368✔
477

478
        const double rx1 = x1 - _cx;
368✔
479
        const double ry1 = y1 - _cy;
368✔
480
        const double rx2 = x2 - _cx;
368✔
481
        const double ry2 = y2 - _cy;
368✔
482

483
        if (const auto intersect = GeometryUtils::intersect(angle, rx1, ry1, rx2, ry2); intersect != nullopt) {
368✔
484
            return {_cx + intersect.value().first, _cy + intersect.value().second};
117✔
485
        }
486
    }
UNCOV
487
    return computeConnectionPointEllipse(angle);
×
488
}
117✔
489

490
void SVGNode::adjustNodeSizeRecord() {
55✔
491
    _recordSizes.clear();
55✔
492
    function<pair<double, double>(const unique_ptr<RecordLabel>&, bool)> adjustRecordSize;
55✔
493
    adjustRecordSize = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal) -> pair<double, double> {
438✔
494
        if (recordLabel->children.empty()) {
328✔
495
            const SVGTextSize textSize;
204✔
496
            const double fontSize = stod(getAttribute(ATTR_KEY_FONT_SIZE));
204✔
497
            const string fontFamily = getAttribute(ATTR_KEY_FONT_NAME);
204✔
498
            auto [width, height] = textSize.computeTextSize(recordLabel->label, fontSize, fontFamily);
204✔
499
            if (width == 0.0) {
204✔
500
                width = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_WIDTH_SCALE;
89✔
501
            }
502
            if (height == 0.0) {
204✔
503
                height = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_HEIGHT_SCALE;
89✔
504
            }
505
            const auto [marginX, marginY] = computeMargin();
204✔
506
            width += marginX * 2;
204✔
507
            height += marginY * 2;
204✔
508
            _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
204✔
509
            return {width, height};
204✔
510
        }
204✔
511
        double width = 0.0, height = 0.0;
124✔
512
        for (const auto& child : recordLabel->children) {
397✔
513
            const auto [subWidth, subHeight] = adjustRecordSize(child, !horizontal);
273✔
514
            if (horizontal) {
273✔
515
                width += subWidth;
153✔
516
                height = max(height, subHeight);
153✔
517
            } else {
518
                height += subHeight;
120✔
519
                width = max(width, subWidth);
120✔
520
            }
521
        }
522
        _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
124✔
523
        return {width, height};
124✔
524
    };
55✔
525
    _recordLabel = AttributeUtils::parseRecordLabel(getAttribute(ATTR_KEY_LABEL));
55✔
526
    const auto [width, height] = adjustRecordSize(_recordLabel, true);
55✔
527
    updateNodeSize(width, height);
55✔
528
}
55✔
529

530
std::vector<std::unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRecord() {
55✔
531
    const auto nodeWidth = width();
55✔
532
    const auto nodeHeight = height();
55✔
533
    vector<unique_ptr<SVGDraw>> svgDraws;
55✔
534
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, nodeWidth, nodeHeight);
55✔
535
    setStrokeStyles(rect.get());
55✔
536
    setFillStyles(rect.get(), svgDraws);
55✔
537
    svgDraws.emplace_back(std::move(rect));
55✔
538
    const auto [marginX, marginY] = margin();
55✔
539

540
    _recordPositions.clear();
55✔
541
    function<void(const unique_ptr<RecordLabel>&, bool, double, double, double, double)> drawRecordLabel;
55✔
542
    drawRecordLabel = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal, double x1, double y1, const double x2, const double y2) {
×
543
        const double cx = (x1 + x2) / 2;
328✔
544
        const double cy = (y1 + y2) / 2;
328✔
545
        if (!recordLabel->fieldId.empty()) {
328✔
546
            _recordPositions[recordLabel->fieldId] = {cx, cy, x2 - x1, y2 - y1};
20✔
547
        }
548
        if (recordLabel->children.empty()) {
328✔
549
            appendSVGDrawsLabelWithLocation(svgDraws, recordLabel->label, cx, cy, x2 - x1 - marginX * 2, y2 - y1 - marginY * 2);
204✔
550
            return;
204✔
551
        }
552
        bool first = true;
124✔
553
        for (const auto& child : recordLabel->children) {
397✔
554
            if (first) {
273✔
555
                first = false;
124✔
556
            } else {
557
                if (horizontal) {
149✔
558
                    auto line = make_unique<SVGDrawLine>(x1, y1, x1, y2);
73✔
559
                    setStrokeStyles(line.get());
73✔
560
                    svgDraws.emplace_back(std::move(line));
73✔
561
                } else {
73✔
562
                    auto line = make_unique<SVGDrawLine>(x1, y1, x2, y1);
76✔
563
                    setStrokeStyles(line.get());
76✔
564
                    svgDraws.emplace_back(std::move(line));
76✔
565
                }
76✔
566
            }
567
            const auto [subWidth, subHeight] = _recordSizes[reinterpret_cast<uintptr_t>(child.get())];
273✔
568
            if (horizontal) {
273✔
569
                drawRecordLabel(child, false, x1, y1, x1 + subWidth, y2);
153✔
570
                x1 += subWidth;
153✔
571
            } else {
572
                drawRecordLabel(child, true, x1, y1, x2, y1 + subHeight);
120✔
573
                y1 += subHeight;
120✔
574
            }
575
        }
576
    };
55✔
577
    const double x1 = _cx - nodeWidth / 2, y1 = _cy - nodeHeight / 2;
55✔
578
    const double x2 = _cx + nodeWidth / 2, y2 = _cy + nodeHeight / 2;
55✔
579
    drawRecordLabel(_recordLabel, true, x1, y1, x2, y2);
55✔
580

581
    return svgDraws;
110✔
582
}
55✔
583

584
std::pair<double, double> SVGNode::computeConnectionPointRecord(const double angle) const {
33✔
585
    return computeConnectionPointRect(angle);
33✔
586
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc