• 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

97.44
/src/attribute_utils.cpp
1
#include "attribute_utils.h"
2
#include "constants.h"
3

4
#include <format>
5
#include <iostream>
6
#include <algorithm>
7
#include <ranges>
8
using namespace std;
9
using namespace svg_diagram;
10

11
double AttributeUtils::pointToInch(const double points) {
1,055✔
12
    return points / POINTS_PER_INCH;
1,055✔
13
}
14

15
double AttributeUtils::inchToPoint(const double inches) {
3,432✔
16
    return inches * POINTS_PER_INCH;
3,432✔
17
}
18

19
double AttributeUtils::centimeterToInch(const double centimeters) {
1✔
20
    return centimeters / CENTIMETERS_PER_INCH;
1✔
21
}
22

23
bool AttributeUtils::isPartOfDouble(const char ch) {
77,507✔
24
    return std::isdigit(ch) ||
9,197✔
25
           ch == '+' || ch == '-' ||
9,197✔
26
           ch == '.' || ch == 'e' || ch == 'E';
86,704✔
27
}
28

29
vector<string> AttributeUtils::splitString(const string& str, const char delimiter) {
665✔
30
    vector<string> splits;
665✔
31
    size_t last = 0, pos = 0;
665✔
32
    while ((pos = str.find(delimiter, last)) != string::npos) {
990✔
33
        splits.push_back(str.substr(last, pos - last));
325✔
34
        last = pos + 1;
325✔
35
    }
36
    splits.push_back(str.substr(last));
665✔
37
    return splits;
665✔
NEW
38
}
×
39

40
vector<string> AttributeUtils::splitString(const string& str, const string& delimiter) {
1✔
41
    vector<string> splits;
1✔
42
    size_t last = 0, pos = 0;
1✔
43
    while ((pos = str.find(delimiter, last)) != string::npos) {
3✔
44
        splits.push_back(str.substr(last, pos - last));
2✔
45
        last = pos + 1;
2✔
46
    }
47
    splits.push_back(str.substr(last));
1✔
48
    return splits;
1✔
NEW
49
}
×
50

51
double AttributeUtils::parseLengthToInch(const string& s) {
1,195✔
52
    int numberStart = -1;
1,195✔
53
    for (int i = 0; i < static_cast<int>(s.length()); ++i) {
1,197✔
54
        if (isPartOfDouble(s[i])) {
1,196✔
55
            numberStart = i;
1,194✔
56
            break;
1,194✔
57
        }
58
    }
59
    if (numberStart == -1) {
1,195✔
60
        throw runtime_error(format("Could not parse length: {}", s));
1✔
61
    }
62
    int numberEnd = static_cast<int>(s.length());
1,194✔
63
    for (int i = numberStart + 1; i < static_cast<int>(s.length()); ++i) {
18,706✔
64
        if (!isPartOfDouble(s[i])) {
17,515✔
65
            numberEnd = i;
3✔
66
            break;
3✔
67
        }
68
    }
69
    const double value = stod(s.substr(numberStart, numberEnd - numberStart));
1,194✔
70
    for (int i = numberEnd; i + 1 < static_cast<int>(s.length()); ++i) {
1,197✔
71
        if (s[i] == 'i' && s[i + 1] == 'n') {
6✔
72
            return value;
1✔
73
        }
74
        if (s[i] == 'p' && s[i + 1] == 't') {
5✔
75
            return pointToInch(value);
1✔
76
        }
77
        if (s[i] == 'c' && s[i + 1] == 'm') {
4✔
78
            return centimeterToInch(value);
1✔
79
        }
80
    }
81
    return value;
1,191✔
82
}
83

84
pair<double, double> AttributeUtils::parseMarginToInches(const string& margin) {
619✔
85
    if (const auto pos = margin.find(','); pos != string::npos) {
619✔
86
        const string left  = margin.substr(0, pos);
572✔
87
        const string right = margin.substr(pos + 1);
572✔
88
        return {parseLengthToInch(left), parseLengthToInch(right)};
572✔
89
    }
572✔
90
    double m = parseLengthToInch(margin);
47✔
91
    return {m, m};
46✔
92
}
93

94
pair<double, double> AttributeUtils::parseMargin(const string& margin) {
616✔
95
    const auto [width, height] = parseMarginToInches(margin);
616✔
96
    return {inchToPoint(width), inchToPoint(height)};
616✔
97
}
98

99
bool AttributeUtils::parseBool(const string& value) {
287✔
100
    if (value.empty()) {
287✔
101
        return false;
265✔
102
    }
103
    string lower = value;
22✔
104
    ranges::transform(lower, lower.begin(), [](const unsigned char c) {
22✔
105
        return tolower(c);
45✔
106
    });
107
    return lower == "true" || lower == "1" || lower == "on" || lower == "yes";
22✔
108
}
22✔
109

110
AttributeParsedStyle AttributeUtils::parseStyle(const string& value) {
662✔
111
    AttributeParsedStyle parsed;
662✔
112
    for (const auto styles = splitString(value, ','); const auto& style : styles) {
1,645✔
113
        if (style == ATTR_STYLE_SOLID) {
983✔
114
            parsed.solid = true;
286✔
115
            parsed.dashed = false;
286✔
116
            parsed.dotted = false;
286✔
117
        } else if (style == ATTR_STYLE_DASHED) {
697✔
118
            parsed.solid = false;
19✔
119
            parsed.dashed = true;
19✔
120
            parsed.dotted = false;
19✔
121
        } else if (style == ATTR_STYLE_DOTTED) {
678✔
122
            parsed.solid = false;
16✔
123
            parsed.dashed = false;
16✔
124
            parsed.dotted = true;
16✔
125
        }
126
    }
662✔
127
    return parsed;
662✔
128
}
129

