[SVGDom] Add fill-rule support

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>
This commit is contained in:
Florin Malita 2016-12-01 13:35:11 -05:00 committed by Skia Commit-Bot
parent ebe79ffd86
commit e932d4b3a9
25 changed files with 135 additions and 17 deletions

View File

@ -12,6 +12,7 @@ SkSVGPresentationAttributes SkSVGPresentationAttributes::MakeInitial() {
result.fFill.set(SkSVGPaint(SkSVGColorType(SK_ColorBLACK)));
result.fFillOpacity.set(SkSVGNumberType(1));
result.fFillRule.set(SkSVGFillRule(SkSVGFillRule::Type::kNonZero));
result.fStroke.set(SkSVGPaint(SkSVGPaint::Type::kNone));
result.fStrokeLineCap.set(SkSVGLineCap(SkSVGLineCap::Type::kButt));

View File

@ -19,6 +19,7 @@ enum class SkSVGAttribute {
kD,
kFill,
kFillOpacity,
kFillRule,
kGradientTransform,
kHeight,
kHref,
@ -56,6 +57,7 @@ struct SkSVGPresentationAttributes {
SkTLazy<SkSVGPaint> fFill;
SkTLazy<SkSVGNumberType> fFillOpacity;
SkTLazy<SkSVGFillRule> fFillRule;
SkTLazy<SkSVGPaint> fStroke;
SkTLazy<SkSVGLineCap> fStrokeLineCap;

View File

@ -551,3 +551,26 @@ bool SkSVGAttributeParser::parsePoints(SkSVGPointsType* points) {
return false;
}
// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
bool SkSVGAttributeParser::parseFillRule(SkSVGFillRule* fillRule) {
static const struct {
SkSVGFillRule::Type fType;
const char* fName;
} gFillRuleInfo[] = {
{ SkSVGFillRule::Type::kNonZero, "nonzero" },
{ SkSVGFillRule::Type::kEvenOdd, "evenodd" },
{ SkSVGFillRule::Type::kInherit, "inherit" },
};
bool parsedValue = false;
for (size_t i = 0; i < SK_ARRAY_COUNT(gFillRuleInfo); ++i) {
if (this->parseExpectedStringToken(gFillRuleInfo[i].fName)) {
*fillRule = SkSVGFillRule(gFillRuleInfo[i].fType);
parsedValue = true;
break;
}
}
return parsedValue && this->parseEOSToken();
}

View File

@ -15,6 +15,7 @@ public:
SkSVGAttributeParser(const char[]);
bool parseColor(SkSVGColorType*);
bool parseFillRule(SkSVGFillRule*);
bool parseNumber(SkSVGNumberType*);
bool parseLength(SkSVGLength*);
bool parseViewBox(SkSVGViewBoxType*);

View File

@ -47,7 +47,7 @@ void SkSVGCircle::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
}
void SkSVGCircle::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
const SkPaint& paint) const {
const SkPaint& paint, SkPath::FillType) const {
const auto cx = lctx.resolve(fCx, SkSVGLengthContext::LengthType::kHorizontal);
const auto cy = lctx.resolve(fCy, SkSVGLengthContext::LengthType::kVertical);
const auto r = lctx.resolve(fR , SkSVGLengthContext::LengthType::kOther);

View File

@ -23,7 +23,8 @@ public:
protected:
void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
SkPath::FillType) const override;
private:
SkSVGCircle();

View File

@ -173,6 +173,18 @@ bool SetPointsAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
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);
@ -256,6 +268,7 @@ SortedDictionaryEntry<AttrParseInfo> gAttributeParseInfo[] = {
{ "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 }},

View File

@ -56,7 +56,7 @@ void SkSVGEllipse::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
}
void SkSVGEllipse::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
const SkPaint& paint) const {
const SkPaint& paint, SkPath::FillType) const {
const auto cx = lctx.resolve(fCx, SkSVGLengthContext::LengthType::kHorizontal);
const auto cy = lctx.resolve(fCy, SkSVGLengthContext::LengthType::kVertical);
const auto rx = lctx.resolve(fRx, SkSVGLengthContext::LengthType::kHorizontal);

View File

@ -24,7 +24,8 @@ public:
protected:
void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
SkPath::FillType) const override;
private:
SkSVGEllipse();

View File

@ -56,7 +56,7 @@ void SkSVGLine::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
}
void SkSVGLine::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
const SkPaint& paint) const {
const SkPaint& paint, SkPath::FillType) const {
const auto x1 = lctx.resolve(fX1, SkSVGLengthContext::LengthType::kHorizontal);
const auto y1 = lctx.resolve(fY1, SkSVGLengthContext::LengthType::kVertical);
const auto x2 = lctx.resolve(fX2, SkSVGLengthContext::LengthType::kHorizontal);

View File

@ -24,7 +24,8 @@ public:
protected:
void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
SkPath::FillType) const override;
private:
SkSVGLine();

