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

CyberZHG / SVGDiagram / 20907493518

12 Jan 2026 03:51AM UTC coverage: 99.788% (+0.3%) from 99.479%
20907493518

push

github

CyberZHG
Update ES binding for label align

2352 of 2357 relevant lines covered (99.79%)

2103.13 hits per line

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

99.66
/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) {
6,442✔
19
    if (attributes().contains(key)) {
6,442✔
20
        return;
4,149✔
21
    }
22
    if (parent() != nullptr) {
2,293✔
23
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
2,293✔
24
            return;
134✔
25
        }
26
    }
27
    setAttribute(key, value);
2,159✔
28
}
29

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

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

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

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

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

60
void SVGNode::adjustNodeSize() {
451✔
61
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
902✔
62
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
902✔
63
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
451✔
64
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
451✔
65
    if (shape == SHAPE_CIRCLE) {
451✔
66
        adjustNodeSizeCircle();
145✔
67
    } else if (shape == SHAPE_DOUBLE_CIRCLE) {
306✔
68
        adjustNodeSizeDoubleCircle();
34✔
69
    } else if (shape == SHAPE_NONE || shape == SHAPE_RECT) {
272✔
70
        adjustNodeSizeRect();
58✔
71
    } else if (shape == SHAPE_ELLIPSE) {
214✔
72
        adjustNodeSizeEllipse();
159✔
73
    } else if (shape == SHAPE_RECORD) {
55✔
74
        adjustNodeSizeRecord();
55✔
75
    }
76
}
451✔
77

78
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
451✔
79
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
902✔
80
    setAttributeIfNotExist(ATTR_KEY_COLOR, string(ATTR_DEF_COLOR));
902✔
81
    setAttributeIfNotExist(ATTR_KEY_FILL_COLOR, string(ATTR_DEF_FILL_COLOR));
902✔
82
    setAttributeIfNotExist(ATTR_KEY_FONT_COLOR, string(ATTR_DEF_FONT_COLOR));
902✔
83
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
902✔
84
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
902✔
85
    setAttributeIfNotExist(ATTR_KEY_STYLE, string(ATTR_DEF_STYLE));
451✔
86
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
451✔
87
    if (shape == SHAPE_CIRCLE) {
451✔
88
        return produceSVGDrawsCircle();
145✔
89
    }
90
    if (shape == SHAPE_DOUBLE_CIRCLE) {
306✔
91
        return produceSVGDrawsDoubleCircle();
34✔
92
    }
93
    if (shape == SHAPE_RECT) {
272✔
94
        return produceSVGDrawsRect();
55✔
95
    }
96
    if (shape == SHAPE_ELLIPSE) {
217✔
97
        return produceSVGDrawsEllipse();
159✔
98
    }
99
    if (shape == SHAPE_RECORD) {
58✔
100
        return produceSVGDrawsRecord();
55✔
101
    }
102
    return produceSVGDrawsNone();
3✔
103
}
451✔
104

105
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
1,169✔
106
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,169✔
107
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,169✔
108
    if (shape == SHAPE_CIRCLE) {
1,169✔
109
        return computeConnectionPointCircle(angle);
328✔
110
    }
111
    if (shape == SHAPE_DOUBLE_CIRCLE) {
841✔
112
        return computeConnectionPointDoubleCircle(angle);
226✔
113
    }
114
    if (shape == SHAPE_RECT || shape == SHAPE_NONE) {
615✔
115
        return computeConnectionPointRect(angle);
231✔
116
    }
117
    if (shape == SHAPE_RECORD) {
384✔
118
        return computeConnectionPointRecord(angle);
33✔
119
    }
120
    return computeConnectionPointEllipse(angle);
351✔
121
}
1,169✔
122

123
pair<double, double> SVGNode::computeFieldConnectionPoint(const string& fieldId, const double angle) {
1,189✔
124
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,189✔
125
    if (shape != SHAPE_RECORD || !_recordPositions.contains(fieldId)) {
1,189✔
126
        return computeConnectionPoint(angle);
1,169✔
127
    }
128
    const auto& [cx, cy, width, height] = _recordPositions.at(fieldId);
20✔
129
    return computeConnectionPointRect(cx, cy, width, height, angle);
20✔
130
}
1,189✔
131

