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

CyberZHG / SVGDiagram / 20449459503

23 Dec 2025 02:20AM UTC coverage: 98.685% (-0.07%) from 98.755%
20449459503

Pull #37

github

web-flow
Merge c3395b0d5 into 97aba17bf
Pull Request #37: Add dashed and dotted styles

917 of 932 new or added lines in 6 files covered. (98.39%)

1801 of 1825 relevant lines covered (98.68%)

1328.95 hits per line

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

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

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

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

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

17
void SVGNode::setAttributeIfNotExist(const string_view &key, const string &value) {
3,654✔
18
    if (attributes().contains(key)) {
3,654✔
19
        return;
2,149✔
20
    }
21
    if (parent() != nullptr) {
1,505✔
22
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
1,505✔
23
            return;
106✔
24
        }
25
    }
26
    setAttribute(key, value);
1,399✔
27
}
28

29
const string& SVGNode::getAttribute(const std::string_view& key) const {
7,938✔
30
    static const string EMPTY_STRING;
7,938✔
31
    if (const auto it = attributes().find(key); it != attributes().end()) {
7,938✔
32
        return it->second;
6,351✔
33
    }
34
    if (parent() != nullptr) {
1,587✔
35
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
1,587✔
36
            return ret.value();
146✔
37
        }
38
    }
39
    return EMPTY_STRING;
1,441✔
40
}
41

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

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

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

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

59
void SVGNode::adjustNodeSize() {
285✔
60
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
570✔
61
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
570✔
62
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
285✔
63
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
285✔
64
    if (shape == SHAPE_CIRCLE) {
285✔
65
        adjustNodeSizeCircle();
123✔
66
    } else if (shape == SHAPE_DOUBLE_CIRCLE) {
162✔
67
        adjustNodeSizeDoubleCircle();
15✔
68
    } else if (shape == SHAPE_NONE || shape == SHAPE_RECT) {
147✔
69
        adjustNodeSizeRect();
45✔
70
    } else if (shape == SHAPE_ELLIPSE) {
102✔
71
        adjustNodeSizeEllipse();
102✔
72
    }
73
}
285✔
74

75
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
285✔
76
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
570✔
77
    setAttributeIfNotExist(ATTR_KEY_COLOR, string(ATTR_DEF_COLOR));
570✔
78
    setAttributeIfNotExist(ATTR_KEY_FILL_COLOR, string(ATTR_DEF_FILL_COLOR));
570✔
79
    setAttributeIfNotExist(ATTR_KEY_FONT_COLOR, string(ATTR_DEF_FONT_COLOR));
570✔
80
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
570✔
81
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
570✔
82
    setAttributeIfNotExist(ATTR_KEY_STYLE, string(ATTR_DEF_STYLE));
285✔
83
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
285✔
84
    if (shape == SHAPE_CIRCLE) {
285✔
85
        return produceSVGDrawsCircle();
123✔
86
    }
87
    if (shape == SHAPE_DOUBLE_CIRCLE) {
162✔
88
        return produceSVGDrawsDoubleCircle();
15✔
89
    }
90
    if (shape == SHAPE_RECT) {
147✔
91
        return produceSVGDrawsRect();
42✔
92
    }
93
    if (shape == SHAPE_ELLIPSE) {
105✔
94
        return produceSVGDrawsEllipse();
102✔
95
    }
96
    return produceSVGDrawsNone();
3✔
97
}
285✔
98

99
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
418✔
100
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
418✔
101
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
418✔
102
    if (shape == SHAPE_CIRCLE) {
418✔
103
        return computeConnectionPointCircle(angle);
122✔
104
    }
105
    if (shape == SHAPE_DOUBLE_CIRCLE) {
296✔
106
        return computeConnectionPointDoubleCircle(angle);
6✔
107
    }
108
    if (shape == SHAPE_RECT || shape == SHAPE_NONE) {
290✔
109
        return computeConnectionPointRect(angle);
128✔
110
    }
111
    if (shape == SHAPE_ELLIPSE) {
162✔
112
        return computeConnectionPointEllipse(angle);
162✔
113
    }
NEW
114
    return {0.0, 0.0};
×
115
}
418✔
116

117
double SVGNode::computeAngle(const double x, const double y) const {
418✔
118
    return atan2(y - _cy, x - _cx);
418✔
119
}
120

121
double SVGNode::computeAngle(const pair<double, double>& p) const {
418✔
122
    return computeAngle(p.first, p.second);
418✔
123
}
124

125
bool SVGNode::isFixedSize() const {
285✔
126
    return AttributeUtils::parseBool(getAttribute(ATTR_KEY_FIXED_SIZE));
285✔
127
}
128

129
void SVGNode::updateNodeSize(const double width, const double height) {
285✔
130
    if (isFixedSize()) {
285✔
131
        setDoubleAttributeIfNotExist(ATTR_KEY_WIDTH, AttributeUtils::pointToInch(width));
20✔
132
        setDoubleAttributeIfNotExist(ATTR_KEY_HEIGHT, AttributeUtils::pointToInch(height));
20✔
133
    } else {
134
        setWidth(width);
265✔
135
        setHeight(height);
265✔
136
    }
137
}
285✔
138

139
void SVGNode::updateNodeSize(const pair<double, double>& size) {
45✔
140
    updateNodeSize(size.first, size.second);
45✔
141
}
45✔
142

