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

CyberZHG / SVGDiagram / 20875325437

10 Jan 2026 08:03AM UTC coverage: 99.689% (-0.1%) from 99.812%
20875325437

Pull #48

github

web-flow
Merge 3a99137a5 into 936f534ab
Pull Request #48: Add record node shape

127 of 130 new or added lines in 3 files covered. (97.69%)

2245 of 2252 relevant lines covered (99.69%)

1945.0 hits per line

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

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

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

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

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

18
void SVGNode::setAttributeIfNotExist(const string_view &key, const string &value) {
5,764✔
19
    if (attributes().contains(key)) {
5,764✔
20
        return;
3,678✔
21
    }
22
    if (parent() != nullptr) {
2,086✔
23
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
2,086✔
24
            return;
128✔
25
        }
26
    }
27
    setAttribute(key, value);
1,958✔
28
}
29

30
const string& SVGNode::getAttribute(const std::string_view& key) const {
14,140✔
31
    static const string EMPTY_STRING;
14,140✔
32
    if (const auto it = attributes().find(key); it != attributes().end()) {
14,140✔
33
        return it->second;
11,305✔
34
    }
35
    if (parent() != nullptr) {
2,835✔
36
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
2,835✔
37
            return ret.value();
168✔
38
        }
39
    }
40
    return EMPTY_STRING;
2,667✔
41
}
42

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

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

51
void SVGNode::setCenter(const double cx, const double cy) {
201✔
52
    _cx = cx;
201✔
53
    _cy = cy;
201✔
54
}
201✔
55

56
pair<double, double> SVGNode::center() const {
1,111✔
57
    return {_cx, _cy};
1,111✔
58
}
59

60
void SVGNode::adjustNodeSize() {
401✔
61
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
802✔
62
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
802✔
63
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
401✔
64
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
401✔
65
    if (shape == SHAPE_CIRCLE) {
401✔
66
        adjustNodeSizeCircle();
140✔
67
    } else if (shape == SHAPE_DOUBLE_CIRCLE) {
261✔
68
        adjustNodeSizeDoubleCircle();
32✔
69
    } else if (shape == SHAPE_NONE || shape == SHAPE_RECT) {
229✔
70
        adjustNodeSizeRect();
53✔
71
    } else if (shape == SHAPE_ELLIPSE) {
176✔
72
        adjustNodeSizeEllipse();
158✔
73
    } else if (shape == SHAPE_RECORD) {
18✔
74
        adjustNodeSizeRecord();
18✔
75
    }
76
}
401✔
77

78
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
401✔
79
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
802✔
80
    setAttributeIfNotExist(ATTR_KEY_COLOR, string(ATTR_DEF_COLOR));
802✔
81
    setAttributeIfNotExist(ATTR_KEY_FILL_COLOR, string(ATTR_DEF_FILL_COLOR));
802✔
82
    setAttributeIfNotExist(ATTR_KEY_FONT_COLOR, string(ATTR_DEF_FONT_COLOR));
802✔
83
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
802✔
84
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
802✔
85
    setAttributeIfNotExist(ATTR_KEY_STYLE, string(ATTR_DEF_STYLE));
401✔
86
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
401✔
87
    if (shape == SHAPE_CIRCLE) {
401✔
88
        return produceSVGDrawsCircle();
140✔
89
    }
90
    if (shape == SHAPE_DOUBLE_CIRCLE) {
261✔
91
        return produceSVGDrawsDoubleCircle();
32✔
92
    }
93
    if (shape == SHAPE_RECT) {
229✔
94
        return produceSVGDrawsRect();
50✔
95
    }
96
    if (shape == SHAPE_ELLIPSE) {
179✔
97
        return produceSVGDrawsEllipse();
158✔
98
    }
99
    if (shape == SHAPE_RECORD) {
21✔
100
        return produceSVGDrawsRecord();
18✔
101
    }
102
    return produceSVGDrawsNone();
3✔
103
}
401✔
104

105
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
1,161✔
106
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,161✔
107
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,161✔
108
    if (shape == SHAPE_CIRCLE) {
1,161✔
109
        return computeConnectionPointCircle(angle);
283✔
110
    }