132
double SVGNode::computeAngle(const double x, const double y) const {
1,106✔
133
    return atan2(y - _cy, x - _cx);
1,106✔
134
}
135

136
double SVGNode::computeAngle(const pair<double, double>& p) const {
1,106✔
137
    return computeAngle(p.first, p.second);
1,106✔
138
}
139

140
double SVGNode::computeFieldAngle(const string &fieldId, const pair<double, double>& p) const {
1,120✔
141
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,120✔
142
    if (shape != SHAPE_RECORD || !_recordPositions.contains(fieldId)) {
1,120✔
143
        return computeAngle(p);
1,106✔
144
    }
145
    const auto& [cx, cy, width, height] = _recordPositions.at(fieldId);
14✔
146
    return atan2(p.second - cy, p.first - cx);
14✔
147
}
1,120✔
148

149
bool SVGNode::isFixedSize() const {
451✔
150
    return AttributeUtils::parseBool(getAttribute(ATTR_KEY_FIXED_SIZE));
451✔
151
}
152

153
void SVGNode::updateNodeSize(const double width, const double height) {
451✔
154
    if (isFixedSize()) {
451✔
155
        setDoubleAttributeIfNotExist(ATTR_KEY_WIDTH, AttributeUtils::pointToInch(width));
46✔
156
        setDoubleAttributeIfNotExist(ATTR_KEY_HEIGHT, AttributeUtils::pointToInch(height));
46✔
157
    } else {
158
        setWidth(width);
405✔
159
        setHeight(height);
405✔
160
    }
161
}
451✔
162

163
void SVGNode::updateNodeSize(const pair<double, double>& size) {
58✔
164
    updateNodeSize(size.first, size.second);
58✔
165
}
58✔
166

167
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
396✔
168
    appendSVGDrawsLabelWithLocation(svgDraws, _cx, _cy);
396✔
169
}
396✔
170

171
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
3✔
172
    vector<unique_ptr<SVGDraw>> svgDraws;
3✔
173
    appendSVGDrawsLabel(svgDraws);
3✔
174
    return svgDraws;
6✔
175
}
3✔
176

177
void SVGNode::adjustNodeSizeCircle() {
179✔
178
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
179✔
179
    updateNodeSize(diameter, diameter);
179✔
180
}
179✔
181

182
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
145✔
183
    vector<unique_ptr<SVGDraw>> svgDraws;
145✔
184
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, max(width(), height()) / 2.0);
145✔
185
    setStrokeStyles(circle.get());
145✔
186
    setFillStyles(circle.get(), svgDraws);
145✔
187
    svgDraws.emplace_back(std::move(circle));
145✔
188
    appendSVGDrawsLabel(svgDraws);
145✔
189
    return svgDraws;
290✔
190
}
145✔
191

192
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
328✔
193
    const double radius = (width() + penWidth()) / 2.0;
328✔
194
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
328✔
195
}
196

197
void SVGNode::adjustNodeSizeDoubleCircle() {
34✔
198
    adjustNodeSizeCircle();
34✔
199
}
34✔
200

201
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsDoubleCircle() {
34✔
202
    vector<unique_ptr<SVGDraw>> svgDraws;
34✔
203
    const double radius = max(width(), height()) / 2.0;
34✔
204
    auto circleInner = make_unique<SVGDrawCircle>(_cx, _cy, radius);
34✔
205
    setStrokeStyles(circleInner.get());
34✔
206
    setFillStyles(circleInner.get(), svgDraws);
34✔
207
    svgDraws.emplace_back(std::move(circleInner));
34✔
208
    auto circleOuter = make_unique<SVGDrawCircle>(_cx, _cy, radius + DOUBLE_BORDER_MARGIN);
34✔
209
    setStrokeStyles(circleOuter.get());
34✔
210
    circleOuter->setFill("none");
68✔
211
    svgDraws.emplace_back(std::move(circleOuter));
34✔
212
    appendSVGDrawsLabel(svgDraws);
34✔
213
    return svgDraws;
68✔
214
}
34✔
215

216
std::pair<double, double> SVGNode::computeConnectionPointDoubleCircle(const double angle) const {
226✔
217
    const double radius = (width() + penWidth()) / 2.0 + DOUBLE_BORDER_MARGIN;
226✔
218
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
226✔
219
}
220

