e932d4b3a9
There's a bit of friction with this attribute, because per spec it is an inherited presentation attribute, but in Skia it is part of the actual SkPath state. So we must add some plumbing to SkSVGShape & friends to allow overriding the fill type at render-time. R=robertphillips@google.com,stephana@google.com Change-Id: I9c926d653c6211beb3914bffac50d4349dbdd2c0 Reviewed-on: https://skia-review.googlesource.com/5415 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
457 lines
16 KiB
C++
457 lines
16 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 "SkSVGDefs.h"
|
|
#include "SkSVGDOM.h"
|
|
#include "SkSVGEllipse.h"
|
|
#include "SkSVGG.h"
|
|
#include "SkSVGLine.h"
|
|
#include "SkSVGLinearGradient.h"
|
|
#include "SkSVGNode.h"
|
|
#include "SkSVGPath.h"
|
|
#include "SkSVGPoly.h"
|
|
#include "SkSVGRect.h"
|
|
#include "SkSVGRenderContext.h"
|
|
#include "SkSVGStop.h"
|
|
#include "SkSVGSVG.h"
|
|
#include "SkSVGTypes.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 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;
|
|
}
|
|
|
|
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[] = {
|
|
{ "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 }},
|
|
{ "gradientTransform", { SkSVGAttribute::kGradientTransform, SetTransformAttribute }},
|
|
{ "height" , { SkSVGAttribute::kHeight , SetLengthAttribute }},
|
|
{ "offset" , { SkSVGAttribute::kOffset , SetLengthAttribute }},
|
|
{ "opacity" , { SkSVGAttribute::kOpacity , SetNumberAttribute }},
|
|
{ "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-linecap" , { SkSVGAttribute::kStrokeLineCap , SetLineCapAttribute }},
|
|
{ "stroke-linejoin" , { SkSVGAttribute::kStrokeLineJoin , SetLineJoinAttribute }},
|
|
{ "stroke-opacity" , { SkSVGAttribute::kStrokeOpacity , SetNumberAttribute }},
|
|
{ "stroke-width" , { SkSVGAttribute::kStrokeWidth , SetLengthAttribute }},
|
|
{ "style" , { SkSVGAttribute::kUnknown , SetStyleAttributes }},
|
|
{ "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[] = {
|
|
{ "circle" , []() -> sk_sp<SkSVGNode> { return SkSVGCircle::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(); }},
|
|
{ "polygon" , []() -> sk_sp<SkSVGNode> { return SkSVGPoly::MakePolygon(); }},
|
|
{ "polyline" , []() -> sk_sp<SkSVGNode> { return SkSVGPoly::MakePolyline(); }},
|
|
{ "rect" , []() -> sk_sp<SkSVGNode> { return SkSVGRect::Make(); }},
|
|
{ "stop" , []() -> sk_sp<SkSVGNode> { return SkSVGStop::Make(); }},
|
|
{ "svg" , []() -> sk_sp<SkSVGNode> { return SkSVGSVG::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) {
|
|
SkSVGRenderContext ctx(canvas,
|
|
fIDMapper,
|
|
SkSVGLengthContext(fContainerSize),
|
|
SkSVGPresentationContext());
|
|
fRoot->render(ctx);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|