143
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
285✔
144
    appendSVGDrawsLabelWithCenter(svgDraws, _cx, _cy);
285✔
145
}
285✔
146

147
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
3✔
148
    vector<unique_ptr<SVGDraw>> svgDraws;
3✔
149
    appendSVGDrawsLabel(svgDraws);
3✔
150
    return svgDraws;
3✔
NEW
151
}
×
152

153
void SVGNode::adjustNodeSizeCircle() {
138✔
154
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
138✔
155
    updateNodeSize(diameter, diameter);
138✔
156
}
138✔
157

158
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
123✔
159
    vector<unique_ptr<SVGDraw>> svgDraws;
123✔
160
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, max(width(), height()) / 2.0);
123✔
161
    setStrokeStyles(circle.get());
123✔
162
    circle->setFill(fillColor());
123✔
163
    svgDraws.emplace_back(std::move(circle));
123✔
164
    appendSVGDrawsLabel(svgDraws);
123✔
165
    return svgDraws;
246✔
166
}
123✔
167

168
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
122✔
169
    const double radius = (width() + penWidth()) / 2.0;
122✔
170
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
122✔
171
}
172

173
void SVGNode::adjustNodeSizeDoubleCircle() {
15✔
174
    adjustNodeSizeCircle();
15✔
175
}
15✔
176

177
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsDoubleCircle() {
15✔
178
    vector<unique_ptr<SVGDraw>> svgDraws;
15✔
179
    const double radius = max(width(), height()) / 2.0;
15✔
180
    auto circleInner = make_unique<SVGDrawCircle>(_cx, _cy, radius);
15✔
181
    setStrokeStyles(circleInner.get());
15✔
182
    circleInner->setFill(fillColor());
15✔
183
    svgDraws.emplace_back(std::move(circleInner));
15✔
184
    auto circleOuter = make_unique<SVGDrawCircle>(_cx, _cy, radius + DOUBLE_BORDER_MARGIN);
15✔
185
    setStrokeStyles(circleOuter.get());
15✔
186
    circleOuter->setFill("none");
30✔
187
    svgDraws.emplace_back(std::move(circleOuter));
15✔
188
    appendSVGDrawsLabel(svgDraws);
15✔
189
    return svgDraws;
30✔
190
}
15✔
191

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

197
void SVGNode::adjustNodeSizeRect() {
45✔
198
    updateNodeSize(computeTextSizeWithMargin());
45✔
199
}
45✔
200

201
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
42✔
202
    vector<unique_ptr<SVGDraw>> svgDraws;
42✔
203
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width(), height());
42✔
204
    setStrokeStyles(rect.get());
42✔
205
    rect->setFill(fillColor());
42✔
206
    svgDraws.emplace_back(std::move(rect));
42✔
207
    appendSVGDrawsLabel(svgDraws);
42✔
208
    return svgDraws;
84✔
209
}
42✔
210

211
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
128✔
212
    const double strokeWidth = penWidth();
128✔
213
    const double totalWidth = width() + strokeWidth;
128✔
214
    const double totalHeight = height() + strokeWidth;
128✔
215
    double x1 = -totalWidth / 2, y1 = -totalHeight / 2;
128✔
216
    double x2 = totalWidth / 2, y2 = totalHeight / 2;
128✔
217
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
256✔
218
    for (const auto& [x, y] : vertices) {
628✔
219
        if (GeometryUtils::isSameAngle(angle, x, y)) {
504✔
220
            return {_cx + x, _cy + y};
4✔
221
        }
222
    }
223
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
282✔
224
        x1 = vertices[i].first;
282✔
225
        y1 = vertices[i].second;
282✔
226
        x2 = vertices[(i + 1) % vertices.size()].first;
282✔
227
        y2 = vertices[(i + 1) % vertices.size()].second;
282✔
228
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
282✔
229
            return {_cx + intersect.value().first, _cy + intersect.value().second};
124✔
230
        }
231
    }
NEW
232
    return {_cx, _cy};
×
233
}
128✔
234

235
void SVGNode::adjustNodeSizeEllipse() {
102✔
236
    const auto [textWidth, textHeight] = computeTextSize();
102✔
237
    const auto [marginX, marginY] = computeMargin();
102✔
238
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
102✔
239
}
102✔
240

241
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
102✔
242
    vector<unique_ptr<SVGDraw>> svgDraws;
102✔
243
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
102✔
244
    setStrokeStyles(ellipse.get());
102✔
245
    ellipse->setFill(fillColor());
102✔
246
    svgDraws.emplace_back(std::move(ellipse));
102✔
247
    appendSVGDrawsLabel(svgDraws);
102✔
248
    return svgDraws;
204✔
249
}
102✔
250

251
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
162✔
252
    const double strokeWidth = penWidth();
162✔
253
    const double totalWidth = width() + strokeWidth;
162✔
254
    const double totalHeight = height() + strokeWidth;
162✔
255
    const double rx = totalWidth / 2, ry = totalHeight / 2;
162✔
256
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
162✔
257
    const double x = rx * ry * cos(angle) / base;
162✔
258
    const double y = rx * ry * sin(angle) / base;
162✔
259
    return {_cx + x, _cy + y};
162✔
260
}
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