e1dadd74f8
https://www.w3.org/TR/SVG/painting.html#StrokeDashoffsetProperty Change-Id: Ia25d0048a56ac3835cabcb4e1794d91667367d7c Reviewed-on: https://skia-review.googlesource.com/59820 Reviewed-by: Robert Phillips <robertphillips@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
511 lines
18 KiB
C++
511 lines
18 KiB
C++
/*
|
|
* 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 "SkCanvas.h"
|
|
#include "SkDOM.h"
|
|
#include "SkParsePath.h"
|
|
#include "SkString.h"
|
|
#include "SkSVGAttributeParser.h"
|
|
#include "SkSVGCircle.h"
|
|
#include "SkSVGClipPath.h"
|
|
#include "SkSVGDefs.h"
|
|
#include "SkSVGDOM.h"
|
|
#include "SkSVGEllipse.h"
|
|
#include "SkSVGG.h"
|
|
#include "SkSVGLine.h"
|
|
#include "SkSVGLinearGradient.h"
|
|
#include "SkSVGNode.h"
|
|
#include "SkSVGPath.h"
|
|
#include "SkSVGPattern.h"
|
|
#include "SkSVGPoly.h"
|
|
#include "SkSVGRadialGradient.h"
|
|
#include "SkSVGRect.h"
|
|
#include "SkSVGRenderContext.h"
|
|
#include "SkSVGStop.h"
|
|
#include "SkSVGSVG.h"
|
|
#include "SkSVGTypes.h"
|
|
#include "SkSVGUse.h"
|
|
#include "SkSVGValue.h"
|
|
#include "SkTSearch.h"
|
|
|
|
namespace {
|
|
|
|
bool SetPaintAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGPaint paint;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parsePaint(&paint)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGPaintValue(paint));
|
|
return true;
|
|
}
|
|
|
|
bool SetColorAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGColorType color;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseColor(&color)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGColorValue(color));
|
|
return true;
|
|
}
|
|
|
|
bool SetIRIAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGStringType iri;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseIRI(&iri)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGStringValue(iri));
|
|
return true;
|
|
}
|
|
|
|
bool SetClipPathAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGClip clip;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseClipPath(&clip)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGClipValue(clip));
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SetPathDataAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkPath path;
|
|
if (!SkParsePath::FromSVGString(stringValue, &path)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGPathValue(path));
|
|
return true;
|
|
}
|
|
|
|
bool SetTransformAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGTransformType transform;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseTransform(&transform)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGTransformValue(transform));
|
|
return true;
|
|
}
|
|
|
|
bool SetLengthAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGLength length;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseLength(&length)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGLengthValue(length));
|
|
return true;
|
|
}
|
|
|
|
bool SetNumberAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGNumberType number;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseNumber(&number)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGNumberValue(number));
|
|
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 SetLineCapAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGLineCap lineCap;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseLineCap(&lineCap)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGLineCapValue(lineCap));
|
|
return true;
|
|
}
|
|
|
|
bool SetLineJoinAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGLineJoin lineJoin;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseLineJoin(&lineJoin)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGLineJoinValue(lineJoin));
|
|
return true;
|
|
}
|
|
|
|
bool SetSpreadMethodAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGSpreadMethod spread;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseSpreadMethod(&spread)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGSpreadMethodValue(spread));
|
|
return true;
|
|
}
|
|
|
|
bool SetPointsAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGPointsType points;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parsePoints(&points)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGPointsValue(points));
|
|
return true;
|
|
}
|
|
|
|
bool SetFillRuleAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGFillRule fillRule;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseFillRule(&fillRule)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGFillRuleValue(fillRule));
|
|
return true;
|
|
}
|
|
|
|
bool SetVisibilityAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGVisibility visibility;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseVisibility(&visibility)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGVisibilityValue(visibility));
|
|
return true;
|
|
}
|
|
|
|
bool SetDashArrayAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
|
|
const char* stringValue) {
|
|
SkSVGDashArray dashArray;
|
|
SkSVGAttributeParser parser(stringValue);
|
|
if (!parser.parseDashArray(&dashArray)) {
|
|
return false;
|
|
}
|
|
|
|
node->setAttribute(attr, SkSVGDashArrayValue(dashArray));
|
|
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;
|
|
};
|
|
|
|
void 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[] = {
|
|
{ "clip-path" , { SkSVGAttribute::kClipPath , SetClipPathAttribute }},
|
|
{ "clip-rule" , { SkSVGAttribute::kClipRule , SetFillRuleAttribute }},
|
|
{ "cx" , { SkSVGAttribute::kCx , SetLengthAttribute }},
|
|
{ "cy" , { SkSVGAttribute::kCy , SetLengthAttribute }},
|
|
{ "d" , { SkSVGAttribute::kD , SetPathDataAttribute }},
|
|
{ "fill" , { SkSVGAttribute::kFill , SetPaintAttribute }},
|
|
{ "fill-opacity" , { SkSVGAttribute::kFillOpacity , SetNumberAttribute }},
|
|
{ "fill-rule" , { SkSVGAttribute::kFillRule , SetFillRuleAttribute }},
|
|
// focal point x & y
|
|
{ "fx" , { SkSVGAttribute::kFx , SetLengthAttribute }},
|
|
{ "fy" , { SkSVGAttribute::kFy , SetLengthAttribute }},
|
|
{ "gradientTransform", { SkSVGAttribute::kGradientTransform, SetTransformAttribute }},
|
|
{ "height" , { SkSVGAttribute::kHeight , SetLengthAttribute }},
|
|
{ "offset" , { SkSVGAttribute::kOffset , SetLengthAttribute }},
|
|
{ "opacity" , { SkSVGAttribute::kOpacity , SetNumberAttribute }},
|
|
{ "patternTransform" , { SkSVGAttribute::kPatternTransform , SetTransformAttribute }},
|
|
{ "points" , { SkSVGAttribute::kPoints , SetPointsAttribute }},
|
|
{ "r" , { SkSVGAttribute::kR , SetLengthAttribute }},
|
|
{ "rx" , { SkSVGAttribute::kRx , SetLengthAttribute }},
|
|
{ "ry" , { SkSVGAttribute::kRy , SetLengthAttribute }},
|
|
{ "spreadMethod" , { SkSVGAttribute::kSpreadMethod , SetSpreadMethodAttribute }},
|
|
{ "stop-color" , { SkSVGAttribute::kStopColor , SetColorAttribute }},
|
|
{ "stop-opacity" , { SkSVGAttribute::kStopOpacity , SetNumberAttribute }},
|
|
{ "stroke" , { SkSVGAttribute::kStroke , SetPaintAttribute }},
|
|
{ "stroke-dasharray" , { SkSVGAttribute::kStrokeDashArray , SetDashArrayAttribute }},
|
|
{ "stroke-dashoffset", { SkSVGAttribute::kStrokeDashOffset , SetLengthAttribute }},
|
|
{ "stroke-linecap" , { SkSVGAttribute::kStrokeLineCap , SetLineCapAttribute }},
|
|
{ "stroke-linejoin" , { SkSVGAttribute::kStrokeLineJoin , SetLineJoinAttribute }},
|
|
{ "stroke-miterlimit", { SkSVGAttribute::kStrokeMiterLimit , SetNumberAttribute }},
|
|
{ "stroke-opacity" , { SkSVGAttribute::kStrokeOpacity , SetNumberAttribute }},
|
|
{ "stroke-width" , { SkSVGAttribute::kStrokeWidth , SetLengthAttribute }},
|
|
{ "style" , { SkSVGAttribute::kUnknown , SetStyleAttributes }},
|
|
{ "transform" , { SkSVGAttribute::kTransform , SetTransformAttribute }},
|
|
{ "viewBox" , { SkSVGAttribute::kViewBox , SetViewBoxAttribute }},
|
|
{ "visibility" , { SkSVGAttribute::kVisibility , SetVisibilityAttribute }},
|
|
{ "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(); }},
|
|
{ "g" , []() -> sk_sp<SkSVGNode> { return SkSVGG::Make(); }},
|
|
{ "line" , []() -> sk_sp<SkSVGNode> { return SkSVGLine::Make(); }},
|
|
{ "linearGradient", []() -> sk_sp<SkSVGNode> { return SkSVGLinearGradient::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" , []() -> sk_sp<SkSVGNode> { return SkSVGSVG::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) {}
|
|
|
|
const SkSVGNode* fParent;
|
|
SkSVGIDMapper* fIDMapper;
|
|
};
|
|
|
|
void set_string_attribute(const sk_sp<SkSVGNode>& node, const char* name, const char* value) {
|
|
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;
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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) {
|
|
SkASSERT(dom.countChildren(xmlNode) == 0);
|
|
// TODO: text handling
|
|
return nullptr;
|
|
}
|
|
|
|
SkASSERT(elemType == SkDOM::kElement_Type);
|
|
|
|
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));
|
|
sk_sp<SkSVGNode> node = gTagFactories[tagIndex].fValue();
|
|
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::SkSVGDOM()
|
|
: fContainerSize(SkSize::Make(0, 0)) {
|
|
}
|
|
|
|
sk_sp<SkSVGDOM> SkSVGDOM::MakeFromDOM(const SkDOM& xmlDom) {
|
|
sk_sp<SkSVGDOM> dom = sk_make_sp<SkSVGDOM>();
|
|
|
|
ConstructionContext ctx(&dom->fIDMapper);
|
|
dom->fRoot = construct_svg_node(xmlDom, ctx, xmlDom.getRootNode());
|
|
|
|
// Reset the default container size to match the intrinsic SVG size.
|
|
dom->setContainerSize(dom->intrinsicSize());
|
|
|
|
return dom;
|
|
}
|
|
|
|
sk_sp<SkSVGDOM> SkSVGDOM::MakeFromStream(SkStream& svgStream) {
|
|
SkDOM xmlDom;
|
|
if (!xmlDom.build(svgStream)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return MakeFromDOM(xmlDom);
|
|
}
|
|
|
|
void SkSVGDOM::render(SkCanvas* canvas) const {
|
|
if (fRoot) {
|
|
SkSVGLengthContext lctx(fContainerSize);
|
|
SkSVGPresentationContext pctx;
|
|
fRoot->render(SkSVGRenderContext(canvas, fIDMapper, lctx, pctx));
|
|
}
|
|
}
|
|
|
|
SkSize SkSVGDOM::intrinsicSize() const {
|
|
if (!fRoot || fRoot->tag() != SkSVGTag::kSvg) {
|
|
return SkSize::Make(0, 0);
|
|
}
|
|
|
|
// Intrinsic sizes are never relative, so the viewport size is irrelevant.
|
|
const SkSVGLengthContext lctx(SkSize::Make(0, 0));
|
|
return static_cast<const SkSVGSVG*>(fRoot.get())->intrinsicSize(lctx);
|
|
}
|
|
|
|
const SkSize& SkSVGDOM::containerSize() const {
|
|
return fContainerSize;
|
|
}
|
|
|
|
void SkSVGDOM::setContainerSize(const SkSize& containerSize) {
|
|
// TODO: inval
|
|
fContainerSize = containerSize;
|
|
}
|
|
|
|
void SkSVGDOM::setRoot(sk_sp<SkSVGNode> root) {
|
|
fRoot = std::move(root);
|
|
}
|