111
    if (shape == SHAPE_DOUBLE_CIRCLE) {
878✔
112
        return computeConnectionPointDoubleCircle(angle);
199✔
113
    }
114
    if (shape == SHAPE_RECT || shape == SHAPE_NONE) {
679✔
115
        return computeConnectionPointRect(angle);
277✔
116
    }
117
    if (shape == SHAPE_RECORD) {
402✔
118
        return computeConnectionPointRecord(angle);
25✔
119
    }
120
    return computeConnectionPointEllipse(angle);
377✔
121
}
1,161✔
122

123
double SVGNode::computeAngle(const double x, const double y) const {
1,098✔
124
    return atan2(y - _cy, x - _cx);
1,098✔
125
}
126

127
double SVGNode::computeAngle(const pair<double, double>& p) const {
1,098✔
128
    return computeAngle(p.first, p.second);
1,098✔
129
}
130

131
bool SVGNode::isFixedSize() const {
401✔
132
    return AttributeUtils::parseBool(getAttribute(ATTR_KEY_FIXED_SIZE));
401✔
133
}
134

135
void SVGNode::updateNodeSize(const double width, const double height) {
401✔
136
    if (isFixedSize()) {
401✔
137
        setDoubleAttributeIfNotExist(ATTR_KEY_WIDTH, AttributeUtils::pointToInch(width));
46✔
138
        setDoubleAttributeIfNotExist(ATTR_KEY_HEIGHT, AttributeUtils::pointToInch(height));
46✔
139
    } else {
140
        setWidth(width);
355✔
141
        setHeight(height);
355✔
142
    }
143
}
401✔
144

145
void SVGNode::updateNodeSize(const pair<double, double>& size) {
53✔
146
    updateNodeSize(size.first, size.second);
53✔
147
}
53✔
148

149
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
383✔
150
    appendSVGDrawsLabelWithCenter(svgDraws, _cx, _cy);
383✔
151
}
383✔
152

153
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
3✔
154
    vector<unique_ptr<SVGDraw>> svgDraws;
3✔
155
    appendSVGDrawsLabel(svgDraws);
3✔
156
    return svgDraws;
6✔
157
}
3✔
158

159
void SVGNode::adjustNodeSizeCircle() {
172✔
160
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
172✔
161
    updateNodeSize(diameter, diameter);
172✔
162
}
172✔
163

164
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
140✔
165
    vector<unique_ptr<SVGDraw>> svgDraws;
140✔
166
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, max(width(), height()) / 2.0);
140✔
167
    setStrokeStyles(circle.get());
140✔
168
    setFillStyles(circle.get(), svgDraws);
140✔
169
    svgDraws.emplace_back(std::move(circle));
140✔
170
    appendSVGDrawsLabel(svgDraws);
140✔
171
    return svgDraws;
280✔
172
}
140✔
173

174
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
283✔
175
    const double radius = (width() + penWidth()) / 2.0;
283✔
176
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
283✔
177
}
178

179
void SVGNode::adjustNodeSizeDoubleCircle() {
32✔
180
    adjustNodeSizeCircle();
32✔
181
}
32✔
182

183
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsDoubleCircle() {
32✔
184
    vector<unique_ptr<SVGDraw>> svgDraws;
32✔
185
    const double radius = max(width(), height()) / 2.0;
32✔
186
    auto circleInner = make_unique<SVGDrawCircle>(_cx, _cy, radius);
32✔
187
    setStrokeStyles(circleInner.get());
32✔
188
    setFillStyles(circleInner.get(), svgDraws);
32✔
189
    svgDraws.emplace_back(std::move(circleInner));
32✔
190
    auto circleOuter = make_unique<SVGDrawCircle>(_cx, _cy, radius + DOUBLE_BORDER_MARGIN);
32✔
191
    setStrokeStyles(circleOuter.get());
32✔
192
    circleOuter->setFill("none");
64✔
193
    svgDraws.emplace_back(std::move(circleOuter));
32✔
194
    appendSVGDrawsLabel(svgDraws);
32✔
195
    return svgDraws;
64✔
196
}
32✔
197

