Line data Source code
1 : /**
2 : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy
6 : of this software and associated documentation files (the "Software"), to deal
7 : in the Software without restriction, including without limitation the rights
8 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 : copies of the Software, and to permit persons to whom the Software is
10 : furnished to do so, subject to the following conditions:
11 :
12 : The above copyright notice and this permission notice shall be included in
13 : all copies or substantial portions of the Software.
14 :
15 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPSvgReader.h"
25 :
26 : namespace STAPPLER_VERSIONIZED stappler::vg {
27 :
28 125 : static Mat4 svg_parseTransform(StringView &r) {
29 125 : Mat4 ret(Mat4::IDENTITY);
30 350 : while (!r.empty()) {
31 225 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
32 225 : if (r.is("matrix(")) {
33 50 : r += "matrix("_len;
34 50 : float values[6] = { 0 };
35 :
36 : uint16_t i = 0;
37 350 : for (; i < 6; ++ i) {
38 300 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
39 300 : if (!r.readFloat().grab(values[i])) {
40 : break;
41 : }
42 : }
43 :
44 50 : if (i != 6) {
45 : break;
46 : }
47 :
48 50 : ret *= Mat4(values[0], values[1], values[2], values[3], values[4], values[5]);
49 :
50 175 : } else if (r.is("translate(")) {
51 50 : r += "translate("_len;
52 :
53 50 : float tx = 0.0f, ty = 0.0f;
54 50 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
55 50 : if (!r.readFloat().grab(tx)) {
56 : break;
57 : }
58 :
59 50 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
60 50 : if (!r.is(')')) {
61 50 : if (!r.readFloat().grab(ty)) {
62 : break;
63 : }
64 : }
65 :
66 50 : ret.m[12] += tx;
67 50 : ret.m[13] += ty;
68 :
69 125 : } else if (r.is("scale(")) {
70 25 : r += "scale("_len;
71 :
72 25 : float sx = 0.0f, sy = 0.0f;
73 25 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
74 25 : if (!r.readFloat().grab(sx)) {
75 : break;
76 : }
77 :
78 25 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
79 25 : if (!r.is(')')) {
80 25 : if (!r.readFloat().grab(sy)) {
81 : break;
82 : }
83 : }
84 :
85 25 : ret.scale(sx, (sy == 0.0f) ? sx : sy, 1.0f);
86 :
87 100 : } else if (r.is("rotate(")) {
88 50 : r += "rotate("_len;
89 :
90 50 : float angle = 0.0f;
91 50 : float cx = 0.0f, cy = 0.0f;
92 :
93 50 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
94 50 : if (!r.readFloat().grab(angle)) {
95 : break;
96 : }
97 :
98 50 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
99 50 : if (!r.is(')')) {
100 25 : if (!r.readFloat().grab(cx)) {
101 : break;
102 : }
103 :
104 25 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
105 :
106 25 : if (!r.readFloat().grab(cy)) {
107 : break;
108 : }
109 : }
110 :
111 50 : if (cx == 0.0f && cy == 0.0f) {
112 25 : ret.rotateZ(math::to_rad(angle));
113 : } else {
114 : // optimize matrix translate operations
115 25 : ret.m[12] += cx;
116 25 : ret.m[13] += cy;
117 :
118 25 : ret.rotateZ(math::to_rad(angle));
119 :
120 25 : ret.m[12] -= cx;
121 25 : ret.m[13] -= cy;
122 : }
123 :
124 50 : } else if (r.is("skewX(")) {
125 25 : r += "skewX("_len;
126 :
127 25 : float angle = 0.0f;
128 25 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
129 25 : if (!r.readFloat().grab(angle)) {
130 : break;
131 : }
132 25 : ret *= Mat4(1, 0, tanf(math::to_rad(angle)), 1, 0, 0);
133 :
134 25 : } else if (r.is("skewY(")) {
135 25 : r += "skewY("_len;
136 :
137 25 : float angle = 0.0f;
138 25 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
139 25 : if (!r.readFloat().grab(angle)) {
140 : break;
141 : }
142 :
143 25 : ret *= Mat4(1, tanf(math::to_rad(angle)), 0, 1, 0, 0);
144 : }
145 225 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
146 225 : if (!r.is(')')) {
147 : break;
148 : } else {
149 225 : ++ r;
150 : }
151 : }
152 125 : return ret;
153 : }
154 :
155 75 : static Rect svg_readViewBox(StringView &r) {
156 75 : float values[4] = { 0 };
157 :
158 : uint16_t i = 0;
159 375 : for (; i < 4; ++ i) {
160 300 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
161 300 : if (!r.readFloat().grab(values[i])) {
162 0 : return Rect();
163 : }
164 : }
165 :
166 75 : return Rect(values[0], values[1], values[2], values[3]);
167 : }
168 :
169 975 : static float svg_readCoordValue(StringView &source, float origin) {
170 975 : Metric m; m.metric = Metric::Px;
171 975 : if (m.readStyleValue(source, false, true)) {
172 975 : switch (m.metric) {
173 925 : case Metric::Px:
174 925 : return m.value;
175 : break;
176 50 : case Metric::Percent:
177 50 : return m.value * origin;
178 : break;
179 : default:
180 : break;
181 : }
182 : }
183 0 : return nan();
184 : }
185 :
186 50 : static void svg_readPointCoords(PathWriter &target, StringView &source) {
187 : float x, y;
188 300 : while (!source.empty()) {
189 250 : source.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
190 250 : if (!source.readFloat().grab(x)) {
191 0 : return;
192 : }
193 :
194 250 : source.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>, StringView::Chars<','>>();
195 250 : if (!source.readFloat().grab(y)) {
196 : return;
197 : }
198 :
199 250 : if (target.empty()) {
200 50 : target.moveTo(x, y);
201 : } else {
202 200 : target.lineTo(x, y);
203 : }
204 : }
205 : }
206 :
207 3775 : VectorPath &SvgTag::getPath() {
208 3775 : return rpath;
209 : }
210 :
211 250 : PathWriter &SvgTag::getWriter() {
212 250 : if (!writer) {
213 175 : writer = rpath.getWriter();
214 : }
215 250 : return writer;
216 : }
217 :
218 100 : SvgReader::SvgReader() { }
219 :
220 1025 : void SvgReader::onBeginTag(Parser &p, Tag &tag) {
221 1025 : if (!p.tagStack.empty()) {
222 925 : tag.rpath.setParams(p.tagStack.back().rpath.getParams());
223 : }
224 :
225 1025 : if (tag.name.equals("rect")) {
226 75 : tag.shape = SvgTag::Rect;
227 950 : } else if (tag.name.equals("circle")) {
228 50 : tag.shape = SvgTag::Circle;
229 900 : } else if (tag.name.equals("ellipse")) {
230 25 : tag.shape = SvgTag::Ellipse;
231 875 : } else if (tag.name.equals("line")) {
232 25 : tag.shape = SvgTag::Line;
233 850 : } else if (tag.name.equals("polyline")) {
234 25 : tag.shape = SvgTag::Polyline;
235 825 : } else if (tag.name.equals("polygon")) {
236 25 : tag.shape = SvgTag::Polygon;
237 800 : } else if (tag.name.equals("use")) {
238 100 : tag.shape = SvgTag::Use;
239 100 : tag.mat = Mat4::IDENTITY;
240 : }
241 1025 : }
242 :
243 1025 : void SvgReader::onEndTag(Parser &p, Tag &tag, bool isClosed) {
244 1025 : if (tag.name.equals("svg")) {
245 100 : _squareLength = sqrtf((_width * _width + _height * _height) / 2.0f);
246 : }
247 :
248 1025 : switch (tag.shape) {
249 75 : case SvgTag::Rect:
250 125 : if (!isnan(tag.mat.m[0]) && !isnan(tag.mat.m[1])
251 25 : && !isnan(tag.mat.m[2]) && tag.mat.m[2] > 0.0f && !isnan(tag.mat.m[3]) && tag.mat.m[3] > 0.0f
252 100 : && (isnan(tag.mat.m[4]) || tag.mat.m[4] >= 0.0f) && (isnan(tag.mat.m[5]) || tag.mat.m[5] >= 0.0f)) {
253 25 : tag.getWriter().addRect(tag.mat.m[0], tag.mat.m[1], tag.mat.m[2], tag.mat.m[3], tag.mat.m[4], tag.mat.m[5]);
254 : }
255 : break;
256 50 : case SvgTag::Circle:
257 50 : if (!isnan(tag.mat.m[0]) && !isnan(tag.mat.m[1]) && !isnan(tag.mat.m[2]) && tag.mat.m[2] >= 0.0f) {
258 50 : tag.getWriter().addCircle(tag.mat.m[0], tag.mat.m[1], tag.mat.m[2]);
259 : }
260 : break;
261 25 : case SvgTag::Ellipse:
262 25 : if (!isnan(tag.mat.m[0]) && !isnan(tag.mat.m[1]) && !isnan(tag.mat.m[2]) && !isnan(tag.mat.m[3]) && tag.mat.m[2] >= 0.0f && tag.mat.m[3] >= 0.0f) {
263 25 : tag.getWriter().addEllipse(tag.mat.m[0], tag.mat.m[1], tag.mat.m[2], tag.mat.m[3]);
264 : }
265 : break;
266 25 : case SvgTag::Line:
267 25 : if (!isnan(tag.mat.m[0]) && !isnan(tag.mat.m[1]) && !isnan(tag.mat.m[2]) && !isnan(tag.mat.m[3])) {
268 25 : tag.getWriter().moveTo(tag.mat.m[0], tag.mat.m[1]);
269 25 : tag.getWriter().lineTo(tag.mat.m[2], tag.mat.m[3]);
270 : }
271 : break;
272 25 : case SvgTag::Polygon:
273 25 : if (!tag.getWriter().empty()) {
274 25 : tag.getWriter().closePath();
275 : }
276 : break;
277 : default:
278 : break;
279 : }
280 1025 : }
281 :
282 1950 : void SvgReader::onStyleParameter(Tag &tag, StringReader &name, StringReader &value) {
283 1950 : if (name.equals("opacity")) {
284 100 : value.readFloat().unwrap([&] (float op) {
285 100 : if (op <= 0.0f) {
286 25 : tag.getPath().setFillOpacity(0);
287 25 : tag.getPath().setStrokeOpacity(0);
288 75 : } else if (op >= 1.0f) {
289 25 : tag.getPath().setFillOpacity(255);
290 25 : tag.getPath().setStrokeOpacity(255);
291 : } else {
292 50 : tag.getPath().setFillOpacity(255 * op);
293 50 : tag.getPath().setStrokeOpacity(255 * op);
294 : }
295 100 : });
296 1850 : } else if (name.equals("fill")) {
297 675 : if (value.equals("none")) {
298 25 : tag.getPath().setStyle(tag.getPath().getStyle() & (~DrawStyle::Fill));
299 : } else {
300 650 : Color3B color;
301 650 : if (readColor(value, color)) {
302 175 : tag.getPath().setFillColor(color, true);
303 175 : tag.getPath().setStyle(tag.getPath().getStyle() | DrawStyle::Fill);
304 : }
305 : }
306 1175 : } else if (name.equals("fill-rule")) {
307 125 : if (value.equals("nonzero")) {
308 75 : tag.getPath().setWindingRule(Winding::NonZero);
309 50 : } else if (value.equals("evenodd")) {
310 50 : tag.getPath().setWindingRule(Winding::EvenOdd);
311 : }
312 1050 : } else if (name.equals("fill-opacity")) {
313 75 : value.readFloat().unwrap([&] (float op) {
314 75 : if (op <= 0.0f) {
315 25 : tag.getPath().setFillOpacity(0);
316 50 : } else if (op >= 1.0f) {
317 25 : tag.getPath().setFillOpacity(255);
318 : } else {
319 25 : tag.getPath().setFillOpacity(255 * op);
320 : }
321 75 : });
322 975 : } else if (name.equals("stroke")) {
323 650 : if (value.equals("none")) {
324 25 : tag.getPath().setStyle(tag.getPath().getStyle() & (~DrawStyle::Stroke));
325 : } else {
326 625 : Color3B color;
327 625 : if (readColor(value, color)) {
328 625 : tag.getPath().setStrokeColor(color, true);
329 625 : tag.getPath().setStyle(tag.getPath().getStyle() | DrawStyle::Stroke);
330 : }
331 : }
332 325 : } else if (name.equals("stroke-opacity")) {
333 75 : value.readFloat().unwrap([&] (float op) {
334 75 : if (op <= 0.0f) {
335 25 : tag.getPath().setStrokeOpacity(0);
336 50 : } else if (op >= 1.0f) {
337 25 : tag.getPath().setStrokeOpacity(255);
338 : } else {
339 25 : tag.getPath().setStrokeOpacity(255 * op);
340 : }
341 75 : });
342 250 : } else if (name.equals("stroke-width")) {
343 25 : auto val = svg_readCoordValue(value, _squareLength);
344 25 : if (!isnan(val)) {
345 25 : tag.getPath().setStrokeWidth(val);
346 : }
347 225 : } else if (name.equals("stroke-linecap")) {
348 75 : if (value.equals("butt")) {
349 25 : tag.getPath().setLineCup(LineCup::Butt);
350 50 : } else if (value.equals("round")) {
351 25 : tag.getPath().setLineCup(LineCup::Round);
352 25 : } else if (value.equals("square")) {
353 25 : tag.getPath().setLineCup(LineCup::Square);
354 : }
355 150 : } else if (name.equals("stroke-linejoin")) {
356 75 : if (value.equals("miter")) {
357 25 : tag.getPath().setLineJoin(LineJoin::Miter);
358 50 : } else if (value.equals("round")) {
359 25 : tag.getPath().setLineJoin(LineJoin::Round);
360 25 : } else if (value.equals("bevel")) {
361 25 : tag.getPath().setLineJoin(LineJoin::Bevel);
362 : }
363 75 : } else if (name.equals("stroke-miterlimit")) {
364 25 : value.readFloat().unwrap([&] (float op) {
365 25 : if (op > 1.0f) {
366 25 : tag.getPath().setMiterLimit(op);
367 : }
368 25 : });
369 : }
370 1950 : }
371 :
372 25 : void SvgReader::onStyle(Tag &tag, StringReader &value) {
373 100 : while (!value.empty()) {
374 75 : auto n = value.readUntil<StringReader::Chars<':'>>();
375 75 : if (value.is(':')) {
376 75 : ++ value;
377 75 : auto v = value.readUntil<StringReader::Chars<';'>>();
378 75 : if (value.is(';')) {
379 75 : ++ value;
380 : }
381 75 : if (!n.empty() && !v.empty()) {
382 75 : onStyleParameter(tag, n, v);
383 : }
384 : }
385 : }
386 25 : }
387 :
388 4025 : void SvgReader::onTagAttribute(Parser &p, Tag &tag, StringReader &name, StringReader &value) {
389 4025 : if (tag.name.equals("svg")) {
390 475 : if (name.equals("height")) {
391 100 : auto val = svg_readCoordValue(value, 0.0f);
392 100 : if (!isnan(val)) {
393 100 : _height = val;
394 : }
395 375 : } else if (name.equals("width")) {
396 100 : auto val = svg_readCoordValue(value, 0.0f);
397 100 : if (!isnan(val)) {
398 100 : _width = val;
399 : }
400 275 : } else if (name.equals("viewbox")) {
401 75 : _viewBox = svg_readViewBox(value);
402 : }
403 3550 : } else if (tag.name.equals("path")) {
404 1650 : if (name.equals("d")) {
405 550 : tag.getPath().init(value);
406 : }
407 : }
408 :
409 7400 : if (name.equals("fill") || name.equals("fill-rule") || name.equals("fill-opacity") || name.equals("stroke")
410 2525 : || name.equals("stroke-opacity") || name.equals("stroke-width") || name.equals("stroke-linecap")
411 6375 : || name.equals("stroke-linejoin") || name.equals("stroke-miterlimit") || name.equals("opacity")) {
412 1875 : onStyleParameter(tag, name, value);
413 2150 : } else if (name.equals("transform") && tag.shape != SvgTag::Use) {
414 50 : tag.getPath().applyTransform(svg_parseTransform(value));
415 2100 : } else if (name.equals("style")) {
416 25 : onStyle(tag, value);
417 2075 : } else if (name.equals("id")) {
418 75 : tag.id = value;
419 : } else {
420 2000 : switch (tag.shape) {
421 275 : case SvgTag::Rect:
422 275 : if (name.equals("x")) {
423 50 : tag.mat.m[0] = svg_readCoordValue(value, _width);
424 225 : } else if (name.equals("y")) {
425 25 : tag.mat.m[1] = svg_readCoordValue(value, _height);
426 200 : } else if (name.equals("width")) {
427 75 : tag.mat.m[2] = svg_readCoordValue(value, _width);
428 125 : } else if (name.equals("height")) {
429 75 : tag.mat.m[3] = svg_readCoordValue(value, _height);
430 50 : } else if (name.equals("rx")) {
431 25 : tag.mat.m[4] = svg_readCoordValue(value, _width);
432 25 : } else if (name.equals("ry")) {
433 25 : tag.mat.m[5] = svg_readCoordValue(value, _height);
434 : }
435 : break;
436 150 : case SvgTag::Circle:
437 150 : if (name.equals("cx")) {
438 50 : tag.mat.m[0] = svg_readCoordValue(value, _width);
439 100 : } else if (name.equals("cy")) {
440 50 : tag.mat.m[1] = svg_readCoordValue(value, _height);
441 50 : } else if (name.equals("r")) {
442 50 : tag.mat.m[2] = svg_readCoordValue(value, _width);
443 : }
444 : break;
445 100 : case SvgTag::Ellipse:
446 100 : if (name.equals("cx")) {
447 25 : tag.mat.m[0] = svg_readCoordValue(value, _width);
448 75 : } else if (name.equals("cy")) {
449 25 : tag.mat.m[1] = svg_readCoordValue(value, _height);
450 50 : } else if (name.equals("rx")) {
451 25 : tag.mat.m[2] = svg_readCoordValue(value, _width);
452 25 : } else if (name.equals("ry")) {
453 25 : tag.mat.m[3] = svg_readCoordValue(value, _height);
454 : }
455 : break;
456 100 : case SvgTag::Line:
457 100 : if (name.equals("x1")) {
458 25 : tag.mat.m[0] = svg_readCoordValue(value, _width);
459 75 : } else if (name.equals("y1")) {
460 25 : tag.mat.m[1] = svg_readCoordValue(value, _height);
461 50 : } else if (name.equals("x2")) {
462 25 : tag.mat.m[2] = svg_readCoordValue(value, _width);
463 25 : } else if (name.equals("y2")) {
464 25 : tag.mat.m[3] = svg_readCoordValue(value, _height);
465 : }
466 : break;
467 25 : case SvgTag::Polyline:
468 25 : if (name.equals("points")) {
469 25 : svg_readPointCoords(tag.getWriter(), value);
470 : }
471 : break;
472 25 : case SvgTag::Polygon:
473 25 : if (name.equals("points")) {
474 25 : svg_readPointCoords(tag.getWriter(), value);
475 : }
476 : break;
477 300 : case SvgTag::Use:
478 300 : if (name.equals("x")) {
479 75 : tag.mat.translate(svg_readCoordValue(value, _width), 0.0f, 0.0f);
480 225 : } else if (name.equals("y")) {
481 50 : tag.mat.translate(0.0f, svg_readCoordValue(value, _width), 0.0f);
482 175 : } else if (name.equals("transform")) {
483 75 : tag.mat.multiply(svg_parseTransform(value));
484 100 : } else if (name.equals("id")) {
485 0 : tag.id = value;
486 100 : } else if (name.equals("xlink:href") || name.equals("href")) {
487 100 : tag.ref = value;
488 : }
489 : break;
490 : default:
491 : break;
492 : }
493 : }
494 4025 : }
495 :
496 150 : void SvgReader::onPushTag(Parser &p, Tag &tag) {
497 150 : if (tag.name == "defs") {
498 25 : _defs = true;
499 : }
500 150 : }
501 150 : void SvgReader::onPopTag(Parser &p, Tag &tag) {
502 150 : if (tag.name == "defs") {
503 25 : _defs = false;
504 : }
505 150 : }
506 :
507 875 : void SvgReader::onInlineTag(Parser &p, Tag &tag) {
508 875 : if (tag.shape == Tag::Shape::Use) {
509 100 : StringView ref(tag.ref);
510 100 : if (ref.is('#')) { ++ ref; }
511 100 : auto pathIt = _paths.find(ref);
512 100 : if (pathIt != _paths.end()) {
513 100 : if (_defs) {
514 25 : if (!tag.id.empty()) {
515 25 : VectorPath npath(pathIt->second);
516 25 : npath.applyTransform(tag.mat);
517 25 : _paths.emplace(tag.id.str<Interface>(), move(npath));
518 25 : }
519 : } else {
520 75 : if (tag.mat.isIdentity()) {
521 25 : _drawOrder.emplace_back(PathXRef{ref.str<Interface>()});
522 : } else {
523 50 : _drawOrder.emplace_back(PathXRef{ref.str<Interface>(), Interface::StringType(), tag.mat});
524 : }
525 : }
526 : }
527 775 : } else if (tag.rpath) {
528 725 : Interface::StringType idStr;
529 725 : StringView id(tag.id);
530 725 : if (id.empty()) {
531 700 : idStr = mem_std::toString("auto-", _nextId);
532 700 : ++ _nextId;
533 700 : id = idStr;
534 : }
535 :
536 725 : _paths.emplace(id.str<Interface>(), move(tag.rpath));
537 725 : if (!_defs) {
538 700 : _drawOrder.emplace_back(PathXRef{id.str<Interface>()});
539 : }
540 725 : }
541 875 : }
542 :
543 : }
|