130
AttributeUtils::DCommands AttributeUtils::parseDCommands(const string& d) {
197✔
131
    auto readDouble = [&](const int start) -> pair<int, double> {
3,281✔
132
        int end = static_cast<int>(d.size());
3,281✔
133
        for (int i = start + 1; i < static_cast<int>(d.size()); ++i) {
55,028✔
134
            if (!isPartOfDouble(d[i])) {
54,832✔
135
                end = i;
3,085✔
136
                break;
3,085✔
137
            }
138
        }
139
        return {end, stod(d.substr(start, end - start))};
3,281✔
140
    };
197✔
141
    vector<pair<char, vector<double>>> commands;
197✔
142
    for (int i = 0; i < static_cast<int>(d.size()); ++i) {
7,927✔
143
        if (d[i] == ' ' || d[i] == ',' || d[i] == '\n' || d[i] == '\r' || d[i] == '\t') {
7,730✔
144
            continue;
3,766✔
145
        }
146
        if (isPartOfDouble(d[i])) {
3,964✔
147
            auto [next, value] = readDouble(i);
3,281✔
148
            commands[commands.size() - 1].second.push_back(value);
3,281✔
149
            i = next - 1;
3,281✔
150
        } else {
151
            commands.push_back({d[i], {}});
683✔
152
        }
153
    }
154
    return commands;
394✔
155
}
×
156

157
std::vector<std::pair<double, double>> AttributeUtils::computeDPathPoints(const DCommands& commands) {
100✔
158
    std::vector<std::pair<double, double>> points;
100✔
159
    for (const auto& [command, parameters] : commands) {
448✔
160
        switch (command) {
348✔
161
            case 'M':  // Move to absolute
339✔
162
            case 'L':  // Line to absolute
163
            case 'C':  // Draw a cubic Bézier curve to absolute
164
            case 'S':  // Draw a smooth cubic Bézier curve to absolute
165
            case 'Q':  // Draw a quadratic Bézier curve to absolute
166
            case 'T':  // Draw a smooth quadratic Bézier curve to absolute
167
                for (int i = 0; i < static_cast<int>(parameters.size()); i += 2) {
1,154✔
168
                    if (i + 1 < static_cast<int>(parameters.size())) {
815✔
169
                        points.emplace_back(parameters[i], parameters[i + 1]);
815✔
170
                    }
171
                }
172
                break;
339✔
173
            case 'm':  // Move to relative
2✔
174
            case 'l':  // Line to relative
175
            case 'c':  // Draw a cubic Bézier curve to relative
176
            case 's':  // Draw a smooth cubic Bézier curve to relative
177
            case 'q':  // Draw a quadratic Bézier curve to relative
178
            case 't':  // Draw a smooth quadratic Bézier curve to relative
179
                for (int i = 0; i < static_cast<int>(parameters.size()); i += 2) {
6✔
180
                    if (i + 1 < static_cast<int>(parameters.size())) {
4✔
181
                        const auto& [lastX, lastY] = points[points.size() - 1];
4✔
182
                        points.emplace_back(lastX + parameters[i], lastY + parameters[i + 1]);
4✔
183
                    }
184
                }
185
                break;
2✔
186
            case 'H':  // Horizontal line to absolute
1✔
187
                for (const double x : parameters) {
2✔
188
                    const auto lastY = points[points.size() - 1].second;
1✔
189
                    points.emplace_back(x, lastY);
1✔
190
                }
191
                break;
1✔
192
            case 'h':  // Horizontal line to relative
1✔
193
                for (const double dx : parameters) {
3✔
194
                    const auto& [lastX, lastY] = points[points.size() - 1];
2✔
195
                    points.emplace_back(lastX + dx, lastY);
2✔
196
                }
197
                break;
1✔
198
            case 'V':  // Vertical line to absolute
1✔
199
                for (const double y : parameters) {
2✔
200
                    const auto lastX = points[points.size() - 1].first;
1✔
201
                    points.emplace_back(lastX, y);
1✔
202
                }
203
                break;
1✔
204
            case 'v':  // Vertical line to relative
1✔
205
                for (const double dy : parameters) {
4✔
206
                    const auto& [lastX, lastY] = points[points.size() - 1];
3✔
207
                    points.emplace_back(lastX, lastY + dy);
3✔
208
                }
209
                break;
1✔
210
            case 'A':  // Draw an arc curve to absolute
1✔
211
                for (int i = 5; i < static_cast<int>(parameters.size()); i += 7) {
2✔
212
                    if (i + 1 < static_cast<int>(parameters.size())) {
1✔
213
                        points.emplace_back(parameters[i], parameters[i + 1]);
1✔
214
                    }
215
                }
216
                break;
1✔
217
            case 'a':  // Draw an arc curve to relative
1✔
218
                for (int i = 5; i < static_cast<int>(parameters.size()); i += 7) {
2✔
219
                    if (i + 1 < static_cast<int>(parameters.size())) {
1✔
220
                        const auto& [lastX, lastY] = points[points.size() - 1];
1✔
221
                        points.emplace_back(lastX + parameters[i], lastY + parameters[i + 1]);
1✔
222
                    }
223
                }
224
                break;
1✔
225
            case 'Z':  // Close the path
1✔
226
            default:
227
                break;
1✔
228
        }
229
    }
230
    return points;
100✔
231
}
×
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