View File

@ -49,6 +49,10 @@ void SkSVGNode::setFillOpacity(const SkSVGNumberType& opacity) {
SkSVGNumberType(SkTPin<SkScalar>(opacity.value(), 0, 1)));
}
void SkSVGNode::setFillRule(const SkSVGFillRule& fillRule) {
fPresentationAttributes.fFillRule.set(fillRule);
}
void SkSVGNode::setOpacity(const SkSVGNumberType& opacity) {
fPresentationAttributes.fOpacity.set(
SkSVGNumberType(SkTPin<SkScalar>(opacity.value(), 0, 1)));
@ -79,6 +83,11 @@ void SkSVGNode::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
this->setFillOpacity(*opacity);
}
break;
case SkSVGAttribute::kFillRule:
if (const SkSVGFillRuleValue* fillRule = v.as<SkSVGFillRuleValue>()) {
this->setFillRule(*fillRule);
}
break;
case SkSVGAttribute::kOpacity:
if (const SkSVGNumberValue* opacity = v.as<SkSVGNumberValue>()) {
this->setOpacity(*opacity);

View File

@ -47,6 +47,7 @@ public:
void setFill(const SkSVGPaint&);
void setFillOpacity(const SkSVGNumberType&);
void setFillRule(const SkSVGFillRule&);
void setOpacity(const SkSVGNumberType&);
void setStroke(const SkSVGPaint&);
void setStrokeOpacity(const SkSVGNumberType&);

View File

@ -25,6 +25,9 @@ void SkSVGPath::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
}
}
void SkSVGPath::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint) const {
void SkSVGPath::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint,
SkPath::FillType fillType) const {
// the passed fillType follows inheritance rules and needs to be applied at draw time.
fPath.setFillType(fillType);
canvas->drawPath(fPath, paint);
}

View File

@ -21,12 +21,13 @@ public:
protected:
void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
SkPath::FillType) const override;
private:
SkSVGPath();
SkPath fPath;
mutable SkPath fPath; // mutated in onDraw(), to apply inherited fill types.
typedef SkSVGShape INHERITED;
};

View File

@ -6,6 +6,7 @@
*/
#include "SkCanvas.h"
#include "SkTLazy.h"
#include "SkSVGRenderContext.h"
#include "SkSVGPoly.h"
#include "SkSVGValue.h"
@ -31,6 +32,9 @@ void SkSVGPoly::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
}
}
void SkSVGPoly::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint) const {
void SkSVGPoly::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint,
SkPath::FillType fillType) const {
// the passed fillType follows inheritance rules and needs to be applied at draw time.
fPath.setFillType(fillType);
canvas->drawPath(fPath, paint);
}

View File

@ -29,12 +29,13 @@ public:
protected:
void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
SkPath::FillType) const override;
private:
SkSVGPoly(SkSVGTag);
SkPath fPath;
mutable SkPath fPath; // mutated in onDraw(), to apply inherited fill types.
typedef SkSVGShape INHERITED;
};

View File

@ -75,7 +75,7 @@ void SkSVGRect::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
}
void SkSVGRect::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
const SkPaint& paint) const {
const SkPaint& paint, SkPath::FillType) const {
const SkRect rect = lctx.resolveRect(fX, fY, fWidth, fHeight);
const SkScalar rx = lctx.resolve(fRx, SkSVGLengthContext::LengthType::kHorizontal);
const SkScalar ry = lctx.resolve(fRy, SkSVGLengthContext::LengthType::kVertical);

View File

@ -26,7 +26,8 @@ public:
protected:
void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
SkPath::FillType) const override;
private:
SkSVGRect();

View File

@ -6,6 +6,7 @@
*/
#include "SkCanvas.h"
#include "SkPath.h"
#include "SkSVGAttribute.h"
#include "SkSVGNode.h"
#include "SkSVGRenderContext.h"
@ -188,6 +189,13 @@ void commitToPaint<SkSVGAttribute::kStrokeWidth>(const SkSVGPresentationAttribut
pctx->fStrokePaint.setStrokeWidth(strokeWidth);
}
template <>
void commitToPaint<SkSVGAttribute::kFillRule>(const SkSVGPresentationAttributes&,
const SkSVGRenderContext&,
SkSVGPresentationContext*) {
// Not part of the SkPaint state; applied to the path at render time.
}
} // anonymous ns
SkSVGPresentationContext::SkSVGPresentationContext()
@ -258,6 +266,7 @@ void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttr
ApplyLazyInheritedAttribute(Fill);
ApplyLazyInheritedAttribute(FillOpacity);
ApplyLazyInheritedAttribute(FillRule);
ApplyLazyInheritedAttribute(Stroke);
ApplyLazyInheritedAttribute(StrokeLineCap);
ApplyLazyInheritedAttribute(StrokeLineJoin);

