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

CyberZHG / SVGDiagram / 20452640503

23 Dec 2025 05:48AM UTC coverage: 99.048% (+0.4%) from 98.685%
20452640503

Pull #38

github

web-flow
Merge c566ec9ed into b5ed953e3
Pull Request #38: Enable opacity for stroke and fill

80 of 82 new or added lines in 5 files covered. (97.56%)

1872 of 1890 relevant lines covered (99.05%)

1341.68 hits per line

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

96.98
/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,081✔
12
    return points / POINTS_PER_INCH;
1,081✔
13
}
14

15
double AttributeUtils::inchToPoint(const double inches) {
3,488✔
16
    return inches * POINTS_PER_INCH;
3,488✔
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,871✔
24
    return std::isdigit(ch) ||
9,217✔
25
           ch == '+' || ch == '-' ||
9,217✔
26
           ch == '.' || ch == 'e' || ch == 'E';
87,088✔
27
}
28

29
string AttributeUtils::removeSpaces(const string& str) {
1,533✔
30
    string result;
1,533✔
31
    for (const auto ch : str) {
10,195✔
32
        if (!std::isspace(ch)) {
8,662✔
33
            result += ch;
8,662✔
34
        }
35
    }
36
    return result;
1,533✔
NEW
37
}
×
38

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

50
vector<string> AttributeUtils::splitString(const string& str, const string& delimiter) {
1✔
51
    vector<string> splits;
1✔
52
    size_t last = 0, pos = 0;
1✔
53
    while ((pos = str.find(delimiter, last)) != string::npos) {
3✔
54
        splits.push_back(str.substr(last, pos - last));
2✔
55
        last = pos + 1;
2✔
56
    }
57
    splits.push_back(str.substr(last));
1✔
58
    return splits;
1✔
59
}
×
60

61
double AttributeUtils::parseLengthToInch(const string& s) {
1,215✔
62
    int numberStart = -1;
1,215✔
63
    for (int i = 0; i < static_cast<int>(s.length()); ++i) {
1,217✔
64
        if (isPartOfDouble(s[i])) {
1,216✔
65
            numberStart = i;
1,214✔
66
            break;
1,214✔
67
        }
68
    }
69
    if (numberStart == -1) {
1,215✔
70
        throw runtime_error(format("Could not parse length: {}", s));
1✔
71
    }
72
    int numberEnd = static_cast<int>(s.length());
1,214✔
73
    for (int i = numberStart + 1; i < static_cast<int>(s.length()); ++i) {
19,070✔
74
        if (!isPartOfDouble(s[i])) {
17,859✔
75
            numberEnd = i;
3✔
76
            break;
3✔
77
        }
78
    }
79
    const double value = stod(s.substr(numberStart, numberEnd - numberStart));
1,214✔
80
    for (int i = numberEnd; i + 1 < static_cast<int>(s.length()); ++i) {
1,217✔
81
        if (s[i] == 'i' && s[i + 1] == 'n') {
6✔
82
            return value;
1✔
83
        }
84
        if (s[i] == 'p' && s[i + 1] == 't') {
5✔
85
            return pointToInch(value);
1✔
86
        }
87
        if (s[i] == 'c' && s[i + 1] == 'm') {
4✔
88
            return centimeterToInch(value);
1✔
89
        }
90
    }
91
    return value;
1,211✔
92
}
93

94
pair<double, double> AttributeUtils::parseMarginToInches(const string& margin) {
629✔
95
    if (const auto pos = margin.find(','); pos != string::npos) {
629✔
96
        const string left  = margin.substr(0, pos);
582✔
97
        const string right = margin.substr(pos + 1);
582✔
98
        return {parseLengthToInch(left), parseLengthToInch(right)};
582✔
99
    }
582✔
100
    double m = parseLengthToInch(margin);
47✔
101
    return {m, m};
46✔
102
}
103

104
pair<double, double> AttributeUtils::parseMargin(const string& margin) {
626✔
105
    const auto [width, height] = parseMarginToInches(margin);
626✔
106
    return {inchToPoint(width), inchToPoint(height)};
626✔
107
}
108

109
bool AttributeUtils::parseBool(const string& value) {
293✔
110
    if (value.empty()) {
293✔
111
        return false;
271✔
112
    }
113
    string lower = value;
22✔
114
    ranges::transform(lower, lower.begin(), [](const unsigned char c) {
22✔
115
        return tolower(c);
45✔
116
    });
117
    return lower == "true" || lower == "1" || lower == "on" || lower == "yes";
22✔
118
}
22✔
119

