/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkCanvas.h" #include "include/core/SkFontMgr.h" #include "include/core/SkString.h" #include "include/private/SkTo.h" #include "modules/svg/include/SkSVGAttributeParser.h" #include "modules/svg/include/SkSVGCircle.h" #include "modules/svg/include/SkSVGClipPath.h" #include "modules/svg/include/SkSVGDOM.h" #include "modules/svg/include/SkSVGDefs.h" #include "modules/svg/include/SkSVGEllipse.h" #include "modules/svg/include/SkSVGFeBlend.h" #include "modules/svg/include/SkSVGFeColorMatrix.h" #include "modules/svg/include/SkSVGFeComposite.h" #include "modules/svg/include/SkSVGFeDisplacementMap.h" #include "modules/svg/include/SkSVGFeFlood.h" #include "modules/svg/include/SkSVGFeGaussianBlur.h" #include "modules/svg/include/SkSVGFeImage.h" #include "modules/svg/include/SkSVGFeLightSource.h" #include "modules/svg/include/SkSVGFeLighting.h" #include "modules/svg/include/SkSVGFeMorphology.h" #include "modules/svg/include/SkSVGFeOffset.h" #include "modules/svg/include/SkSVGFeTurbulence.h" #include "modules/svg/include/SkSVGFilter.h" #include "modules/svg/include/SkSVGG.h" #include "modules/svg/include/SkSVGImage.h" #include "modules/svg/include/SkSVGLine.h" #include "modules/svg/include/SkSVGLinearGradient.h" #include "modules/svg/include/SkSVGMask.h" #include "modules/svg/include/SkSVGNode.h" #include "modules/svg/include/SkSVGPath.h" #include "modules/svg/include/SkSVGPattern.h" #include "modules/svg/include/SkSVGPoly.h" #include "modules/svg/include/SkSVGRadialGradient.h" #include "modules/svg/include/SkSVGRect.h" #include "modules/svg/include/SkSVGRenderContext.h" #include "modules/svg/include/SkSVGSVG.h" #include "modules/svg/include/SkSVGStop.h" #include "modules/svg/include/SkSVGText.h" #include "modules/svg/include/SkSVGTypes.h" #include "modules/svg/include/SkSVGUse.h" #include "modules/svg/include/SkSVGValue.h" #include "src/core/SkTSearch.h" #include "src/core/SkTraceEvent.h" #include "src/xml/SkDOM.h" namespace { bool SetIRIAttribute(const sk_sp& node, SkSVGAttribute attr, const char* stringValue) { auto parseResult = SkSVGAttributeParser::parse(stringValue); if (!parseResult.isValid()) { return false; } node->setAttribute(attr, SkSVGStringValue(parseResult->iri())); return true; } bool SetStringAttribute(const sk_sp& node, SkSVGAttribute attr, const char* stringValue) { SkString str(stringValue, strlen(stringValue)); SkSVGStringType strType = SkSVGStringType(str); node->setAttribute(attr, SkSVGStringValue(strType)); return true; } bool SetTransformAttribute(const sk_sp& node, SkSVGAttribute attr, const char* stringValue) { auto parseResult = SkSVGAttributeParser::parse(stringValue); if (!parseResult.isValid()) { return false; } node->setAttribute(attr, SkSVGTransformValue(*parseResult)); return true; } bool SetLengthAttribute(const sk_sp& node, SkSVGAttribute attr, const char* stringValue) { auto parseResult = SkSVGAttributeParser::parse(stringValue); if (!parseResult.isValid()) { return false; } node->setAttribute(attr, SkSVGLengthValue(*parseResult)); return true; } bool SetViewBoxAttribute(const sk_sp& node, SkSVGAttribute attr, const char* stringValue) { SkSVGViewBoxType viewBox; SkSVGAttributeParser parser(stringValue); if (!parser.parseViewBox(&viewBox)) { return false; } node->setAttribute(attr, SkSVGViewBoxValue(viewBox)); return true; } bool SetObjectBoundingBoxUnitsAttribute(const sk_sp& node, SkSVGAttribute attr, const char* stringValue) { auto parseResult = SkSVGAttributeParser::parse(stringValue); if (!parseResult.isValid()) { return false; } node->setAttribute(attr, SkSVGObjectBoundingBoxUnitsValue(*parseResult)); return true; } bool SetPreserveAspectRatioAttribute(const sk_sp& node, SkSVGAttribute attr, const char* stringValue) { SkSVGPreserveAspectRatio par; SkSVGAttributeParser parser(stringValue); if (!parser.parsePreserveAspectRatio(&par)) { return false; } node->setAttribute(attr, SkSVGPreserveAspectRatioValue(par)); return true; } SkString TrimmedString(const char* first, const char* last) { SkASSERT(first); SkASSERT(last); SkASSERT(first <= last); while (first <= last && *first <= ' ') { first++; } while (first <= last && *last <= ' ') { last--; } SkASSERT(last - first + 1 >= 0); return SkString(first, SkTo(last - first + 1)); } // Breaks a "foo: bar; baz: ..." string into key:value pairs. class StyleIterator { public: StyleIterator(const char* str) : fPos(str) { } std::tuple next() { SkString name, value; if (fPos) { const char* sep = this->nextSeparator(); SkASSERT(*sep == ';' || *sep == '\0'); const char* valueSep = strchr(fPos, ':'); if (valueSep && valueSep < sep) { name = TrimmedString(fPos, valueSep - 1); value = TrimmedString(valueSep + 1, sep - 1); } fPos = *sep ? sep + 1 : nullptr; } return std::make_tuple(name, value); } private: const char* nextSeparator() const { const char* sep = fPos; while (*sep != ';' && *sep != '\0') { sep++; } return sep; } const char* fPos; }; bool set_string_attribute(const sk_sp& node, const char* name, const char* value); bool SetStyleAttributes(const sk_sp& node, SkSVGAttribute, const char* stringValue) { SkString name, value; StyleIterator iter(stringValue); for (;;) { std::tie(name, value) = iter.next(); if (name.isEmpty()) { break; } set_string_attribute(node, name.c_str(), value.c_str()); } return true; } template struct SortedDictionaryEntry { const char* fKey; const T fValue; }; struct AttrParseInfo { SkSVGAttribute fAttr; bool (*fSetter)(const sk_sp& node, SkSVGAttribute attr, const char* stringValue); }; SortedDictionaryEntry gAttributeParseInfo[] = { { "cx" , { SkSVGAttribute::kCx , SetLengthAttribute }}, { "cy" , { SkSVGAttribute::kCy , SetLengthAttribute }}, { "filterUnits" , { SkSVGAttribute::kFilterUnits , SetObjectBoundingBoxUnitsAttribute }}, // focal point x & y { "fx" , { SkSVGAttribute::kFx , SetLengthAttribute }}, { "fy" , { SkSVGAttribute::kFy , SetLengthAttribute }}, { "height" , { SkSVGAttribute::kHeight , SetLengthAttribute }}, { "preserveAspectRatio", { SkSVGAttribute::kPreserveAspectRatio, SetPreserveAspectRatioAttribute }}, { "r" , { SkSVGAttribute::kR , SetLengthAttribute }}, { "rx" , { SkSVGAttribute::kRx , SetLengthAttribute }}, { "ry" , { SkSVGAttribute::kRy , SetLengthAttribute }}, { "style" , { SkSVGAttribute::kUnknown , SetStyleAttributes }}, { "text" , { SkSVGAttribute::kText , SetStringAttribute }}, { "transform" , { SkSVGAttribute::kTransform , SetTransformAttribute }}, { "viewBox" , { SkSVGAttribute::kViewBox , SetViewBoxAttribute }}, { "width" , { SkSVGAttribute::kWidth , SetLengthAttribute }}, { "x" , { SkSVGAttribute::kX , SetLengthAttribute }}, { "x1" , { SkSVGAttribute::kX1 , SetLengthAttribute }}, { "x2" , { SkSVGAttribute::kX2 , SetLengthAttribute }}, { "xlink:href" , { SkSVGAttribute::kHref , SetIRIAttribute }}, { "y" , { SkSVGAttribute::kY , SetLengthAttribute }}, { "y1" , { SkSVGAttribute::kY1 , SetLengthAttribute }}, { "y2" , { SkSVGAttribute::kY2 , SetLengthAttribute }}, }; SortedDictionaryEntry(*)()> gTagFactories[] = { { "a" , []() -> sk_sp { return SkSVGG::Make(); }}, { "circle" , []() -> sk_sp { return SkSVGCircle::Make(); }}, { "clipPath" , []() -> sk_sp { return SkSVGClipPath::Make(); }}, { "defs" , []() -> sk_sp { return SkSVGDefs::Make(); }}, { "ellipse" , []() -> sk_sp { return SkSVGEllipse::Make(); }}, { "feBlend" , []() -> sk_sp { return SkSVGFeBlend::Make(); }}, { "feColorMatrix" , []() -> sk_sp { return SkSVGFeColorMatrix::Make(); }}, { "feComposite" , []() -> sk_sp { return SkSVGFeComposite::Make(); }}, { "feDiffuseLighting" , []() -> sk_sp { return SkSVGFeDiffuseLighting::Make(); }}, { "feDisplacementMap" , []() -> sk_sp { return SkSVGFeDisplacementMap::Make(); }}, { "feDistantLight" , []() -> sk_sp { return SkSVGFeDistantLight::Make(); }}, { "feFlood" , []() -> sk_sp { return SkSVGFeFlood::Make(); }}, { "feGaussianBlur" , []() -> sk_sp { return SkSVGFeGaussianBlur::Make(); }}, { "feImage" , []() -> sk_sp { return SkSVGFeImage::Make(); }}, { "feMorphology" , []() -> sk_sp { return SkSVGFeMorphology::Make(); }}, { "feOffset" , []() -> sk_sp { return SkSVGFeOffset::Make(); }}, { "fePointLight" , []() -> sk_sp { return SkSVGFePointLight::Make(); }}, { "feSpecularLighting", []() -> sk_sp { return SkSVGFeSpecularLighting::Make(); }}, { "feSpotLight" , []() -> sk_sp { return SkSVGFeSpotLight::Make(); }}, { "feTurbulence" , []() -> sk_sp { return SkSVGFeTurbulence::Make(); }}, { "filter" , []() -> sk_sp { return SkSVGFilter::Make(); }}, { "g" , []() -> sk_sp { return SkSVGG::Make(); }}, { "image" , []() -> sk_sp { return SkSVGImage::Make(); }}, { "line" , []() -> sk_sp { return SkSVGLine::Make(); }}, { "linearGradient" , []() -> sk_sp { return SkSVGLinearGradient::Make(); }}, { "mask" , []() -> sk_sp { return SkSVGMask::Make(); }}, { "path" , []() -> sk_sp { return SkSVGPath::Make(); }}, { "pattern" , []() -> sk_sp { return SkSVGPattern::Make(); }}, { "polygon" , []() -> sk_sp { return SkSVGPoly::MakePolygon(); }}, { "polyline" , []() -> sk_sp { return SkSVGPoly::MakePolyline(); }}, { "radialGradient" , []() -> sk_sp { return SkSVGRadialGradient::Make(); }}, { "rect" , []() -> sk_sp { return SkSVGRect::Make(); }}, { "stop" , []() -> sk_sp { return SkSVGStop::Make(); }}, // "svg" handled explicitly { "text" , []() -> sk_sp { return SkSVGText::Make(); }}, { "textPath" , []() -> sk_sp { return SkSVGTextPath::Make(); }}, { "tspan" , []() -> sk_sp { return SkSVGTSpan::Make(); }}, { "use" , []() -> sk_sp { return SkSVGUse::Make(); }}, }; struct ConstructionContext { ConstructionContext(SkSVGIDMapper* mapper) : fParent(nullptr), fIDMapper(mapper) {} ConstructionContext(const ConstructionContext& other, const sk_sp& newParent) : fParent(newParent.get()), fIDMapper(other.fIDMapper) {} SkSVGNode* fParent; SkSVGIDMapper* fIDMapper; }; bool set_string_attribute(const sk_sp& node, const char* name, const char* value) { if (node->parseAndSetAttribute(name, value)) { // Handled by new code path return true; } const int attrIndex = SkStrSearch(&gAttributeParseInfo[0].fKey, SkTo(SK_ARRAY_COUNT(gAttributeParseInfo)), name, sizeof(gAttributeParseInfo[0])); if (attrIndex < 0) { #if defined(SK_VERBOSE_SVG_PARSING) SkDebugf("unhandled attribute: %s\n", name); #endif return false; } SkASSERT(SkTo(attrIndex) < SK_ARRAY_COUNT(gAttributeParseInfo)); const auto& attrInfo = gAttributeParseInfo[attrIndex].fValue; if (!attrInfo.fSetter(node, attrInfo.fAttr, value)) { #if defined(SK_VERBOSE_SVG_PARSING) SkDebugf("could not parse attribute: '%s=\"%s\"'\n", name, value); #endif return false; } return true; } void parse_node_attributes(const SkDOM& xmlDom, const SkDOM::Node* xmlNode, const sk_sp& svgNode, SkSVGIDMapper* mapper) { const char* name, *value; SkDOM::AttrIter attrIter(xmlDom, xmlNode); while ((name = attrIter.next(&value))) { // We're handling id attributes out of band for now. if (!strcmp(name, "id")) { mapper->set(SkString(value), svgNode); continue; } set_string_attribute(svgNode, name, value); } } sk_sp construct_svg_node(const SkDOM& dom, const ConstructionContext& ctx, const SkDOM::Node* xmlNode) { const char* elem = dom.getName(xmlNode); const SkDOM::Type elemType = dom.getType(xmlNode); if (elemType == SkDOM::kText_Type) { // Text literals require special handling. SkASSERT(dom.countChildren(xmlNode) == 0); auto txt = SkSVGTextLiteral::Make(); txt->setText(SkString(dom.getName(xmlNode))); ctx.fParent->appendChild(std::move(txt)); return nullptr; } SkASSERT(elemType == SkDOM::kElement_Type); auto make_node = [](const ConstructionContext& ctx, const char* elem) -> sk_sp { if (strcmp(elem, "svg") == 0) { // Outermost SVG element must be tagged as such. return SkSVGSVG::Make(ctx.fParent ? SkSVGSVG::Type::kInner : SkSVGSVG::Type::kRoot); } const int tagIndex = SkStrSearch(&gTagFactories[0].fKey, SkTo(SK_ARRAY_COUNT(gTagFactories)), elem, sizeof(gTagFactories[0])); if (tagIndex < 0) { #if defined(SK_VERBOSE_SVG_PARSING) SkDebugf("unhandled element: <%s>\n", elem); #endif return nullptr; } SkASSERT(SkTo(tagIndex) < SK_ARRAY_COUNT(gTagFactories)); return gTagFactories[tagIndex].fValue(); }; auto node = make_node(ctx, elem); if (!node) { return nullptr; } parse_node_attributes(dom, xmlNode, node, ctx.fIDMapper); ConstructionContext localCtx(ctx, node); for (auto* child = dom.getFirstChild(xmlNode, nullptr); child; child = dom.getNextSibling(child)) { sk_sp childNode = construct_svg_node(dom, localCtx, child); if (childNode) { node->appendChild(std::move(childNode)); } } return node; } } // anonymous namespace SkSVGDOM::Builder& SkSVGDOM::Builder::setFontManager(sk_sp fmgr) { fFontMgr = std::move(fmgr); return *this; } SkSVGDOM::Builder& SkSVGDOM::Builder::setResourceProvider(sk_sp rp) { fResourceProvider = std::move(rp); return *this; } sk_sp SkSVGDOM::Builder::make(SkStream& str) const { TRACE_EVENT0("skia", TRACE_FUNC); SkDOM xmlDom; if (!xmlDom.build(str)) { return nullptr; } SkSVGIDMapper mapper; ConstructionContext ctx(&mapper); auto root = construct_svg_node(xmlDom, ctx, xmlDom.getRootNode()); if (!root || root->tag() != SkSVGTag::kSvg) { return nullptr; } class NullResourceProvider final : public skresources::ResourceProvider { sk_sp load(const char[], const char[]) const override { return nullptr; } }; auto resource_provider = fResourceProvider ? fResourceProvider : sk_make_sp(); return sk_sp(new SkSVGDOM(sk_sp(static_cast(root.release())), std::move(fFontMgr), std::move(resource_provider), std::move(mapper))); } SkSVGDOM::SkSVGDOM(sk_sp root, sk_sp fmgr, sk_sp rp, SkSVGIDMapper&& mapper) : fRoot(std::move(root)) , fFontMgr(std::move(fmgr)) , fResourceProvider(std::move(rp)) , fIDMapper(std::move(mapper)) , fContainerSize(fRoot->intrinsicSize(SkSVGLengthContext(SkSize::Make(0, 0)))) { SkASSERT(fResourceProvider); } void SkSVGDOM::render(SkCanvas* canvas) const { TRACE_EVENT0("skia", TRACE_FUNC); if (fRoot) { SkSVGLengthContext lctx(fContainerSize); SkSVGPresentationContext pctx; fRoot->render(SkSVGRenderContext(canvas, fFontMgr, fResourceProvider, fIDMapper, lctx, pctx, {nullptr, nullptr})); } } const SkSize& SkSVGDOM::containerSize() const { return fContainerSize; } void SkSVGDOM::setContainerSize(const SkSize& containerSize) { // TODO: inval fContainerSize = containerSize; } sk_sp* SkSVGDOM::findNodeById(const char* id) { SkString idStr(id); return this->fIDMapper.find(idStr); } // TODO(fuego): move this to SkSVGNode or its own CU. bool SkSVGNode::setAttribute(const char* attributeName, const char* attributeValue) { return set_string_attribute(sk_ref_sp(this), attributeName, attributeValue); }