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