120
AttributeParsedStyle AttributeUtils::parseStyle(const string& value) {
674✔
121
    AttributeParsedStyle parsed;
674✔
122
    for (const auto styles = splitString(removeSpaces(value), ','); const auto& style : styles) {
1,675✔
123
        if (style == ATTR_STYLE_SOLID) {
1,001✔
124
            parsed.solid = true;
292✔
125
            parsed.dashed = false;
292✔
126
            parsed.dotted = false;
292✔
127
        } else if (style == ATTR_STYLE_DASHED) {
709✔
128
            parsed.solid = false;
19✔
129
            parsed.dashed = true;
19✔
130
            parsed.dotted = false;
19✔
131
        } else if (style == ATTR_STYLE_DOTTED) {
690✔
132
            parsed.solid = false;
16✔
133
            parsed.dashed = false;
16✔
134
            parsed.dotted = true;
16✔
135
        }
136
    }
674✔
137
    return parsed;
674✔
138
}
139

140
vector<AttributeParsedColor> AttributeUtils::parseColorList(const string& value) {
859✔
141
    vector<AttributeParsedColor> colors;
859✔
142
    double totalWeights = 0.0;
859✔
143
    int numNoWeight = 0;
859✔
144
    for (const auto segments = splitString(removeSpaces(value), ':'); const auto& segment : segments) {
1,728✔
145
        AttributeParsedColor color;
869✔
146
        const auto parts = splitString(segment, ';');
869✔
147
        if (parts.size() > 1) {
869✔
148
            color.weight = stod(parts[1]);
9✔
149
            totalWeights += color.weight;
9✔
150
        } else {
151
            ++numNoWeight;
860✔
152
        }
153
        if (parts[0].size() == 9 && parts[0][0] == '#') {
869✔
154
            color.color = parts[0].substr(0, 7);
13✔
155
            color.opacity = stoi(parts[0].substr(7), nullptr, 16) / 255.0;
13✔
156
        } else {
157
            color.color = parts[0];
856✔
158
        }
159
        colors.emplace_back(color);
869✔
160
    }
1,728✔
161
    if (0.0 < totalWeights && totalWeights <= 1.0) {
859✔
162
        if (numNoWeight) {
2✔
163
            const double weight = (1.0 - totalWeights) / numNoWeight;
2✔
164
            for (auto& color : colors) {
8✔
165
                if (color.weight < 0) {
6✔
166
                    color.weight = weight;
3✔
167
                }
168
            }
169
        }
170
    } else if (totalWeights > 1.0) {
859✔
171
        if (numNoWeight) {
3✔
172
            for (auto& color : colors) {
4✔
173
                if (color.weight < 0) {
3✔
174
                    color.weight = 1.0;
1✔
175
                }
176
            }
177
        }
178
    }
179
    if ((totalWeights < 1.0 && numNoWeight == 0) || totalWeights > 1.0) {
859✔
180
        totalWeights = 0.0;
3✔
181
        for (const auto& color : colors) {
10✔
182
            totalWeights += color.weight;
7✔
183
        }
184
        for (auto& color : colors) {
10✔
185
            color.weight /= totalWeights;
7✔
186
        }
187
    }
188
    return colors;
859✔
NEW
189
}
×
190

191
AttributeUtils::DCommands AttributeUtils::parseDCommands(const string& d) {
197✔
192
    auto readDouble = [&](const int start) -> pair<int, double> {
3,281✔
193
        int end = static_cast<int>(d.size());
3,281✔
194
        for (int i = start + 1; i < static_cast<int>(d.size()); ++i) {
55,028✔
195
            if (!isPartOfDouble(d[i])) {
54,832✔
196
                end = i;
3,085✔
197
                break;
3,085✔
198
            }
199
        }
200
        return {end, stod(d.substr(start, end - start))};
3,281✔
201
    };
197✔
202
    vector<pair<char, vector<double>>> commands;
197✔
203
    for (int i = 0; i < static_cast<int>(d.size()); ++i) {
7,927✔
204
        if (d[i] == ' ' || d[i] == ',' || d[i] == '\n' || d[i] == '\r' || d[i] == '\t') {
7,730✔
205
            continue;
3,766✔
206
        }
207
        if (isPartOfDouble(d[i])) {
3,964✔
208
            auto [next, value] = readDouble(i);
3,281✔
209
            commands[commands.size() - 1].second.push_back(value);
3,281✔
210
            i = next - 1;
3,281✔
211
        } else {
212
            commands.push_back({d[i], {}});
683✔
213
        }
214
    }
215
    return commands;
394✔
216
}
×
217

