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));
kD,
kFill,
kFillOpacity,
+ kFillRule,
kGradientTransform,
kHeight,
kHref,
SkTLazy<SkSVGPaint> fFill;
SkTLazy<SkSVGNumberType> fFillOpacity;
+ SkTLazy<SkSVGFillRule> fFillRule;
SkTLazy<SkSVGPaint> fStroke;
SkTLazy<SkSVGLineCap> fStrokeLineCap;
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();
+}
SkSVGAttributeParser(const char[]);
bool parseColor(SkSVGColorType*);
+ bool parseFillRule(SkSVGFillRule*);
bool parseNumber(SkSVGNumberType*);
bool parseLength(SkSVGLength*);
bool parseViewBox(SkSVGViewBoxType*);
}
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);
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();
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);
{ "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 }},
}
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);
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();
}
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);
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();
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)));
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);
void setFill(const SkSVGPaint&);
void setFillOpacity(const SkSVGNumberType&);
+ void setFillRule(const SkSVGFillRule&);
void setOpacity(const SkSVGNumberType&);
void setStroke(const SkSVGPaint&);
void setStrokeOpacity(const SkSVGNumberType&);
}
}
-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);
}
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;
};
*/
#include "SkCanvas.h"
+#include "SkTLazy.h"
#include "SkSVGRenderContext.h"
#include "SkSVGPoly.h"
#include "SkSVGValue.h"
}
}
-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);
}
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;
};
}
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);
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();
*/
#include "SkCanvas.h"
+#include "SkPath.h"
#include "SkSVGAttribute.h"
#include "SkSVGNode.h"
#include "SkSVGRenderContext.h"
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()
ApplyLazyInheritedAttribute(Fill);
ApplyLazyInheritedAttribute(FillOpacity);
+ ApplyLazyInheritedAttribute(FillRule);
ApplyLazyInheritedAttribute(Stroke);
ApplyLazyInheritedAttribute(StrokeLineCap);
ApplyLazyInheritedAttribute(StrokeLineJoin);
const SkSVGLengthContext& lengthContext() const { return *fLengthContext; }
SkSVGLengthContext* writableLengthContext() { return fLengthContext.writable(); }
+ const SkSVGPresentationContext& presentationContext() const { return *fPresentationContext; }
+
SkCanvas* canvas() const { return fCanvas; }
enum ApplyFlags {
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;
+ }
+}
#ifndef SkSVGShape_DEFINED
#define SkSVGShape_DEFINED
+#include "SkPath.h"
#include "SkSVGTransformableNode.h"
class SkSVGLengthContext;
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;
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
public:
enum class Type {
kColor,
+ kFillRule,
kLength,
kLineCap,
kLineJoin,
};
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>;