View File

@ -65,6 +65,8 @@ public:
const SkSVGLengthContext& lengthContext() const { return *fLengthContext; }
SkSVGLengthContext* writableLengthContext() { return fLengthContext.writable(); }
const SkSVGPresentationContext& presentationContext() const { return *fPresentationContext; }
SkCanvas* canvas() const { return fCanvas; }
enum ApplyFlags {

View File

@ -11,16 +11,31 @@
SkSVGShape::SkSVGShape(SkSVGTag t) : INHERITED(t) {}
void SkSVGShape::onRender(const SkSVGRenderContext& ctx) const {
const SkPath::FillType fillType =
FillRuleToFillType(*ctx.presentationContext().fInherited.fFillRule.get());
// TODO: this approach forces duplicate geometry resolution in onDraw(); refactor to avoid.
if (const SkPaint* fillPaint = ctx.fillPaint()) {
this->onDraw(ctx.canvas(), ctx.lengthContext(), *fillPaint);
this->onDraw(ctx.canvas(), ctx.lengthContext(), *fillPaint, fillType);
}
if (const SkPaint* strokePaint = ctx.strokePaint()) {
this->onDraw(ctx.canvas(), ctx.lengthContext(), *strokePaint);
this->onDraw(ctx.canvas(), ctx.lengthContext(), *strokePaint, fillType);
}
}
void SkSVGShape::appendChild(sk_sp<SkSVGNode>) {
SkDebugf("cannot append child nodes to an SVG shape.\n");
}
SkPath::FillType SkSVGShape::FillRuleToFillType(const SkSVGFillRule& fillRule) {
switch (fillRule.type()) {
case SkSVGFillRule::Type::kNonZero:
return SkPath::kWinding_FillType;
case SkSVGFillRule::Type::kEvenOdd:
return SkPath::kEvenOdd_FillType;
default:
SkASSERT(false);
return SkPath::kWinding_FillType;
}
}

View File

@ -8,6 +8,7 @@
#ifndef SkSVGShape_DEFINED
#define SkSVGShape_DEFINED
#include "SkPath.h"
#include "SkSVGTransformableNode.h"
class SkSVGLengthContext;
@ -24,7 +25,10 @@ protected:
void onRender(const SkSVGRenderContext&) const final;
virtual void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const = 0;
virtual void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
SkPath::FillType) const = 0;
static SkPath::FillType FillRuleToFillType(const SkSVGFillRule&);
private:
typedef SkSVGTransformableNode INHERITED;

View File

@ -191,4 +191,27 @@ private:
Type fType;
};
class SkSVGFillRule {
public:
enum class Type {
kNonZero,
kEvenOdd,
kInherit,
};
constexpr SkSVGFillRule() : fType(Type::kInherit) {}
constexpr explicit SkSVGFillRule(Type t) : fType(t) {}
SkSVGFillRule(const SkSVGFillRule&) = default;
SkSVGFillRule& operator=(const SkSVGFillRule&) = default;
bool operator==(const SkSVGFillRule& other) const { return fType == other.fType; }
bool operator!=(const SkSVGFillRule& other) const { return !(*this == other); }
Type type() const { return fType; }
private:
Type fType;
};
#endif // SkSVGTypes_DEFINED

View File

@ -18,6 +18,7 @@ class SkSVGValue : public SkNoncopyable {
public:
enum class Type {
kColor,
kFillRule,
kLength,
kLineCap,
kLineJoin,
@ -70,6 +71,7 @@ private:
};
using SkSVGColorValue = SkSVGWrapperValue<SkSVGColorType , SkSVGValue::Type::kColor >;
using SkSVGFillRuleValue = SkSVGWrapperValue<SkSVGFillRule , SkSVGValue::Type::kFillRule >;
using SkSVGLengthValue = SkSVGWrapperValue<SkSVGLength , SkSVGValue::Type::kLength >;
using SkSVGPathValue = SkSVGWrapperValue<SkPath , SkSVGValue::Type::kPath >;
using SkSVGTransformValue = SkSVGWrapperValue<SkSVGTransformType, SkSVGValue::Type::kTransform>;