218
std::vector<std::pair<double, double>> AttributeUtils::computeDPathPoints(const DCommands& commands) {
100✔
219
    std::vector<std::pair<double, double>> points;
100✔
220
    for (const auto& [command, parameters] : commands) {
448✔
221
        switch (command) {
348✔
222
            case 'M':  // Move to absolute
339✔
223
            case 'L':  // Line to absolute
224
            case 'C':  // Draw a cubic Bézier curve to absolute
225
            case 'S':  // Draw a smooth cubic Bézier curve to absolute
226
            case 'Q':  // Draw a quadratic Bézier curve to absolute
227
            case 'T':  // Draw a smooth quadratic Bézier curve to absolute
228
                for (int i = 0; i < static_cast<int>(parameters.size()); i += 2) {
1,154✔
229
                    if (i + 1 < static_cast<int>(parameters.size())) {
815✔
230
                        points.emplace_back(parameters[i], parameters[i + 1]);
815✔
231
                    }
232
                }
233
                break;
339✔
234
            case 'm':  // Move to relative
2✔
235
            case 'l':  // Line to relative
236
            case 'c':  // Draw a cubic Bézier curve to relative
237
            case 's':  // Draw a smooth cubic Bézier curve to relative
238
            case 'q':  // Draw a quadratic Bézier curve to relative
239
            case 't':  // Draw a smooth quadratic Bézier curve to relative
240
                for (int i = 0; i < static_cast<int>(parameters.size()); i += 2) {
6✔
241
                    if (i + 1 < static_cast<int>(parameters.size())) {
4✔
242
                        const auto& [lastX, lastY] = points[points.size() - 1];
4✔
243
                        points.emplace_back(lastX + parameters[i], lastY + parameters[i + 1]);
4✔
244
                    }
245
                }
246
                break;
2✔
247
            case 'H':  // Horizontal line to absolute
1✔
248
                for (const double x : parameters) {
2✔
249
                    const auto lastY = points[points.size() - 1].second;
1✔
250
                    points.emplace_back(x, lastY);
1✔
251
                }
252
                break;
1✔
253
            case 'h':  // Horizontal line to relative
1✔
254
                for (const double dx : parameters) {
3✔
255
                    const auto& [lastX, lastY] = points[points.size() - 1];
2✔
256
                    points.emplace_back(lastX + dx, lastY);
2✔
257
                }
258
                break;
1✔
259
            case 'V':  // Vertical line to absolute
1✔
260
                for (const double y : parameters) {
2✔
261
                    const auto lastX = points[points.size() - 1].first;
1✔
262
                    points.emplace_back(lastX, y);
1✔
263
                }
264
                break;
1✔
265
            case 'v':  // Vertical line to relative
1✔
266
                for (const double dy : parameters) {
4✔
267
                    const auto& [lastX, lastY] = points[points.size() - 1];
3✔
268
                    points.emplace_back(lastX, lastY + dy);
3✔
269
                }
270
                break;
1✔
271
            case 'A':  // Draw an arc curve to absolute
1✔
272
                for (int i = 5; i < static_cast<int>(parameters.size()); i += 7) {
2✔
273
                    if (i + 1 < static_cast<int>(parameters.size())) {
1✔
274
                        points.emplace_back(parameters[i], parameters[i + 1]);
1✔
275
                    }
276
                }
277
                break;
1✔
278
            case 'a':  // Draw an arc curve to relative
1✔
279
                for (int i = 5; i < static_cast<int>(parameters.size()); i += 7) {
2✔
280
                    if (i + 1 < static_cast<int>(parameters.size())) {
1✔
281
                        const auto& [lastX, lastY] = points[points.size() - 1];
1✔
282
                        points.emplace_back(lastX + parameters[i], lastY + parameters[i + 1]);
1✔
283
                    }
284
                }
285
                break;
1✔
286
            case 'Z':  // Close the path
1✔
287
            default:
288
                break;
1✔
289
        }
290
    }
291
    return points;
100✔
292
}
×
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