221
void SVGNode::adjustNodeSizeRect() {
58✔
222
    updateNodeSize(computeTextSizeWithMargin());
58✔
223
}
58✔
224

225
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
55✔
226
    vector<unique_ptr<SVGDraw>> svgDraws;
55✔
227
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width(), height());
55✔
228
    setStrokeStyles(rect.get());
55✔
229
    setFillStyles(rect.get(), svgDraws);
55✔
230
    svgDraws.emplace_back(std::move(rect));
55✔
231
    appendSVGDrawsLabel(svgDraws);
55✔
232
    return svgDraws;
110✔
233
}
55✔
234

235
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
264✔
236
    return computeConnectionPointRect(_cx, _cy, width(), height(), angle);
264✔
237
}
238

239
pair<double, double> SVGNode::computeConnectionPointRect(const double cx, const double cy, const double width, const double height, const double angle) const {
284✔
240
    const double strokeWidth = penWidth();
284✔
241
    const double totalWidth = width + strokeWidth;
284✔
242
    const double totalHeight = height + strokeWidth;
284✔
243
    double x1 = -totalWidth / 2, y1 = -totalHeight / 2;
284✔
244
    double x2 = totalWidth / 2, y2 = totalHeight / 2;
284✔
245
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
568✔
246
    for (const auto& [x, y] : vertices) {
1,408✔
247
        if (GeometryUtils::isSameAngle(angle, x, y)) {
1,128✔
248
            return {cx + x, cy + y};
4✔
249
        }
250
    }
251
    double x = 0.0, y = 0.0;
280✔
252
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
1,400✔
253
        x1 = vertices[i].first;
1,120✔
254
        y1 = vertices[i].second;
1,120✔
255
        x2 = vertices[(i + 1) % vertices.size()].first;
1,120✔
256
        y2 = vertices[(i + 1) % vertices.size()].second;
1,120✔
257
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
1,120✔
258
            x = _cx + intersect.value().first;
280✔
259
            y = _cy + intersect.value().second;
280✔
260
        }
261
    }
262
    return {x, y};
280✔
263
}
284✔
264

265
void SVGNode::adjustNodeSizeEllipse() {
159✔
266
    const auto [textWidth, textHeight] = computeTextSize();
159✔
267
    const auto [marginX, marginY] = computeMargin();
159✔
268
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
159✔
269
}
159✔
270

271
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
159✔
272
    vector<unique_ptr<SVGDraw>> svgDraws;
159✔
273
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
159✔
274
    setStrokeStyles(ellipse.get());
159✔
275
    setFillStyles(ellipse.get(), svgDraws);
159✔
276
    svgDraws.emplace_back(std::move(ellipse));
159✔
277
    appendSVGDrawsLabel(svgDraws);
159✔
278
    return svgDraws;
318✔
279
}
159✔
280

281
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
351✔
282
    const double strokeWidth = penWidth();
351✔
283
    const double totalWidth = width() + strokeWidth;
351✔
284
    const double totalHeight = height() + strokeWidth;
351✔
285
    const double rx = totalWidth / 2, ry = totalHeight / 2;
351✔
286
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
351✔
287
    const double x = rx * ry * cos(angle) / base;
351✔
288
    const double y = rx * ry * sin(angle) / base;
351✔
289
    return {_cx + x, _cy + y};
351✔
290
}
291

292
void SVGNode::adjustNodeSizeRecord() {
55✔
293
    _recordSizes.clear();
55✔
294
    function<pair<double, double>(const unique_ptr<RecordLabel>&, bool)> adjustRecordSize;
55✔
295
    adjustRecordSize = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal) -> pair<double, double> {
438✔
296
        if (recordLabel->children.empty()) {
328✔
297
            const SVGTextSize textSize;
204✔
298
            const double fontSize = stod(getAttribute(ATTR_KEY_FONT_SIZE));
204✔
299
            const string fontFamily = getAttribute(ATTR_KEY_FONT_NAME);
204✔
300
            auto [width, height] = textSize.computeTextSize(recordLabel->label, fontSize, fontFamily);
204✔
301
            if (width == 0.0) {
204✔
302
                width = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_WIDTH_SCALE;
89✔
303
            }
304
            if (height == 0.0) {
204✔
305
                height = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_HEIGHT_SCALE;
89✔
306
            }
307
            const auto [marginX, marginY] = computeMargin();
204✔
308
            width += marginX * 2;
204✔
309
            height += marginY * 2;
204✔
310
            _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
204✔
311
            return {width, height};
204✔
312
        }
204✔
313
        double width = 0.0, height = 0.0;
124✔
314
        for (const auto& child : recordLabel->children) {
397✔
315
            const auto [subWidth, subHeight] = adjustRecordSize(child, !horizontal);
273✔
316
            if (horizontal) {
273✔
317
                width += subWidth;
153✔
318
                height = max(height, subHeight);
153✔
319
            } else {
320
                height += subHeight;
120✔
321
                width = max(width, subWidth);
120✔
322
            }
323
        }
324
        _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
124✔
325
        return {width, height};
124✔
326
    };
