| /* | |
| * 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 "modules/svg/include/SkSVGDOM.h" | |
| #include "include/core/SkData.h" | |
| #include "include/core/SkFontMgr.h" | |
| #include "include/core/SkString.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkTo.h" | |
| #include "modules/skshaper/include/SkShaper_factory.h" | |
| #include "modules/svg/include/SkSVGAttribute.h" | |
| #include "modules/svg/include/SkSVGAttributeParser.h" | |
| #include "modules/svg/include/SkSVGCircle.h" | |
| #include "modules/svg/include/SkSVGClipPath.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/SkSVGFeComponentTransfer.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/SkSVGFeMerge.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" | |
| #include <array> | |
| #include <cstring> | |
| #include <stdint.h> | |
| #include <tuple> | |
| #include <utility> | |
| namespace { | |
| bool SetIRIAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr, | |
| const char* stringValue) { | |
| auto parseResult = SkSVGAttributeParser::parse<SkSVGIRI>(stringValue); | |
| if (!parseResult.has_value()) { | |
| 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.has_value()) { | |
| 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.has_value()) { | |
| 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.has_value()) { | |
| 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); | |
| }; | |
| static constexpr auto gAttributeParseInfo = std::to_array<SortedDictionaryEntry<AttrParseInfo>>({ | |
| { "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 }}, | |
| }); | |
| static constexpr auto gTagFactories = std::to_array<SortedDictionaryEntry<sk_sp<SkSVGNode> (*)()>>({ | |
| { "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(); }}, | |
| { "feComponentTransfer", []() -> sk_sp<SkSVGNode> { return SkSVGFeComponentTransfer::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(); }}, | |
| { "feFuncA" , []() -> sk_sp<SkSVGNode> { return SkSVGFeFunc::MakeFuncA(); }}, | |
| { "feFuncB" , []() -> sk_sp<SkSVGNode> { return SkSVGFeFunc::MakeFuncB(); }}, | |
| { "feFuncG" , []() -> sk_sp<SkSVGNode> { return SkSVGFeFunc::MakeFuncG(); }}, | |
| { "feFuncR" , []() -> sk_sp<SkSVGNode> { return SkSVGFeFunc::MakeFuncR(); }}, | |
| { "feGaussianBlur" , []() -> sk_sp<SkSVGNode> { return SkSVGFeGaussianBlur::Make(); }}, | |
| { "feImage" , []() -> sk_sp<SkSVGNode> { return SkSVGFeImage::Make(); }}, | |
| { "feMerge" , []() -> sk_sp<SkSVGNode> { return SkSVGFeMerge::Make(); }}, | |
| { "feMergeNode" , []() -> sk_sp<SkSVGNode> { return SkSVGFeMergeNode::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>(std::size(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) < std::size(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>(std::size(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) < std::size(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; | |
| } | |
| SkSVGDOM::Builder& SkSVGDOM::Builder::setTextShapingFactory(sk_sp<SkShapers::Factory> f) { | |
| fTextShapingFactory = f; | |
| 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>(); | |
| auto factory = fTextShapingFactory ? fTextShapingFactory : SkShapers::Primitive::Factory(); | |
| return sk_sp<SkSVGDOM>(new SkSVGDOM(sk_sp<SkSVGSVG>(static_cast<SkSVGSVG*>(root.release())), | |
| std::move(fFontMgr), | |
| std::move(resource_provider), | |
| std::move(mapper), | |
| std::move(factory))); | |
| } | |
| SkSVGDOM::SkSVGDOM(sk_sp<SkSVGSVG> root, | |
| sk_sp<SkFontMgr> fmgr, | |
| sk_sp<skresources::ResourceProvider> rp, | |
| SkSVGIDMapper&& mapper, | |
| sk_sp<SkShapers::Factory> fact) | |
| : fRoot(std::move(root)) | |
| , fFontMgr(std::move(fmgr)) | |
| , fTextShapingFactory(std::move(fact)) | |
| , fResourceProvider(std::move(rp)) | |
| , fIDMapper(std::move(mapper)) | |
| , fContainerSize(fRoot->intrinsicSize(SkSVGLengthContext(SkSize::Make(0, 0)))) { | |
| SkASSERT(fResourceProvider); | |
| SkASSERT(fTextShapingFactory); | |
| } | |
| sk_sp<SkSVGDOM> SkSVGDOM::MakeFromStream(SkStream& str) { return Builder().make(str); } | |
| 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}, | |
| fTextShapingFactory)); | |
| } | |
| } | |
| 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}, | |
| fTextShapingFactory), | |
| 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); | |
| } |