[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:
parent
ebe79ffd86
commit
e932d4b3a9
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ public:
|
||||
SkSVGAttributeParser(const char[]);
|
||||
|
||||
bool parseColor(SkSVGColorType*);
|
||||
bool parseFillRule(SkSVGFillRule*);
|
||||
bool parseNumber(SkSVGNumberType*);
|
||||
bool parseLength(SkSVGLength*);
|
||||
bool parseViewBox(SkSVGViewBoxType*);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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 }},
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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&);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
|
Loading…
Reference in New Issue
Block a user