198
std::pair<double, double> SVGNode::computeConnectionPointDoubleCircle(const double angle) const {
199✔
199
    const double radius = (width() + penWidth()) / 2.0 + DOUBLE_BORDER_MARGIN;
199✔
200
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
199✔
201
}
202

203
void SVGNode::adjustNodeSizeRect() {
53✔
204
    updateNodeSize(computeTextSizeWithMargin());
53✔
205
}
53✔
206

207
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
50✔
208
    vector<unique_ptr<SVGDraw>> svgDraws;
50✔
209
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width(), height());
50✔
210
    setStrokeStyles(rect.get());
50✔
211
    setFillStyles(rect.get(), svgDraws);
50✔
212
    svgDraws.emplace_back(std::move(rect));
50✔
213
    appendSVGDrawsLabel(svgDraws);
50✔
214
    return svgDraws;
100✔
215
}
50✔
216

217
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
302✔
218
    const double strokeWidth = penWidth();
302✔
219
    const double totalWidth = width() + strokeWidth;
302✔
220
    const double totalHeight = height() + strokeWidth;
302✔
221
    double x1 = -totalWidth / 2, y1 = -totalHeight / 2;
302✔
222
    double x2 = totalWidth / 2, y2 = totalHeight / 2;
302✔
223
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
604✔
224
    for (const auto& [x, y] : vertices) {
1,498✔
225
        if (GeometryUtils::isSameAngle(angle, x, y)) {
1,200✔
226
            return {_cx + x, _cy + y};
4✔
227
        }
228
    }
229
    double x = 0.0, y = 0.0;
298✔
230
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
1,490✔
231
        x1 = vertices[i].first;
1,192✔
232
        y1 = vertices[i].second;
1,192✔
233
        x2 = vertices[(i + 1) % vertices.size()].first;
1,192✔
234
        y2 = vertices[(i + 1) % vertices.size()].second;
1,192✔
235
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
1,192✔
236
            x = _cx + intersect.value().first;
298✔
237
            y = _cy + intersect.value().second;
298✔
238
        }
239
    }
240
    return {x, y};
298✔
241
}
302✔
242

243
void SVGNode::adjustNodeSizeEllipse() {
158✔
244
    const auto [textWidth, textHeight] = computeTextSize();
158✔
245
    const auto [marginX, marginY] = computeMargin();
158✔
246
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
158✔
247
}
158✔
248

249
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
158✔
250
    vector<unique_ptr<SVGDraw>> svgDraws;
158✔
251
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
158✔
252
    setStrokeStyles(ellipse.get());
158✔
253
    setFillStyles(ellipse.get(), svgDraws);
158✔
254
    svgDraws.emplace_back(std::move(ellipse));
158✔
255
    appendSVGDrawsLabel(svgDraws);
158✔
256
    return svgDraws;
316✔
257
}
158✔
258

259
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
377✔
260
    const double strokeWidth = penWidth();
377✔
261
    const double totalWidth = width() + strokeWidth;
377✔
262
    const double totalHeight = height() + strokeWidth;
377✔
263
    const double rx = totalWidth / 2, ry = totalHeight / 2;
377✔
264
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
377✔
265
    const double x = rx * ry * cos(angle) / base;
377✔
266
    const double y = rx * ry * sin(angle) / base;
377✔
267
    return {_cx + x, _cy + y};
377✔
268
}
269

