|  | /* | 
|  | * 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<SkSVGNode>& node, SkSVGAttribute attr, | 
|  | const char* stringValue) { | 
|  | auto parseResult = SkSVGAttributeParser::parse<SkSVGIRI>(stringValue); | 
|  | if (!parseResult.isValid()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | node->setAttribute(attr, SkSVGStringValue(parseResult->iri())); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SetStringAttribute(const sk_sp<SkSVGNode>& 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<SkSVGNode>& node, SkSVGAttribute attr, | 
|  | const char* stringValue) { | 
|  | auto parseResult = SkSVGAttributeParser::parse<SkSVGTransformType>(stringValue); | 
|  | if (!parseResult.isValid()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | node->setAttribute(attr, SkSVGTransformValue(*parseResult)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SetLengthAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr, | 
|  | const char* stringValue) { | 
|  | auto parseResult = SkSVGAttributeParser::parse<SkSVGLength>(stringValue); | 
|  | if (!parseResult.isValid()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | node->setAttribute(attr, SkSVGLengthValue(*parseResult)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SetViewBoxAttribute(const sk_sp<SkSVGNode>& 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<SkSVGNode>& node, | 
|  | SkSVGAttribute attr, | 
|  | const char* stringValue) { | 
|  | auto parseResult = SkSVGAttributeParser::parse<SkSVGObjectBoundingBoxUnits>(stringValue); | 
|  | if (!parseResult.isValid()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | node->setAttribute(attr, SkSVGObjectBoundingBoxUnitsValue(*parseResult)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SetPreserveAspectRatioAttribute(const sk_sp<SkSVGNode>& 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<size_t>(last - first + 1)); | 
|  | } | 
|  |  | 
|  | // Breaks a "foo: bar; baz: ..." string into key:value pairs. | 
|  | class StyleIterator { | 
|  | public: | 
|  | StyleIterator(const char* str) : fPos(str) { } | 
|  |  | 
|  | std::tuple<SkString, SkString> 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<SkSVGNode>& node, const char* name, const char* value); | 
|  |  | 
|  | bool SetStyleAttributes(const sk_sp<SkSVGNode>& 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<typename T> | 
|  | struct SortedDictionaryEntry { | 
|  | const char* fKey; | 
|  | const T     fValue; | 
|  | }; | 
|  |  | 
|  | struct AttrParseInfo { | 
|  | SkSVGAttribute fAttr; | 
|  | bool (*fSetter)(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr, const char* stringValue); | 
|  | }; | 
|  |  | 
|  | SortedDictionaryEntry<AttrParseInfo> 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<sk_sp<SkSVGNode>(*)()> gTagFactories[] = { | 
|  | { "a"                 , []() -> sk_sp<SkSVGNode> { return SkSVGG::Make();                  }}, | 
|  | { "circle"            , []() -> sk_sp<SkSVGNode> { return SkSVGCircle::Make();             }}, | 
|  | { "clipPath"          , []() -> sk_sp<SkSVGNode> { return SkSVGClipPath::Make();           }}, | 
|  | { "defs"              , []() -> sk_sp<SkSVGNode> { return SkSVGDefs::Make();               }}, | 
|  | { "ellipse"           , []() -> sk_sp<SkSVGNode> { return SkSVGEllipse::Make();            }}, | 
|  | { "feBlend"           , []() -> sk_sp<SkSVGNode> { return SkSVGFeBlend::Make();            }}, | 
|  | { "feColorMatrix"     , []() -> sk_sp<SkSVGNode> { return SkSVGFeColorMatrix::Make();      }}, | 
|  | { "feComposite"       , []() -> sk_sp<SkSVGNode> { return SkSVGFeComposite::Make();        }}, | 
|  | { "feDiffuseLighting" , []() -> sk_sp<SkSVGNode> { return SkSVGFeDiffuseLighting::Make();  }}, | 
|  | { "feDisplacementMap" , []() -> sk_sp<SkSVGNode> { return SkSVGFeDisplacementMap::Make();  }}, | 
|  | { "feDistantLight"    , []() -> sk_sp<SkSVGNode> { return SkSVGFeDistantLight::Make();     }}, | 
|  | { "feFlood"           , []() -> sk_sp<SkSVGNode> { return SkSVGFeFlood::Make();            }}, | 
|  | { "feGaussianBlur"    , []() -> sk_sp<SkSVGNode> { return SkSVGFeGaussianBlur::Make();     }}, | 
|  | { "feImage"           , []() -> sk_sp<SkSVGNode> { return SkSVGFeImage::Make();            }}, | 
|  | { "feMorphology"      , []() -> sk_sp<SkSVGNode> { return SkSVGFeMorphology::Make();       }}, | 
|  | { "feOffset"          , []() -> sk_sp<SkSVGNode> { return SkSVGFeOffset::Make();           }}, | 
|  | { "fePointLight"      , []() -> sk_sp<SkSVGNode> { return SkSVGFePointLight::Make();       }}, | 
|  | { "feSpecularLighting", []() -> sk_sp<SkSVGNode> { return SkSVGFeSpecularLighting::Make(); }}, | 
|  | { "feSpotLight"       , []() -> sk_sp<SkSVGNode> { return SkSVGFeSpotLight::Make();        }}, | 
|  | { "feTurbulence"      , []() -> sk_sp<SkSVGNode> { return SkSVGFeTurbulence::Make();       }}, | 
|  | { "filter"            , []() -> sk_sp<SkSVGNode> { return SkSVGFilter::Make();             }}, | 
|  | { "g"                 , []() -> sk_sp<SkSVGNode> { return SkSVGG::Make();                  }}, | 
|  | { "image"             , []() -> sk_sp<SkSVGNode> { return SkSVGImage::Make();              }}, | 
|  | { "line"              , []() -> sk_sp<SkSVGNode> { return SkSVGLine::Make();               }}, | 
|  | { "linearGradient"    , []() -> sk_sp<SkSVGNode> { return SkSVGLinearGradient::Make();     }}, | 
|  | { "mask"              , []() -> sk_sp<SkSVGNode> { return SkSVGMask::Make();               }}, | 
|  | { "path"              , []() -> sk_sp<SkSVGNode> { return SkSVGPath::Make();               }}, | 
|  | { "pattern"           , []() -> sk_sp<SkSVGNode> { return SkSVGPattern::Make();            }}, | 
|  | { "polygon"           , []() -> sk_sp<SkSVGNode> { return SkSVGPoly::MakePolygon();        }}, | 
|  | { "polyline"          , []() -> sk_sp<SkSVGNode> { return SkSVGPoly::MakePolyline();       }}, | 
|  | { "radialGradient"    , []() -> sk_sp<SkSVGNode> { return SkSVGRadialGradient::Make();     }}, | 
|  | { "rect"              , []() -> sk_sp<SkSVGNode> { return SkSVGRect::Make();               }}, | 
|  | { "stop"              , []() -> sk_sp<SkSVGNode> { return SkSVGStop::Make();               }}, | 
|  | //    "svg" handled explicitly | 
|  | { "text"              , []() -> sk_sp<SkSVGNode> { return SkSVGText::Make();               }}, | 
|  | { "textPath"          , []() -> sk_sp<SkSVGNode> { return SkSVGTextPath::Make();           }}, | 
|  | { "tspan"             , []() -> sk_sp<SkSVGNode> { return SkSVGTSpan::Make();              }}, | 
|  | { "use"               , []() -> sk_sp<SkSVGNode> { return SkSVGUse::Make();                }}, | 
|  | }; | 
|  |  | 
|  | struct ConstructionContext { | 
|  | ConstructionContext(SkSVGIDMapper* mapper) : fParent(nullptr), fIDMapper(mapper) {} | 
|  | ConstructionContext(const ConstructionContext& other, const sk_sp<SkSVGNode>& newParent) | 
|  | : fParent(newParent.get()), fIDMapper(other.fIDMapper) {} | 
|  |  | 
|  | SkSVGNode*     fParent; | 
|  | SkSVGIDMapper* fIDMapper; | 
|  | }; | 
|  |  | 
|  | bool set_string_attribute(const sk_sp<SkSVGNode>& 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<int>(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<size_t>(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<SkSVGNode>& 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<SkSVGNode> 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<SkSVGNode> { | 
|  | 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<int>(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<size_t>(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<SkSVGNode> 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<SkFontMgr> fmgr) { | 
|  | fFontMgr = std::move(fmgr); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | SkSVGDOM::Builder& SkSVGDOM::Builder::setResourceProvider(sk_sp<skresources::ResourceProvider> rp) { | 
|  | fResourceProvider = std::move(rp); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | sk_sp<SkSVGDOM> 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<SkData> load(const char[], const char[]) const override { return nullptr; } | 
|  | }; | 
|  |  | 
|  | auto resource_provider = fResourceProvider ? fResourceProvider | 
|  | : sk_make_sp<NullResourceProvider>(); | 
|  |  | 
|  | return sk_sp<SkSVGDOM>(new SkSVGDOM(sk_sp<SkSVGSVG>(static_cast<SkSVGSVG*>(root.release())), | 
|  | std::move(fFontMgr), std::move(resource_provider), | 
|  | std::move(mapper))); | 
|  | } | 
|  |  | 
|  | SkSVGDOM::SkSVGDOM(sk_sp<SkSVGSVG> root, sk_sp<SkFontMgr> fmgr, | 
|  | sk_sp<skresources::ResourceProvider> 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})); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SkSVGDOM::renderNode(SkCanvas* canvas, SkSVGPresentationContext& pctx, const char* id) const { | 
|  | TRACE_EVENT0("skia", TRACE_FUNC); | 
|  |  | 
|  | if (fRoot) { | 
|  | SkSVGLengthContext lctx(fContainerSize); | 
|  | fRoot->renderNode(SkSVGRenderContext(canvas, fFontMgr, fResourceProvider, fIDMapper, | 
|  | lctx, pctx, {nullptr, nullptr}), | 
|  | SkSVGIRI(SkSVGIRI::Type::kLocal, SkSVGStringType(id))); | 
|  | } | 
|  | } | 
|  |  | 
|  | const SkSize& SkSVGDOM::containerSize() const { | 
|  | return fContainerSize; | 
|  | } | 
|  |  | 
|  | void SkSVGDOM::setContainerSize(const SkSize& containerSize) { | 
|  | // TODO: inval | 
|  | fContainerSize = containerSize; | 
|  | } | 
|  |  | 
|  | sk_sp<SkSVGNode>* 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); | 
|  | } |