| /* |
| * 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/base/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>(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; |
| } |
| |
| 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); |
| } |