270
void SVGNode::adjustNodeSizeRecord() {
18✔
271
    _recordSizes.clear();
18✔
272
    function<pair<double, double>(const unique_ptr<RecordLabel>&, bool)> adjustRecordSize;
18✔
273
    adjustRecordSize = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal) -> pair<double, double> {
134✔
274
        if (recordLabel->children.empty()) {
98✔
275
            const SVGTextSize textSize;
57✔
276
            const double fontSize = stod(getAttribute(ATTR_KEY_FONT_SIZE));
57✔
277
            const string fontFamily = getAttribute(ATTR_KEY_FONT_NAME);
57✔
278
            auto [width, height] = textSize.computeTextSize(recordLabel->label, fontSize, fontFamily);
57✔
279
            if (width == 0.0) {
57✔
NEW
280
                width = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_WIDTH_SCALE;
×
281
            }
282
            if (height == 0.0) {
57✔
NEW
283
                height = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_HEIGHT_SCALE;
×
284
            }
285
            const auto [marginX, marginY] = computeMargin();
57✔
286
            width += marginX * 2;
57✔
287
            height += marginY * 2;
57✔
288
            _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
57✔
289
            return {width, height};
57✔
290
        }
57✔
291
        double width = 0.0, height = 0.0;
41✔
292
        for (const auto& child : recordLabel->children) {
121✔
293
            const auto [subWidth, subHeight] = adjustRecordSize(child, !horizontal);
80✔
294
            if (horizontal) {
80✔
295
                width += subWidth;
46✔
296
                height = max(height, subHeight);
46✔
297
            } else {
298
                height += subHeight;
34✔
299
                width = max(width, subWidth);
34✔
300
            }
301
        }
302
        _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
41✔
303
        return {width, height};
41✔
304
    };
18✔
305
    _recordLabel = AttributeUtils::parseRecordLabel(getAttribute(ATTR_KEY_LABEL));
18✔
306
    const auto [width, height] = adjustRecordSize(_recordLabel, true);
18✔
307
    updateNodeSize(width, height);
18✔
308
}
18✔
309

310
std::vector<std::unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRecord() {
18✔
311
    const auto nodeWidth = width();
18✔
312
    const auto nodeHeight = height();
18✔
313
    vector<unique_ptr<SVGDraw>> svgDraws;
18✔
314
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, nodeWidth, nodeHeight);
18✔
315
    setStrokeStyles(rect.get());
18✔
316
    setFillStyles(rect.get(), svgDraws);
18✔
317
    svgDraws.emplace_back(std::move(rect));
18✔
318

319
    function<void(const unique_ptr<RecordLabel>&, bool, double, double, double, double)> drawRecordLabel;
18✔
NEW
320
    drawRecordLabel = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal, double x1, double y1, const double x2, const double y2) {
×
321
        if (recordLabel->children.empty()) {
98✔
322
            const double cx = (x1 + x2) / 2;
57✔
323
            const double cy = (y1 + y2) / 2;
57✔
324
            appendSVGDrawsLabelWithCenter(svgDraws, recordLabel->label, cx, cy);
57✔
325
            return;
57✔
326
        }
327
        bool first = true;
41✔
328
        for (const auto& child : recordLabel->children) {
121✔
329
            if (first) {
80✔
330
                first = false;
41✔
331
            } else {
332
                if (horizontal) {
39✔
333
                    auto line = make_unique<SVGDrawLine>(x1, y1, x1, y2);
19✔
334
                    setStrokeStyles(line.get());
19✔
335
                    svgDraws.emplace_back(std::move(line));
19✔
336
                } else {
19✔
337
                    auto line = make_unique<SVGDrawLine>(x1, y1, x2, y1);
20✔
338
                    setStrokeStyles(line.get());
20✔
339
                    svgDraws.emplace_back(std::move(line));
20✔
340
                }
20✔
341
            }
342
            const auto [subWidth, subHeight] = _recordSizes[reinterpret_cast<uintptr_t>(child.get())];
80✔
343
            if (horizontal) {
80✔
344
                drawRecordLabel(child, false, x1, y1, x1 + subWidth, y2);
46✔
345
                x1 += subWidth;
46✔
346
            } else {
347
                drawRecordLabel(child, true, x1, y1, x2, y1 + subHeight);
34✔
348
                y1 += subHeight;
34✔
349
            }
350
        }
351
    };
18✔
352
    const double x1 = _cx - nodeWidth / 2, y1 = _cy - nodeHeight / 2;
18✔
353
    const double x2 = _cx + nodeWidth / 2, y2 = _cy + nodeHeight / 2;
18✔
354
    drawRecordLabel(_recordLabel, true, x1, y1, x2, y2);
18✔
355

356
    return svgDraws;
36✔
357
}
18✔
358

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

© 2026 Coveralls, Inc