From 14b843bf331e34e40505c1578e84683bbf9ef1da Mon Sep 17 00:00:00 2001 From: cxl Date: Thu, 7 Mar 2019 09:44:09 +0000 Subject: [PATCH] Painter: SVG improvements git-svn-id: svn://ultimatepp.org/upp/trunk@12815 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppsrc/Painter/Painter.h | 2 +- uppsrc/Painter/PainterPath.cpp | 14 ++ uppsrc/Painter/SvgInternal.h | 34 +++-- uppsrc/Painter/SvgParser.cpp | 269 +++++++++++++++++++-------------- uppsrc/RichText/RichImage.cpp | 1 + 5 files changed, 194 insertions(+), 126 deletions(-) diff --git a/uppsrc/Painter/Painter.h b/uppsrc/Painter/Painter.h index 991a5a2ca..7ced7ffe6 100644 --- a/uppsrc/Painter/Painter.h +++ b/uppsrc/Painter/Painter.h @@ -123,7 +123,7 @@ protected: virtual void FillOp(const Pointf& p1, const RGBA& color1, const Pointf& p2, const RGBA& color2, int style) = 0; - virtual void FillOp(const Pointf& f, const RGBA& color1, + virtual void FillOp(const Pointf& f, const RGBA& color1, const Pointf& c, double r, const RGBA& color2, int style) = 0; virtual void FillOp(const RGBA& color1, const RGBA& color2, const Xform2D& transsrc, diff --git a/uppsrc/Painter/PainterPath.cpp b/uppsrc/Painter/PainterPath.cpp index e50257cf9..db895500e 100644 --- a/uppsrc/Painter/PainterPath.cpp +++ b/uppsrc/Painter/PainterPath.cpp @@ -29,6 +29,7 @@ Pointf Painter::ReadPoint(CParser& p, Pointf current, bool rel) Painter& Painter::Path(CParser& p) { Pointf current(0, 0); + bool done = false; while(!p.IsEof()) { int c = p.GetChar(); p.Spaces(); @@ -43,20 +44,24 @@ Painter& Painter::Path(CParser& p) current = ReadPoint(p, current, rel); Line(current); } + done = true; break; case 'Z': Close(); + done = true; break; case 'H': while(p.IsDouble2()) { current.x = p.ReadDouble() + rel * current.x; Line(current); + done = true; } break; case 'V': while(p.IsDouble2()) { current.y = p.ReadDouble() + rel * current.y; Line(current); + done = true; } break; case 'C': @@ -65,6 +70,7 @@ Painter& Painter::Path(CParser& p) t2 = ReadPoint(p, current, rel); current = ReadPoint(p, current, rel); Cubic(t1, t2, current); + done = true; } break; case 'S': @@ -72,6 +78,7 @@ Painter& Painter::Path(CParser& p) t2 = ReadPoint(p, current, rel); current = ReadPoint(p, current, rel); Cubic(t2, current); + done = true; } break; case 'Q': @@ -79,12 +86,14 @@ Painter& Painter::Path(CParser& p) t1 = ReadPoint(p, current, rel); current = ReadPoint(p, current, rel); Quadratic(t1, current); + done = true; } break; case 'T': while(p.IsDouble2()) { current = ReadPoint(p, current, rel); Quadratic(current); + done = true; } break; case 'A': @@ -95,12 +104,17 @@ Painter& Painter::Path(CParser& p) bool sweep = ReadBool(p); current = ReadPoint(p, current, rel); SvgArc(t1, xangle * M_PI / 180.0, large, sweep, current); + done = true; } break; default: + if(!done) + Move(0, 0); // to clear previous path return *this; } } + if(!done) + Move(0, 0); // to clear previous path return *this; } diff --git a/uppsrc/Painter/SvgInternal.h b/uppsrc/Painter/SvgInternal.h index b77aae05d..980b85dfe 100644 --- a/uppsrc/Painter/SvgInternal.h +++ b/uppsrc/Painter/SvgInternal.h @@ -43,7 +43,7 @@ struct BoundsPainter : public NilPainter { BoundsPainter(Painter& sw) : sw(sw) { New(); mtx.Add(); svg_boundingbox = Null; compute_svg_boundingbox = false; } }; -struct SvgParser : XmlParser { +struct SvgParser { Painter& sw; void ParseSVG(const char *svg, const char *folder); @@ -85,13 +85,17 @@ struct SvgParser : XmlParser { double dash_offset; Font font; + + const XmlNode *n; }; - Array state; - bool closed; - Pointf prev; - Xform2D lastTransform; - BoundsPainter bp; + const XmlNode *current = NULL; + Array state; + bool closed; + Pointf prev; + Xform2D lastTransform; + BoundsPainter bp; + VectorMap idmap; void Reset(); @@ -102,28 +106,30 @@ struct SvgParser : XmlParser { void Style(const char *style); Xform2D Transform(const char *transform); - String Txt(const char *id) { return (*this)[id]; } + String Txt(const char *id) { return current ? current->Attr(id) : String(); } double Dbl(const char *id, double def = 0) { return Nvl(StrDbl(Txt(id)), def); } - void StartElement(); + void StartElement(const XmlNode& n); void EndElement(); void StrokeFinishElement(); void FinishElement(); void DoGradient(int gi, bool stroke); - void Poly(bool line); - void ParseG(); - void ParseGradient(bool radial); + void Poly(const XmlNode& n, bool line); + void Items(const XmlNode& n, int depth); + void Element(const XmlNode& n, int depth, bool dosymbols = false); + void ParseGradient(const XmlNode& n, bool radial); void ResolveGradient(int i); - + void MapIds(const XmlNode& n); + bool ReadBool(CParser& p); double ReadDouble(CParser& p); Pointf ReadPoint0(CParser& p, bool rel); Pointf ReadPoint(CParser& p, bool rel); void Path(const char *s); - bool Parse(); + bool Parse(const char *xml); Event resloader; - SvgParser(const char *svg, Painter& sw); + SvgParser(Painter& sw); }; diff --git a/uppsrc/Painter/SvgParser.cpp b/uppsrc/Painter/SvgParser.cpp index 5cd406e3e..4643197fb 100644 --- a/uppsrc/Painter/SvgParser.cpp +++ b/uppsrc/Painter/SvgParser.cpp @@ -1,4 +1,5 @@ #include "Painter.h" +#include "Painter.h" #define LLOG(x) @@ -32,15 +33,17 @@ void SvgParser::ResolveGradient(int i) g.style = Nvl(Nvl(g.style, g2.style), GRADIENT_PAD); } -void SvgParser::StartElement() +void SvgParser::StartElement(const XmlNode& n) { - state.Add(); - state.Top() = state[state.GetCount() - 2]; + State& s = state.Add(); + s = state[state.GetCount() - 2]; + s.n = current; + current = &n; bp.Begin(); bp.Transform(Transform(Txt("transform"))); Style(Txt("style")); - for(int i = 0; i < GetAttrCount(); i++) - ProcessValue(GetAttr(i), (*this)[i]); + for(int i = 0; i < n.GetAttrCount(); i++) + ProcessValue(n.AttrId(i), n.Attr(i)); closed = false; } @@ -49,6 +52,7 @@ void SvgParser::EndElement() if(!closed) { sw.Stroke(0, Black()); // Finish path to allow new transformations, if not yet done } + current = state.Top().n; state.Drop(); bp.End(); } @@ -143,34 +147,37 @@ void SvgParser::FinishElement() { State& s = state.Top(); double o = s.opacity * s.fill_opacity; - if(o != 1) { - sw.Begin(); - sw.Opacity(o); + if(o > 0) { + if(o != 1) { + sw.Begin(); + sw.Opacity(o); + } + if(s.fill_gradient >= 0) + DoGradient(s.fill_gradient, false); + else + if(!IsNull(s.fill)) { + sw.Fill(s.fill); + bp.Finish(0); + closed = true; + } + if(o != 1) + sw.End(); } - if(s.fill_gradient >= 0) - DoGradient(s.fill_gradient, false); - else - if(!IsNull(s.fill)) { - sw.Fill(s.fill); - bp.Finish(0); - closed = true; - } - if(o != 1) - sw.End(); StrokeFinishElement(); } -void SvgParser::ParseGradient(bool radial) +void SvgParser::ParseGradient(const XmlNode& n, bool radial) { - LLOG("ParseGradient " << Txt("id")); - Gradient& g = gradient.Add(Txt("id")); + LLOG("ParseGradient " << n.Attr("id")); + Gradient& g = gradient.Add(n.Attr("id")); g.radial = radial; - g.user_space = Txt("gradientUnits") == "userSpaceOnUse"; - g.transform = Txt("gradientTransform"); - g.href = Txt("xlink:href"); + g.user_space = n.Attr("gradientUnits") == "userSpaceOnUse"; + g.transform = n.Attr("gradientTransform"); + g.href = n.Attr("xlink:href"); g.resolved = IsNull(g.href); double def = g.resolved ? 0.0 : (double)Null; double def5 = g.resolved ? 0.5 : (double)Null; + auto Dbl = [&](const char *id, double def) { return Nvl(StrDbl(n.Attr(id)), def); }; g.c.x = Dbl("cx", def5); g.c.y = Dbl("cy", def5); g.r = Dbl("r", g.resolved ? 1.0 : (double)Null); @@ -182,11 +189,12 @@ void SvgParser::ParseGradient(bool radial) g.b.y = Dbl("y2", def); g.style = decode(Txt("spreadMethod"), "pad", GRADIENT_PAD, "reflect", GRADIENT_REFLECT, "repeat", GRADIENT_REPEAT, (int)Null); - while(!End()) - if(TagE("stop")) { + + for(const XmlNode& m : n) + if(m.IsTag("stop")) { Stop &s = g.stop.Add(); double offset = 0; - String st = Txt("style"); + String st = m.Attr("style"); const char *style = st; double opacity = 1; Color color; @@ -218,23 +226,21 @@ void SvgParser::ParseGradient(bool radial) else value.Cat(*style++); } - value = Txt("stop-color"); + value = m.Attr("stop-color"); if(value.GetCount()) color = GetColor(value); - value = Txt("stop-opacity"); + value = m.Attr("stop-opacity"); if(value.GetCount()) - opacity = StrDbl(value); + opacity = Nvl(StrDbl(value), opacity); s.color = clamp(int(opacity * 255 + 0.5), 0, 255) * color; - s.offset = Nvl(Dbl("offset"), offset); + s.offset = Nvl(StrDbl(m.Attr("offset")), offset); } - else - Skip(); } -void SvgParser::Poly(bool line) +void SvgParser::Poly(const XmlNode& n, bool line) { Vector r; - String value = Txt("points"); + String value = n.Attr("points"); try { CParser p(value); while(!p.IsEof()) { @@ -248,7 +254,7 @@ void SvgParser::Poly(bool line) } catch(CParser::Error) {} if(r.GetCount()) { - StartElement(); + StartElement(n); bp.Move(r[0].x, r[0].y); for(int i = 1; i < r.GetCount(); ++i) bp.Line(r[i].x, r[i].y); @@ -266,9 +272,8 @@ double ReadNumber(CParser& p) return p.ReadDouble(); } -Rectf GetSvgViewBox(XmlParser& xml) +Rectf GetSvgViewBox(const String& v) { - String v = xml["viewBox"]; Rectf r = Null; if(v.GetCount()) { try { @@ -285,6 +290,16 @@ Rectf GetSvgViewBox(XmlParser& xml) return r; } +Rectf GetSvgViewBox(XmlParser& xml) +{ + return GetSvgViewBox(xml["viewBox"]); +} + +Rectf GetSvgViewBox(const XmlNode& xml) +{ + return GetSvgViewBox(xml.Attr("viewBox")); +} + Sizef GetSvgSize(XmlParser& xml) { Sizef sz; @@ -305,49 +320,68 @@ Pointf GetSvgPos(XmlParser& xml) return p; } -void SvgParser::ParseG() { -#ifdef _DEBUG - LLOG("====== TAG " << (IsTag() ? PeekTag() : String())); -#endif - if(Tag("defs")) { - while(!End()) - if(Tag("linearGradient")) - ParseGradient(false); +Sizef GetSvgSize(const XmlNode& xml) +{ + Sizef sz; + sz.cx = StrDbl(xml.Attr("width")); + sz.cy = StrDbl(xml.Attr("height")); + if(IsNull(sz.cx) || IsNull(sz.cy)) + sz = Null; + return sz; +} + +Pointf GetSvgPos(const XmlNode& xml) +{ + Pointf p; + p.x = StrDbl(xml.Attr("x")); + p.y = StrDbl(xml.Attr("y")); + if(IsNull(p.x) || IsNull(p.y)) + p = Null; + return p; +} + +void SvgParser::Element(const XmlNode& n, int depth, bool dosymbols) +{ + if(depth > 100) // defend against id recursion + return; + LLOG("====== " << n.GetTag()); + if(n.IsTag("defs")) { + for(const auto& m : n) + if(m.IsTag("linearGradient")) + ParseGradient(m, false); else - if(Tag("radialGradient")) - ParseGradient(true); - else // TODO: Implement pattern, filter, ... - Skip(); + if(m.IsTag("radialGradient")) + ParseGradient(m, true); } else - if(Tag("linearGradient")) - ParseGradient(false); + if(n.IsTag("linearGradient")) + ParseGradient(n, false); else - if(Tag("radialGradient")) - ParseGradient(true); + if(n.IsTag("radialGradient")) + ParseGradient(n, true); else - if(TagE("rect")) { - StartElement(); + if(n.IsTag("rect")) { + StartElement(n); bp.RoundedRectangle(Dbl("x"), Dbl("y"), Dbl("width"), Dbl("height"), Dbl("rx"), Dbl("ry")); FinishElement(); } else - if(TagE("ellipse")) { - StartElement(); + if(n.IsTag("ellipse")) { + StartElement(n); bp.Ellipse(Dbl("cx"), Dbl("cy"), Dbl("rx"), Dbl("ry")); FinishElement(); } else - if(TagE("circle")) { - StartElement(); + if(n.IsTag("circle")) { + StartElement(n); Pointf c(Dbl("cx"), Dbl("cy")); double r = Dbl("r"); bp.Ellipse(c.x, c.y, r, r); FinishElement(); } else - if(TagE("line")) { - StartElement(); + if(n.IsTag("line")) { + StartElement(n); Pointf a(Dbl("x1"), Dbl("y1")); Pointf b(Dbl("x2"), Dbl("y2")); bp.Move(a); @@ -355,20 +389,20 @@ void SvgParser::ParseG() { FinishElement(); } else - if(TagE("polygon")) - Poly(false); + if(n.IsTag("polygon")) + Poly(n, false); else - if(TagE("polyline")) - Poly(true); + if(n.IsTag("polyline")) + Poly(n, true); else - if(TagE("path")) { - StartElement(); + if(n.IsTag("path")) { + StartElement(n); bp.Path(Txt("d")); FinishElement(); } else - if(TagE("image")) { - StartElement(); + if(n.IsTag("image")) { + StartElement(n); String fileName = Txt("xlink:href"); String data; resloader(fileName, data); @@ -382,10 +416,10 @@ void SvgParser::ParseG() { EndElement(); } else - if(Tag("text")) { - StartElement(); - auto DoText = [&] { - String text = ReadText(); + if(n.IsTag("text")) { + StartElement(n); + auto DoText = [&](const XmlNode& n) { + String text = n.GatherText(); text.Replace("\n", " "); text.Replace("\r", ""); text.Replace("\t", " "); @@ -394,52 +428,67 @@ void SvgParser::ParseG() { bp.Text(Dbl("x"), Dbl("y") - fnt.GetAscent(), text, fnt); } }; - DoText(); - while(Tag("tspan")) { - StartElement(); - DoText(); - FinishElement(); - PassEnd(); - } + DoText(n); + for(const auto& m : n) + if(m.IsTag("tspan")) { + StartElement(m); + DoText(m); + FinishElement(); + } FinishElement(); - PassEnd(); } else - if(Tag("g")) { - StartElement(); - while(!End()) - ParseG(); - EndElement(); + if(n.IsTag("g") || n.IsTag("symbol") && dosymbols) + Items(n, depth); + else + if(n.IsTag("use")) { + const XmlNode *idn = idmap.Get(Nvl(n.Attr("href"), n.Attr("xlink:href")), NULL); + if(idn) { + StartElement(n); + bp.Translate(Dbl("x"), Dbl("y")); + Element(*idn, depth + 1, true); + FinishElement(); + } } else - if(Tag("svg")) { - Sizef sz = GetSvgSize(*this); + if(n.IsTag("svg")) { + Sizef sz = GetSvgSize(n); if(!IsNull(sz)) { - Pointf p = Nvl(GetSvgPos(*this), Pointf(0, 0)); - Rectf vb = Nvl(GetSvgViewBox(*this), sz); + Pointf p = Nvl(GetSvgPos(n), Pointf(0, 0)); + Rectf vb = Nvl(GetSvgViewBox(n), sz); //TODO: For now, we support "xyMid meet" only bp.Translate(p.x, p.y); bp.Scale(min(sz.cx / vb.GetWidth(), sz.cy / vb.GetHeight())); bp.Translate(-vb.TopLeft()); - StartElement(); - while(!End()) - ParseG(); - EndElement(); + Items(n, depth); } } - else - Skip(); } -bool SvgParser::Parse() { +void SvgParser::Items(const XmlNode& n, int depth) +{ + StartElement(n); + for(const auto& m : n) + Element(m, depth); + EndElement(); +} + +void SvgParser::MapIds(const XmlNode& n) +{ + String id = n.Attr("id"); + if(id.GetCount()) + idmap.Add('#' + id, &n); + for(const auto& m : n) + MapIds(m); +} + +bool SvgParser::Parse(const char *xml) { try { - while(!IsTag()) - Skip(); - PassTag("svg"); - bp.Begin(); - while(!End()) - ParseG(); - bp.End(); + XmlNode n = ParseXML(xml); + MapIds(n); + for(const auto& m : n) + if(m.IsTag("svg")) + Items(m, 0); } catch(XmlError e) { return false; @@ -447,20 +496,18 @@ bool SvgParser::Parse() { return true; } -SvgParser::SvgParser(const char *svg, Painter& sw) -: XmlParser(svg), - sw(sw), - bp(sw) +SvgParser::SvgParser(Painter& sw) +: sw(sw), bp(sw) { Reset(); } bool ParseSVG(Painter& p, const char *svg, Event resloader, Rectf *boundingbox) { - SvgParser sp(svg, p); + SvgParser sp(p); sp.bp.compute_svg_boundingbox = boundingbox; sp.resloader = resloader; - if(!sp.Parse()) + if(!sp.Parse(svg)) return false; if(boundingbox) *boundingbox = sp.bp.svg_boundingbox; diff --git a/uppsrc/RichText/RichImage.cpp b/uppsrc/RichText/RichImage.cpp index d590a9673..7541f8511 100644 --- a/uppsrc/RichText/RichImage.cpp +++ b/uppsrc/RichText/RichImage.cpp @@ -216,6 +216,7 @@ Size RichRawImage::GetPhysicalSize(const Value& data) const Zoom z = GetRichTextStdScreenZoom(); return z.d * (Size)f.GetSize() / z.m; } + return Size(0, 0); } Size RichRawImage::GetPixelSize(const Value& data) const