1 #include "Painter.h"
2 #include "Painter.h"
3
4 #define LLOG(x)
5
6 namespace Upp {
7
8 #include "SvgInternal.h"
9
ResolveGradient(int i)10 void SvgParser::ResolveGradient(int i)
11 {
12 Gradient& g = gradient[i];
13 if(g.resolved || g.href.GetCount() < 2)
14 return;
15 int q = gradient.Find(g.href.Mid(1));
16 g.resolved = true;
17 if(q < 0)
18 return;
19 ResolveGradient(q);
20 Gradient& g2 = gradient[q];
21 if(g.stop.GetCount() == 0)
22 g.stop <<= g2.stop;
23 g.a.x = Nvl(Nvl(g.a.x, g2.a.x));
24 g.a.y = Nvl(Nvl(g.a.y, g2.a.y));
25 g.b.x = Nvl(g.b.x, g2.b.x); // In user-space units, needs to be replaced by cx, in normal with 1
26 g.b.y = Nvl(Nvl(g.b.y, g2.b.y));
27 g.c.x = Nvl(Nvl(g.c.x, g2.c.x), 0.5);
28 g.c.y = Nvl(Nvl(g.c.y, g2.c.y), 0.5);
29 g.f.x = Nvl(Nvl(g.f.x, g2.f.x), g.c.x);
30 g.f.y = Nvl(Nvl(g.f.y, g2.f.y), g.c.y);
31 g.r = Nvl(Nvl(g.r, g2.r), 1.0);
32 g.transform = Nvl(g.transform, g2.transform);
33 g.style = Nvl(Nvl(g.style, g2.style), GRADIENT_PAD);
34 }
35
StartElement(const XmlNode & n)36 void SvgParser::StartElement(const XmlNode& n)
37 {
38 State& s = state.Add();
39 s = state[state.GetCount() - 2];
40 s.n = current;
41 current = &n;
42 bp.Begin();
43 bp.Transform(Transform(Txt("transform")));
44 Style(Txt("style"));
45 String classid = Txt("class");
46 if(classid.GetCount())
47 Style(classes.Get(classid, String()));
48 for(int i = 0; i < n.GetAttrCount(); i++)
49 ProcessValue(n.AttrId(i), n.Attr(i));
50 closed = false;
51 }
52
EndElement()53 void SvgParser::EndElement()
54 {
55 if(!closed) {
56 sw.Stroke(0, Black()); // Finish path to allow new transformations, if not yet done
57 }
58 current = state.Top().n;
59 state.Drop();
60 bp.End();
61 }
62
DoGradient(int gi,bool stroke)63 void SvgParser::DoGradient(int gi, bool stroke)
64 {
65 State& s = state.Top();
66 ResolveGradient(gi);
67 Gradient& g = gradient[gi];
68 if(g.stop.GetCount()) {
69 for(int i = 0; i < g.stop.GetCount(); i++)
70 sw.ColorStop(g.stop[i].offset, g.stop[i].color);
71 Pointf a = g.a;
72 Pointf b = g.b;
73 Pointf c = g.c;
74 Pointf f = g.f;
75 Pointf r(g.r, g.r);
76 Sizef sz = bp.boundingbox.GetSize();
77 Pointf pos = bp.boundingbox.TopLeft();
78 if(IsNull(b.x))
79 b.x = g.user_space ? bp.boundingbox.right : 1.0;
80 if(g.user_space) {
81 a = (a - pos) / sz;
82 b = (b - pos) / sz;
83 c = (c - pos) / sz;
84 f = (f - pos) / sz;
85 r = r / sz;
86 }
87 Xform2D m;
88
89 if(g.radial) {
90 m.x.x = r.x;
91 m.x.y = 0;
92 m.y.x = 0;
93 m.y.y = r.y;
94 m.t = c;
95 f = (f - c) / r;
96 }
97 else {
98 Pointf d = b - a;
99 m.x.x = d.x;
100 m.x.y = -d.y;
101 m.y.x = d.y;
102 m.y.y = d.x;
103 m.t = a;
104 }
105 m = m * Xform2D::Scale(sz.cx, sz.cy) * Xform2D::Translation(pos.x, pos.y);
106 if(g.transform.GetCount())
107 m = m * Transform(g.transform);
108 RGBA c1 = g.stop[0].color;
109 RGBA c2 = g.stop.Top().color;
110 if(stroke)
111 if(g.radial)
112 sw.Stroke(s.stroke_width, f, c1, c2, m, g.style);
113 else
114 sw.Stroke(s.stroke_width, c1, c2, m, g.style);
115 else
116 if(g.radial)
117 sw.Fill(f, c1, c2, m, g.style);
118 else
119 sw.Fill(c1, c2, m, g.style);
120 bp.Finish(stroke * s.stroke_width);
121 sw.ClearStops();
122 closed = true;
123 }
124 }
125
StrokeFinishElement()126 void SvgParser::StrokeFinishElement()
127 {
128 State& s = state.Top();
129 if(s.stroke_width > 0) {
130 double o = s.opacity * s.stroke_opacity;
131 if(o != 1) {
132 sw.Begin();
133 sw.Opacity(o);
134 }
135 if(s.stroke_gradient >= 0 && s.stroke_width > 0)
136 DoGradient(s.stroke_gradient, true);
137 else
138 if(!IsNull(s.stroke) && s.stroke_width > 0) {
139 sw.Stroke(s.stroke_width, s.stroke);
140 bp.Finish(s.stroke_width);
141 closed = true;
142 }
143 if(o != 1)
144 sw.End();
145 }
146 EndElement();
147 }
148
FinishElement()149 void SvgParser::FinishElement()
150 {
151 State& s = state.Top();
152 double o = s.opacity * s.fill_opacity;
153 if(o > 0) {
154 if(o != 1) {
155 sw.Begin();
156 sw.Opacity(o);
157 }
158 if(s.fill_gradient >= 0)
159 DoGradient(s.fill_gradient, false);
160 else
161 if(!IsNull(s.fill)) {
162 sw.Fill(s.fill);
163 bp.Finish(0);
164 closed = true;
165 }
166 if(o != 1)
167 sw.End();
168 }
169 StrokeFinishElement();
170 }
171
ParseGradient(const XmlNode & n,bool radial)172 void SvgParser::ParseGradient(const XmlNode& n, bool radial)
173 {
174 LLOG("ParseGradient " << n.Attr("id"));
175 Gradient& g = gradient.Add(n.Attr("id"));
176 g.radial = radial;
177 g.user_space = n.Attr("gradientUnits") == "userSpaceOnUse";
178 g.transform = n.Attr("gradientTransform");
179 g.href = n.Attr("xlink:href");
180 g.resolved = IsNull(g.href);
181 double def = g.resolved ? 0.0 : (double)Null;
182 double def5 = g.resolved ? 0.5 : (double)Null;
183 auto Dbl = [&](const char *id, double def) { return Nvl(StrDbl(n.Attr(id)), def); };
184 g.c.x = Dbl("cx", def5);
185 g.c.y = Dbl("cy", def5);
186 g.r = Dbl("r", g.resolved ? 1.0 : (double)Null);
187 g.f.x = Dbl("fx", g.c.x);
188 g.f.y = Dbl("fy", g.c.y);
189 g.a.x = Dbl("x1", def);
190 g.a.y = Dbl("y1", def);
191 g.b.x = Dbl("x2", Null);
192 g.b.y = Dbl("y2", def);
193 g.style = decode(Txt("spreadMethod"), "pad", GRADIENT_PAD, "reflect", GRADIENT_REFLECT,
194 "repeat", GRADIENT_REPEAT, (int)Null);
195
196 for(const XmlNode& m : n)
197 if(m.IsTag("stop")) {
198 Stop &s = g.stop.Add();
199 double offset = 0;
200 String st = m.Attr("style");
201 const char *style = st;
202 double opacity = 1;
203 Color color;
204 String key, value;
205 for(;;) {
206 if(*style == ';' || *style == '\0') {
207 value = TrimBoth(value);
208 if(key == "stop-color")
209 color = GetColor(value);
210 else
211 if(key == "stop-opacity")
212 opacity = StrDbl(value);
213 else
214 if(key == "offset")
215 offset = StrDbl(value);
216 value.Clear();
217 key.Clear();
218 if(*style == '\0')
219 break;
220 else
221 style++;
222 }
223 else
224 if(*style == ':') {
225 key << TrimBoth(value);
226 value.Clear();
227 style++;
228 }
229 else
230 value.Cat(*style++);
231 }
232 value = m.Attr("stop-color");
233 if(value.GetCount())
234 color = GetColor(value);
235 value = m.Attr("stop-opacity");
236 if(value.GetCount())
237 opacity = Nvl(StrDbl(value), opacity);
238 s.color = clamp(int(opacity * 255 + 0.5), 0, 255) * color;
239 s.offset = Nvl(StrDbl(m.Attr("offset")), offset);
240 }
241 }
242
Poly(const XmlNode & n,bool line)243 void SvgParser::Poly(const XmlNode& n, bool line)
244 {
245 Vector<Point> r;
246 String value = n.Attr("points");
247 try {
248 CParser p(value);
249 while(!p.IsEof()) {
250 Pointf n;
251 n.x = p.ReadDouble();
252 p.Char(',');
253 n.y = p.ReadDouble();
254 r.Add(n);
255 p.Char(',');
256 }
257 }
258 catch(CParser::Error) {}
259 if(r.GetCount()) {
260 StartElement(n);
261 bp.Move(r[0].x, r[0].y);
262 for(int i = 1; i < r.GetCount(); ++i)
263 bp.Line(r[i].x, r[i].y);
264 if(!line)
265 bp.Close();
266 if(line)
267 StrokeFinishElement();
268 else
269 FinishElement();
270 }
271 }
272
ReadNumber(CParser & p)273 double ReadNumber(CParser& p)
274 {
275 while(!p.IsEof() && (!p.IsDouble() || p.IsChar('.')))
276 p.SkipTerm();
277 return p.ReadDouble();
278 }
279
GetSvgViewBox(const String & v)280 Rectf GetSvgViewBox(const String& v)
281 {
282 Rectf r = Null;
283 if(v.GetCount()) {
284 try {
285 CParser p(v);
286 r.left = ReadNumber(p);
287 r.top = ReadNumber(p);
288 r.right = r.left + ReadNumber(p);
289 r.bottom = r.top + ReadNumber(p);
290 }
291 catch(CParser::Error) {
292 r = Null;
293 }
294 }
295 return r;
296 }
297
GetSvgViewBox(XmlParser & xml)298 Rectf GetSvgViewBox(XmlParser& xml)
299 {
300 return GetSvgViewBox(xml["viewBox"]);
301 }
302
GetSvgViewBox(const XmlNode & xml)303 Rectf GetSvgViewBox(const XmlNode& xml)
304 {
305 return GetSvgViewBox(xml.Attr("viewBox"));
306 }
307
GetSvgSize(XmlParser & xml)308 Sizef GetSvgSize(XmlParser& xml)
309 {
310 Sizef sz;
311 sz.cx = StrDbl(xml["width"]);
312 sz.cy = StrDbl(xml["height"]);
313 if(IsNull(sz.cx) || IsNull(sz.cy))
314 sz = Null;
315 return sz;
316 }
317
GetSvgPos(XmlParser & xml)318 Pointf GetSvgPos(XmlParser& xml)
319 {
320 Pointf p;
321 p.x = StrDbl(xml["x"]);
322 p.y = StrDbl(xml["y"]);
323 if(IsNull(p.x) || IsNull(p.y))
324 p = Null;
325 return p;
326 }
327
GetSvgSize(const XmlNode & xml)328 Sizef GetSvgSize(const XmlNode& xml)
329 {
330 Sizef sz;
331 sz.cx = StrDbl(xml.Attr("width"));
332 sz.cy = StrDbl(xml.Attr("height"));
333 if(IsNull(sz.cx) || IsNull(sz.cy))
334 sz = Null;
335 return sz;
336 }
337
GetSvgPos(const XmlNode & xml)338 Pointf GetSvgPos(const XmlNode& xml)
339 {
340 Pointf p;
341 p.x = StrDbl(xml.Attr("x"));
342 p.y = StrDbl(xml.Attr("y"));
343 if(IsNull(p.x) || IsNull(p.y))
344 p = Null;
345 return p;
346 }
347
Element(const XmlNode & n,int depth,bool dosymbols)348 void SvgParser::Element(const XmlNode& n, int depth, bool dosymbols)
349 {
350 if(depth > 100) // defend against id recursion
351 return;
352 LLOG("====== " << n.GetTag());
353 if(n.IsTag("defs")) {
354 for(const auto& m : n)
355 if(m.IsTag("linearGradient"))
356 ParseGradient(m, false);
357 else
358 if(m.IsTag("radialGradient"))
359 ParseGradient(m, true);
360 }
361 else
362 if(n.IsTag("linearGradient"))
363 ParseGradient(n, false);
364 else
365 if(n.IsTag("radialGradient"))
366 ParseGradient(n, true);
367 else
368 if(n.IsTag("rect")) {
369 StartElement(n);
370 bp.RoundedRectangle(Dbl("x"), Dbl("y"), Dbl("width"), Dbl("height"), Dbl("rx"), Dbl("ry"));
371 FinishElement();
372 }
373 else
374 if(n.IsTag("ellipse")) {
375 StartElement(n);
376 bp.Ellipse(Dbl("cx"), Dbl("cy"), Dbl("rx"), Dbl("ry"));
377 FinishElement();
378 }
379 else
380 if(n.IsTag("circle")) {
381 StartElement(n);
382 Pointf c(Dbl("cx"), Dbl("cy"));
383 double r = Dbl("r");
384 bp.Ellipse(c.x, c.y, r, r);
385 FinishElement();
386 }
387 else
388 if(n.IsTag("line")) {
389 StartElement(n);
390 Pointf a(Dbl("x1"), Dbl("y1"));
391 Pointf b(Dbl("x2"), Dbl("y2"));
392 bp.Move(a);
393 bp.Line(b);
394 FinishElement();
395 }
396 else
397 if(n.IsTag("polygon"))
398 Poly(n, false);
399 else
400 if(n.IsTag("polyline"))
401 Poly(n, true);
402 else
403 if(n.IsTag("path")) {
404 StartElement(n);
405 bp.Path(Txt("d"));
406 FinishElement();
407 }
408 else
409 if(n.IsTag("image")) {
410 StartElement(n);
411 String fileName = Txt("xlink:href");
412 String data;
413 resloader(fileName, data);
414 if(data.GetCount()) {
415 Image img = StreamRaster::LoadFileAny(fileName);
416 if(!IsNull(img)) {
417 bp.Rectangle(Dbl("x"), Dbl("y"), Dbl("width"), Dbl("height"));
418 sw.Fill(StreamRaster::LoadFileAny(fileName), Dbl("x"), Dbl("y"), Dbl("width"), 0);
419 }
420 }
421 EndElement();
422 }
423 else
424 if(n.IsTag("text")) {
425 StartElement(n);
426 auto DoText = [&](const XmlNode& n) {
427 String text = n.GatherText();
428 text.Replace("\n", " ");
429 text.Replace("\r", "");
430 text.Replace("\t", " ");
431 if(text.GetCount()) {
432 Font fnt = state.Top().font;
433 int anchor = state.Top().text_anchor;
434 double x = Dbl("x");
435 if(anchor) {
436 Sizef sz = GetTextSize(text, fnt); // TODO; GetTextSizef
437 x -= anchor == 1 ? sz.cx / 2 : sz.cx;
438 }
439 bp.Text(x , Dbl("y") - fnt.GetAscent(), text, fnt);
440 }
441 };
442 DoText(n);
443 for(const auto& m : n)
444 if(m.IsTag("tspan")) {
445 StartElement(m);
446 DoText(m);
447 FinishElement();
448 }
449 FinishElement();
450 }
451 else
452 if(n.IsTag("g") || n.IsTag("symbol") && dosymbols)
453 Items(n, depth);
454 else
455 if(n.IsTag("use")) {
456 const XmlNode *idn = idmap.Get(Nvl(n.Attr("href"), n.Attr("xlink:href")), NULL);
457 if(idn) {
458 StartElement(n);
459 Rectf vr = GetSvgViewBox(*idn);
460 Sizef sz = GetSvgSize(*idn);
461 bp.Translate(Dbl("x"), Dbl("y"));
462 if(!IsNull(vr) && !IsNull(sz)) {
463 bp.Rectangle(0, 0, sz.cx, sz.cy).Clip();
464 sz /= vr.GetSize();
465 bp.Scale(sz.cx, sz.cy);
466 bp.Translate(-vr.left, -vr.top);
467 }
468 Element(*idn, depth + 1, true);
469 EndElement();
470 }
471 }
472 else
473 if(n.IsTag("svg")) {
474 Sizef sz = GetSvgSize(n);
475 if(!IsNull(sz)) {
476 Pointf p = Nvl(GetSvgPos(n), Pointf(0, 0));
477 Rectf vb = Nvl(GetSvgViewBox(n), sz);
478 //TODO: For now, we support "xyMid meet" only
479 bp.Translate(p.x, p.y);
480 bp.Scale(min(sz.cx / vb.GetWidth(), sz.cy / vb.GetHeight()));
481 bp.Translate(-vb.TopLeft());
482 Items(n, depth);
483 }
484 }
485 else
486 if(n.IsTag("style")) {
487 String text = n.GatherText();
488 try {
489 CParser p(text);
490 while(!p.IsEof()) {
491 if(p.Char('.') && p.IsId()) {
492 String id = p.ReadId();
493 if(p.Char('{')) {
494 const char *b = p.GetPtr();
495 while(!p.IsChar('}') && !p.IsEof())
496 p.SkipTerm();
497 classes.Add(id, String(b, p.GetPtr()));
498 }
499 p.Char('}');
500 }
501 else
502 p.SkipTerm();
503 }
504 }
505 catch(CParser::Error) {}
506 }
507 }
508
Items(const XmlNode & n,int depth)509 void SvgParser::Items(const XmlNode& n, int depth)
510 {
511 StartElement(n);
512 for(const auto& m : n)
513 Element(m, depth);
514 EndElement();
515 }
516
MapIds(const XmlNode & n)517 void SvgParser::MapIds(const XmlNode& n)
518 {
519 String id = n.Attr("id");
520 if(id.GetCount())
521 idmap.Add('#' + id, &n);
522 for(const auto& m : n)
523 MapIds(m);
524 }
525
Parse(const char * xml)526 bool SvgParser::Parse(const char *xml) {
527 try {
528 XmlNode n = ParseXML(xml);
529 MapIds(n);
530 for(const auto& m : n)
531 if(m.IsTag("svg"))
532 Items(m, 0);
533 }
534 catch(XmlError e) {
535 return false;
536 }
537 return true;
538 }
539
SvgParser(Painter & sw)540 SvgParser::SvgParser(Painter& sw)
541 : sw(sw), bp(sw)
542 {
543 Reset();
544 }
545
ParseSVG(Painter & p,const char * svg,Event<String,String &> resloader,Rectf * boundingbox)546 bool ParseSVG(Painter& p, const char *svg, Event<String, String&> resloader, Rectf *boundingbox)
547 {
548 SvgParser sp(p);
549 sp.bp.compute_svg_boundingbox = boundingbox;
550 sp.resloader = resloader;
551 if(!sp.Parse(svg))
552 return false;
553 if(boundingbox)
554 *boundingbox = sp.bp.svg_boundingbox;
555 return true;
556 }
557
RenderSVG(Painter & p,const char * svg,Event<String,String &> resloader)558 bool RenderSVG(Painter& p, const char *svg, Event<String, String&> resloader)
559 {
560 return ParseSVG(p, svg, resloader, NULL);
561 }
562
RenderSVG(Painter & p,const char * svg)563 bool RenderSVG(Painter& p, const char *svg)
564 {
565 return RenderSVG(p, svg, Event<String, String&>());
566 }
567
GetSVGDimensions(const char * svg,Sizef & sz,Rectf & viewbox)568 void GetSVGDimensions(const char *svg, Sizef& sz, Rectf& viewbox)
569 {
570 viewbox = Null;
571 sz = Null;
572 try {
573 XmlParser xml(svg);
574 while(!xml.IsTag())
575 xml.Skip();
576 xml.PassTag("svg");
577 viewbox = GetSvgViewBox(xml);
578 sz = GetSvgSize(xml);
579 }
580 catch(XmlError e) {
581 }
582 }
583
GetSVGBoundingBox(const char * svg)584 Rectf GetSVGBoundingBox(const char *svg)
585 {
586 NilPainter nil;
587 Rectf bb;
588 if(!ParseSVG(nil, svg, Event<String, String&>(), &bb))
589 return Null;
590 return bb;
591 }
592
RenderSVGImage(Size sz,const char * svg,Event<String,String &> resloader)593 Image RenderSVGImage(Size sz, const char *svg, Event<String, String&> resloader)
594 {
595 Rectf f = GetSVGBoundingBox(svg);
596 Sizef iszf = GetFitSize(f.GetSize(), Sizef(sz.cx, sz.cy) - 10.0);
597 Size isz((int)ceil(iszf.cx), (int)ceil(iszf.cy));
598 if(isz.cx <= 0 || isz.cy <= 0)
599 return Null;
600 RLOG("===================== PAINT");
601 ImageBuffer ib(isz);
602 BufferPainter sw(ib);
603 sw.Clear(White());
604 sw.Scale(min(isz.cx / f.GetWidth(), isz.cy / f.GetHeight()));
605 sw.Translate(-f.left, -f.top);
606 RenderSVG(sw, svg, resloader);
607 return ib;
608 }
609
RenderSVGImage(Size sz,const char * svg)610 Image RenderSVGImage(Size sz, const char *svg)
611 {
612 return RenderSVGImage(sz, svg, Event<String, String&>());
613 }
614
IsSVG(const char * svg)615 bool IsSVG(const char *svg)
616 {
617 try {
618 XmlParser xml(svg);
619 while(!xml.IsTag())
620 xml.Skip();
621 if(xml.Tag("svg"))
622 return true;
623 }
624 catch(XmlError e) {
625 }
626 return false;
627 }
628
GetSVGPathBoundingBox(const char * path)629 Rectf GetSVGPathBoundingBox(const char *path)
630 {
631 NilPainter nilp;
632 BoundsPainter p(nilp);
633 p.Path(path).Fill(Black());
634 return p.Get();
635 }
636
637 }
638