55✔
327
    _recordLabel = AttributeUtils::parseRecordLabel(getAttribute(ATTR_KEY_LABEL));
55✔
328
    const auto [width, height] = adjustRecordSize(_recordLabel, true);
55✔
329
    updateNodeSize(width, height);
55✔
330
}
55✔
331

332
std::vector<std::unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRecord() {
55✔
333
    const auto nodeWidth = width();
55✔
334
    const auto nodeHeight = height();
55✔
335
    vector<unique_ptr<SVGDraw>> svgDraws;
55✔
336
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, nodeWidth, nodeHeight);
55✔
337
    setStrokeStyles(rect.get());
55✔
338
    setFillStyles(rect.get(), svgDraws);
55✔
339
    svgDraws.emplace_back(std::move(rect));
55✔
340
    const auto [marginX, marginY] = margin();
55✔
341

342
    _recordPositions.clear();
55✔
343
    function<void(const unique_ptr<RecordLabel>&, bool, double, double, double, double)> drawRecordLabel;
55✔
344
    drawRecordLabel = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal, double x1, double y1, const double x2, const double y2) {
×
345
        const double cx = (x1 + x2) / 2;
328✔
346
        const double cy = (y1 + y2) / 2;
328✔
347
        if (!recordLabel->fieldId.empty()) {
328✔
348
            _recordPositions[recordLabel->fieldId] = {cx, cy, x2 - x1, y2 - y1};
20✔
349
        }
350
        if (recordLabel->children.empty()) {
328✔
351
            appendSVGDrawsLabelWithLocation(svgDraws, recordLabel->label, cx, cy, x2 - x1 - marginX * 2, y2 - y1 - marginY * 2);
204✔
352
            return;
204✔
353
        }
354
        bool first = true;
124✔
355
        for (const auto& child : recordLabel->children) {
397✔
356
            if (first) {
273✔
357
                first = false;
124✔
358
            } else {
359
                if (horizontal) {
149✔
360
                    auto line = make_unique<SVGDrawLine>(x1, y1, x1, y2);
73✔
361
                    setStrokeStyles(line.get());
73✔
362
                    svgDraws.emplace_back(std::move(line));
73✔
363
                } else {
73✔
364
                    auto line = make_unique<SVGDrawLine>(x1, y1, x2, y1);
76✔
365
                    setStrokeStyles(line.get());
76✔
366
                    svgDraws.emplace_back(std::move(line));
76✔
367
                }
76✔
368
            }
369
            const auto [subWidth, subHeight] = _recordSizes[reinterpret_cast<uintptr_t>(child.get())];
273✔
370
            if (horizontal) {
273✔
371
                drawRecordLabel(child, false, x1, y1, x1 + subWidth, y2);
153✔
372
                x1 += subWidth;
153✔
373
            } else {
374
                drawRecordLabel(child, true, x1, y1, x2, y1 + subHeight);
120✔
375
                y1 += subHeight;
120✔
376
            }
377
        }
378
    };
55✔
379
    const double x1 = _cx - nodeWidth / 2, y1 = _cy - nodeHeight / 2;
55✔
380
    const double x2 = _cx + nodeWidth / 2, y2 = _cy + nodeHeight / 2;
55✔
381
    drawRecordLabel(_recordLabel, true, x1, y1, x2, y2);
55✔
382

383
    return svgDraws;
110✔
384
}
55✔
385

386
std::pair<double, double> SVGNode::computeConnectionPointRecord(const double angle) const {
33✔
387
    return computeConnectionPointRect(angle);
33✔
388
}
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