From 6ceef3dd67617c5f4572ada98d5ee85777d2db99 Mon Sep 17 00:00:00 2001 From: fmalita Date: Tue, 26 Jul 2016 18:46:34 -0700 Subject: [PATCH] Initial SVG model A minimal subset needed to render tiger.svg: , , , 'd', 'fill'/'stroke' (color-only), 'transform'. R=reed@google.com,robertphillips@google.com GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2164193002 Review-Url: https://codereview.chromium.org/2164193002 --- experimental/svg/model/SkSVGAttribute.cpp | 34 +++ experimental/svg/model/SkSVGAttribute.h | 41 ++++ experimental/svg/model/SkSVGContainer.cpp | 21 ++ experimental/svg/model/SkSVGContainer.h | 31 +++ experimental/svg/model/SkSVGDOM.cpp | 244 ++++++++++++++++++++++ experimental/svg/model/SkSVGDOM.h | 39 ++++ experimental/svg/model/SkSVGG.h | 25 +++ experimental/svg/model/SkSVGNode.cpp | 60 ++++++ experimental/svg/model/SkSVGNode.h | 56 +++++ experimental/svg/model/SkSVGPath.cpp | 37 ++++ experimental/svg/model/SkSVGPath.h | 38 ++++ experimental/svg/model/SkSVGRenderContext.cpp | 52 +++++ experimental/svg/model/SkSVGRenderContext.h | 36 ++++ experimental/svg/model/SkSVGSVG.cpp | 10 + experimental/svg/model/SkSVGSVG.h | 25 +++ experimental/svg/model/SkSVGTransformableNode.cpp | 26 +++ experimental/svg/model/SkSVGTransformableNode.h | 34 +++ experimental/svg/model/SkSVGValue.cpp | 8 + experimental/svg/model/SkSVGValue.h | 59 ++++++ gyp/samples.gypi | 2 + gyp/svg.gyp | 40 ++++ include/xml/SkDOM.h | 2 +- include/xml/SkXMLParser.h | 8 +- samplecode/SampleApp.cpp | 26 +++ samplecode/SampleSVGFile.cpp | 63 ++++++ 25 files changed, 1012 insertions(+), 5 deletions(-) create mode 100644 experimental/svg/model/SkSVGAttribute.cpp create mode 100644 experimental/svg/model/SkSVGAttribute.h create mode 100644 experimental/svg/model/SkSVGContainer.cpp create mode 100644 experimental/svg/model/SkSVGContainer.h create mode 100644 experimental/svg/model/SkSVGDOM.cpp create mode 100644 experimental/svg/model/SkSVGDOM.h create mode 100644 experimental/svg/model/SkSVGG.h create mode 100644 experimental/svg/model/SkSVGNode.cpp create mode 100644 experimental/svg/model/SkSVGNode.h create mode 100644 experimental/svg/model/SkSVGPath.cpp create mode 100644 experimental/svg/model/SkSVGPath.h create mode 100644 experimental/svg/model/SkSVGRenderContext.cpp create mode 100644 experimental/svg/model/SkSVGRenderContext.h create mode 100644 experimental/svg/model/SkSVGSVG.cpp create mode 100644 experimental/svg/model/SkSVGSVG.h create mode 100644 experimental/svg/model/SkSVGTransformableNode.cpp create mode 100644 experimental/svg/model/SkSVGTransformableNode.h create mode 100644 experimental/svg/model/SkSVGValue.cpp create mode 100644 experimental/svg/model/SkSVGValue.h create mode 100644 samplecode/SampleSVGFile.cpp diff --git a/experimental/svg/model/SkSVGAttribute.cpp b/experimental/svg/model/SkSVGAttribute.cpp new file mode 100644 index 0000000..c4a374e --- /dev/null +++ b/experimental/svg/model/SkSVGAttribute.cpp @@ -0,0 +1,34 @@ +/* + * 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 "SkSVGAttribute.h" +#include "SkSVGRenderContext.h" + +SkSVGPresentationAttributes::SkSVGPresentationAttributes() + : fFillIsSet(false) + , fStrokeIsSet(false) { } + +void SkSVGPresentationAttributes::setFill(SkColor c) { + fFill = c; + fFillIsSet = true; +} + +void SkSVGPresentationAttributes::setStroke(SkColor c) { + fStroke = c; + fStrokeIsSet = true; +} + + +void SkSVGPresentationAttributes::applyTo(SkTCopyOnFirstWrite& ctx) const { + if (fFillIsSet) { + ctx.writable()->setFillColor(fFill); + } + + if (fStrokeIsSet) { + ctx.writable()->setStrokeColor(fStroke); + } +} diff --git a/experimental/svg/model/SkSVGAttribute.h b/experimental/svg/model/SkSVGAttribute.h new file mode 100644 index 0000000..50989f1 --- /dev/null +++ b/experimental/svg/model/SkSVGAttribute.h @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGAttribute_DEFINED +#define SkSVGAttribute_DEFINED + +#include "SkColor.h" +#include "SkTLazy.h" + +enum class SkSVGAttribute { + d, + fill, + stroke, + transform, +}; + +class SkSVGRenderContext; + +class SkSVGPresentationAttributes { +public: + SkSVGPresentationAttributes(); + + void setFill(SkColor); + void setStroke(SkColor); + + void applyTo(SkTCopyOnFirstWrite&) const; + +private: + // Color only for now. + SkColor fFill; + SkColor fStroke; + + unsigned fFillIsSet : 1; + unsigned fStrokeIsSet : 1; +}; + +#endif // SkSVGAttribute_DEFINED diff --git a/experimental/svg/model/SkSVGContainer.cpp b/experimental/svg/model/SkSVGContainer.cpp new file mode 100644 index 0000000..f866797 --- /dev/null +++ b/experimental/svg/model/SkSVGContainer.cpp @@ -0,0 +1,21 @@ +/* + * 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 "SkSVGContainer.h" + +SkSVGContainer::SkSVGContainer(SkSVGTag t) : INHERITED(t) { } + +void SkSVGContainer::appendChild(sk_sp node) { + SkASSERT(node); + fChildren.push_back(std::move(node)); +} + +void SkSVGContainer::onRender(SkCanvas* canvas, const SkSVGRenderContext& ctx) const { + for (int i = 0; i < fChildren.count(); ++i) { + fChildren[i]->render(canvas, ctx); + } +} diff --git a/experimental/svg/model/SkSVGContainer.h b/experimental/svg/model/SkSVGContainer.h new file mode 100644 index 0000000..27c7944 --- /dev/null +++ b/experimental/svg/model/SkSVGContainer.h @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGContainer_DEFINED +#define SkSVGContainer_DEFINED + +#include "SkSVGTransformableNode.h" +#include "SkTArray.h" + +class SkSVGContainer : public SkSVGTransformableNode { +public: + virtual ~SkSVGContainer() = default; + + void appendChild(sk_sp) override; + +protected: + SkSVGContainer(SkSVGTag); + + void onRender(SkCanvas*, const SkSVGRenderContext&) const override; + +private: + SkSTArray<1, sk_sp, true> fChildren; + + typedef SkSVGTransformableNode INHERITED; +}; + +#endif // SkSVGSVG_DEFINED diff --git a/experimental/svg/model/SkSVGDOM.cpp b/experimental/svg/model/SkSVGDOM.cpp new file mode 100644 index 0000000..6f782c3 --- /dev/null +++ b/experimental/svg/model/SkSVGDOM.cpp @@ -0,0 +1,244 @@ +/* + * 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 "SkParse.h" +#include "SkParsePath.h" +#include "SkSVGDOM.h" +#include "SkSVGG.h" +#include "SkSVGNode.h" +#include "SkSVGPath.h" +#include "SkSVGSVG.h" +#include "SkSVGValue.h" +#include "SkTSearch.h" + +namespace { + +SkColor ParseColor(const char* str) { + // FIXME: real parser + if (*str++ != '#') { + return SK_ColorBLACK; + } + + uint32_t v; + const char* consumed = SkParse::FindHex(str, &v); + + switch(consumed - str) { + case 6: + // matched '#xxxxxx' + break; + case 3: + // matched '#xxx; + v = ((v << 12) & 0x00f00000) | + ((v << 8) & 0x000ff000) | + ((v << 4) & 0x00000ff0) | + ((v << 0) & 0x0000000f); + break; + default: + // failed + v = 0; + break; + } + + return v | 0xff000000; +} + +const char* ParseScalarPair(const char* str, SkScalar v[2]) { + str = SkParse::FindScalar(str, v); + if (str) { + const char* second = SkParse::FindScalar(str, v + 1); + if (!second) { + v[1] = v[0]; + } else { + str = second; + } + } + + return str; +} + +SkMatrix ParseTransform(const char* str) { + SkMatrix m = SkMatrix::I(); + + // FIXME: real parser + if (!strncmp(str, "matrix(", 7)) { + SkScalar values[6]; + str = SkParse::FindScalars(str + 7, values, 6); + if (str) { + m.setAffine(values); + } + } else if (!strncmp(str, "scale(", 6)) { + SkScalar values[2]; + str = ParseScalarPair(str + 6, values); + if (str) { + m.setScale(values[0], values[1]); + } + } else if (!strncmp(str, "translate(", 10)) { + SkScalar values[2]; + str = ParseScalarPair(str + 10, values); + if (str) { + m.setTranslate(values[0], values[1]); + } + } else if (!strncmp(str, "rotate(", 7)) { + SkScalar value; + str = SkParse::FindScalar(str + 7, &value); + if (str) { + m.setRotate(value); + } + } + + return m; +} + +bool SetPaintAttribute(const sk_sp& node, SkSVGAttribute attr, + const char* stringValue) { + node->setAttribute(attr, SkSVGColorValue(ParseColor(stringValue))); + return true; +} + +bool SetPathDataAttribute(const sk_sp& 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& node, SkSVGAttribute attr, + const char* stringValue) { + node->setAttribute(attr, SkSVGTransformValue(ParseTransform(stringValue))); + return true; +} + +template +struct SortedDictionaryEntry { + const char* fKey; + const T fValue; +}; + +struct AttrParseInfo { + SkSVGAttribute fAttr; + bool (*fSetter)(const sk_sp& node, SkSVGAttribute attr, const char* stringValue); +}; + +SortedDictionaryEntry gAttributeParseInfo[] = { + { "d", { SkSVGAttribute::d, SetPathDataAttribute }}, + { "fill", { SkSVGAttribute::fill, SetPaintAttribute }}, + { "stroke", { SkSVGAttribute::stroke, SetPaintAttribute }}, + { "transform", { SkSVGAttribute::transform, SetTransformAttribute }}, +}; + +SortedDictionaryEntry(*)()> gTagFactories[] = { + { "g" , []() -> sk_sp { return SkSVGG::Make(); }}, + { "path", []() -> sk_sp { return SkSVGPath::Make(); }}, + { "svg" , []() -> sk_sp { return SkSVGSVG::Make(); }}, +}; + +struct ConstructionContext { + ConstructionContext() : fParent(nullptr) { } + ConstructionContext(const ConstructionContext& other, const sk_sp& newParent) + : fParent(newParent.get()) { } + + const SkSVGNode* fParent; +}; + +void parse_node_attributes(const SkDOM& xmlDom, const SkDOM::Node* xmlNode, + const sk_sp& svgNode) { + const char* name, *value; + SkDOM::AttrIter attrIter(xmlDom, xmlNode); + while ((name = attrIter.next(&value))) { + const int attrIndex = SkStrSearch(&gAttributeParseInfo[0].fKey, + SkTo(SK_ARRAY_COUNT(gAttributeParseInfo)), + name, sizeof(gAttributeParseInfo[0])); + if (attrIndex < 0) { + SkDebugf("unhandled attribute: %s\n", name); + continue; + } + + SkASSERT(SkTo(attrIndex) < SK_ARRAY_COUNT(gAttributeParseInfo)); + const auto& attrInfo = gAttributeParseInfo[attrIndex].fValue; + if (!attrInfo.fSetter(svgNode, attrInfo.fAttr, value)) { + SkDebugf("could not parse attribute: '%s=\"%s\"'\n", name, value); + } + } +} + +sk_sp 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(SK_ARRAY_COUNT(gTagFactories)), + elem, sizeof(gTagFactories[0])); + if (tagIndex < 0) { + SkDebugf("unhandled element: <%s>\n", elem); + return nullptr; + } + + SkASSERT(SkTo(tagIndex) < SK_ARRAY_COUNT(gTagFactories)); + sk_sp node = gTagFactories[tagIndex].fValue(); + parse_node_attributes(dom, xmlNode, node); + + ConstructionContext localCtx(ctx, node); + for (auto* child = dom.getFirstChild(xmlNode, nullptr); child; + child = dom.getNextSibling(child)) { + sk_sp childNode = construct_svg_node(dom, localCtx, child); + if (childNode) { + node->appendChild(std::move(childNode)); + } + } + + return node; +} + +} // anonymous namespace + +SkSVGDOM::SkSVGDOM(const SkSize& containerSize) + : fContainerSize(containerSize) { +} + +sk_sp SkSVGDOM::MakeFromDOM(const SkDOM& xmlDom, const SkSize& containerSize) { + sk_sp dom = sk_make_sp(containerSize); + + ConstructionContext ctx; + dom->fRoot = construct_svg_node(xmlDom, ctx, xmlDom.getRootNode()); + + return dom; +} + +sk_sp SkSVGDOM::MakeFromStream(SkStream& svgStream, const SkSize& containerSize) { + SkDOM xmlDom; + if (!xmlDom.build(svgStream)) { + return nullptr; + } + + return MakeFromDOM(xmlDom, containerSize); +} + +void SkSVGDOM::render(SkCanvas* canvas) const { + if (fRoot) { + fRoot->render(canvas); + } +} + +void SkSVGDOM::setContainerSize(const SkSize& containerSize) { + // TODO: inval + fContainerSize = containerSize; +} diff --git a/experimental/svg/model/SkSVGDOM.h b/experimental/svg/model/SkSVGDOM.h new file mode 100644 index 0000000..468ac00 --- /dev/null +++ b/experimental/svg/model/SkSVGDOM.h @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGDOM_DEFINED +#define SkSVGDOM_DEFINED + +#include "SkRefCnt.h" +#include "SkSize.h" +#include "SkTemplates.h" + +class SkCanvas; +class SkDOM; +class SkStream; +class SkSVGNode; + +class SkSVGDOM : public SkRefCnt { +public: + SkSVGDOM(const SkSize& containerSize); + ~SkSVGDOM() = default; + + static sk_sp MakeFromDOM(const SkDOM&, const SkSize& containerSize); + static sk_sp MakeFromStream(SkStream&, const SkSize& containerSize); + + void setContainerSize(const SkSize&); + + void render(SkCanvas*) const; + +private: + SkSize fContainerSize; + sk_sp fRoot; + + typedef SkRefCnt INHERITED; +}; + +#endif // SkSVGDOM_DEFINED diff --git a/experimental/svg/model/SkSVGG.h b/experimental/svg/model/SkSVGG.h new file mode 100644 index 0000000..eaaf822 --- /dev/null +++ b/experimental/svg/model/SkSVGG.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGG_DEFINED +#define SkSVGG_DEFINED + +#include "SkSVGContainer.h" + +class SkSVGG : public SkSVGContainer { +public: + virtual ~SkSVGG() = default; + + static sk_sp Make() { return sk_sp(new SkSVGG()); } + +private: + SkSVGG() : INHERITED(SkSVGTag::g) { } + + typedef SkSVGContainer INHERITED; +}; + +#endif // SkSVGG_DEFINED diff --git a/experimental/svg/model/SkSVGNode.cpp b/experimental/svg/model/SkSVGNode.cpp new file mode 100644 index 0000000..2b89179 --- /dev/null +++ b/experimental/svg/model/SkSVGNode.cpp @@ -0,0 +1,60 @@ +/* + * 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 "SkMatrix.h" +#include "SkSVGNode.h" +#include "SkSVGRenderContext.h" +#include "SkSVGValue.h" +#include "SkTLazy.h" + +SkSVGNode::SkSVGNode(SkSVGTag t) : fTag(t) { } + +SkSVGNode::~SkSVGNode() { } + +void SkSVGNode::render(SkCanvas* canvas) const { + this->render(canvas, SkSVGRenderContext()); +} + +void SkSVGNode::render(SkCanvas* canvas, const SkSVGRenderContext& ctx) const { + SkTCopyOnFirstWrite localContext(ctx); + fPresentationAttributes.applyTo(localContext); + + SkAutoCanvasRestore acr(canvas, false); + const SkMatrix& m = this->onLocalMatrix(); + if (!m.isIdentity()) { + canvas->save(); + canvas->concat(m); + } + + this->onRender(canvas, *localContext); +} + +void SkSVGNode::setAttribute(SkSVGAttribute attr, const SkSVGValue& v) { + this->onSetAttribute(attr, v); +} + +void SkSVGNode::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) { + switch (attr) { + case SkSVGAttribute::fill: + if (const SkSVGColorValue* color = v.as()) { + fPresentationAttributes.setFill(*color); + } + break; + case SkSVGAttribute::stroke: + if (const SkSVGColorValue* color = v.as()) { + fPresentationAttributes.setStroke(*color); + } + break; + default: + break; + } +} + +const SkMatrix& SkSVGNode::onLocalMatrix() const { + return SkMatrix::I(); +} diff --git a/experimental/svg/model/SkSVGNode.h b/experimental/svg/model/SkSVGNode.h new file mode 100644 index 0000000..de18014 --- /dev/null +++ b/experimental/svg/model/SkSVGNode.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGNode_DEFINED +#define SkSVGNode_DEFINED + +#include "SkRefCnt.h" +#include "SkSVGAttribute.h" + +class SkCanvas; +class SkMatrix; +class SkSVGRenderContext; +class SkSVGValue; + +enum class SkSVGTag { + g, + path, + svg +}; + +class SkSVGNode : public SkRefCnt { +public: + virtual ~SkSVGNode(); + + SkSVGTag tag() const { return fTag; } + + virtual void appendChild(sk_sp) = 0; + + void render(SkCanvas*) const; + void render(SkCanvas*, const SkSVGRenderContext&) const; + + void setAttribute(SkSVGAttribute, const SkSVGValue&); + +protected: + SkSVGNode(SkSVGTag); + + virtual void onRender(SkCanvas*, const SkSVGRenderContext&) const = 0; + + virtual void onSetAttribute(SkSVGAttribute, const SkSVGValue&); + + virtual const SkMatrix& onLocalMatrix() const; + +private: + SkSVGTag fTag; + + // FIXME: this should be sparse + SkSVGPresentationAttributes fPresentationAttributes; + + typedef SkRefCnt INHERITED; +}; + +#endif // SkSVGNode_DEFINED diff --git a/experimental/svg/model/SkSVGPath.cpp b/experimental/svg/model/SkSVGPath.cpp new file mode 100644 index 0000000..ba5e3d6 --- /dev/null +++ b/experimental/svg/model/SkSVGPath.cpp @@ -0,0 +1,37 @@ +/* + * 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 "SkPaint.h" +#include "SkSVGPath.h" +#include "SkSVGRenderContext.h" +#include "SkSVGValue.h" + +SkSVGPath::SkSVGPath() : INHERITED(SkSVGTag::path) { } + +void SkSVGPath::doRender(SkCanvas* canvas, const SkPaint* paint) const { + if (paint) { + canvas->drawPath(fPath, *paint); + } +} + +void SkSVGPath::onRender(SkCanvas* canvas, const SkSVGRenderContext& ctx) const { + this->doRender(canvas, ctx.fillPaint()); + this->doRender(canvas, ctx.strokePaint()); +} + +void SkSVGPath::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) { + switch (attr) { + case SkSVGAttribute::d: + if (const auto* path = v.as()) { + this->setPath(*path); + } + break; + default: + this->INHERITED::onSetAttribute(attr, v); + } +} diff --git a/experimental/svg/model/SkSVGPath.h b/experimental/svg/model/SkSVGPath.h new file mode 100644 index 0000000..b650f67 --- /dev/null +++ b/experimental/svg/model/SkSVGPath.h @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGPath_DEFINED +#define SkSVGPath_DEFINED + +#include "SkPath.h" +#include "SkSVGTransformableNode.h" + +class SkSVGPath final : public SkSVGTransformableNode { +public: + virtual ~SkSVGPath() = default; + static sk_sp Make() { return sk_sp(new SkSVGPath()); } + + void appendChild(sk_sp) override { } + + void setPath(const SkPath& path) { fPath = path; } + +protected: + void onRender(SkCanvas*, const SkSVGRenderContext&) const override; + + void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override; + +private: + SkSVGPath(); + + void doRender(SkCanvas*, const SkPaint*) const; + + SkPath fPath; + + typedef SkSVGTransformableNode INHERITED; +}; + +#endif // SkSVGPath_DEFINED diff --git a/experimental/svg/model/SkSVGRenderContext.cpp b/experimental/svg/model/SkSVGRenderContext.cpp new file mode 100644 index 0000000..703606c --- /dev/null +++ b/experimental/svg/model/SkSVGRenderContext.cpp @@ -0,0 +1,52 @@ +/* + * 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 "SkSVGRenderContext.h" + +SkSVGRenderContext::SkSVGRenderContext() { } + +SkSVGRenderContext& SkSVGRenderContext::operator=(const SkSVGRenderContext& other) { + if (other.fFill.isValid()) { + fFill.set(*other.fFill.get()); + } else { + fFill.reset(); + } + + if (other.fStroke.isValid()) { + fStroke.set(*other.fStroke.get()); + } else { + fStroke.reset(); + } + + return *this; +} + +SkPaint& SkSVGRenderContext::ensureFill() { + if (!fFill.isValid()) { + fFill.init(); + fFill.get()->setStyle(SkPaint::kFill_Style); + fFill.get()->setAntiAlias(true); + } + return *fFill.get(); +} + +SkPaint& SkSVGRenderContext::ensureStroke() { + if (!fStroke.isValid()) { + fStroke.init(); + fStroke.get()->setStyle(SkPaint::kStroke_Style); + fStroke.get()->setAntiAlias(true); + } + return *fStroke.get(); +} + +void SkSVGRenderContext::setFillColor(SkColor color) { + this->ensureFill().setColor(color); +} + +void SkSVGRenderContext::setStrokeColor(SkColor color) { + this->ensureStroke().setColor(color); +} diff --git a/experimental/svg/model/SkSVGRenderContext.h b/experimental/svg/model/SkSVGRenderContext.h new file mode 100644 index 0000000..8ebaf12 --- /dev/null +++ b/experimental/svg/model/SkSVGRenderContext.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGRenderContext_DEFINED +#define SkSVGRenderContext_DEFINED + +#include "SkPaint.h" +#include "SkTLazy.h" + +class SkPaint; + +class SkSVGRenderContext { +public: + SkSVGRenderContext(); + SkSVGRenderContext(const SkSVGRenderContext&) = default; + SkSVGRenderContext& operator=(const SkSVGRenderContext&); + + const SkPaint* fillPaint() const { return fFill.getMaybeNull(); } + const SkPaint* strokePaint() const { return fStroke.getMaybeNull(); } + + void setFillColor(SkColor); + void setStrokeColor(SkColor); + +private: + SkPaint& ensureFill(); + SkPaint& ensureStroke(); + + SkTLazy fFill; + SkTLazy fStroke; +}; + +#endif // SkSVGRenderContext_DEFINED diff --git a/experimental/svg/model/SkSVGSVG.cpp b/experimental/svg/model/SkSVGSVG.cpp new file mode 100644 index 0000000..feae0d7 --- /dev/null +++ b/experimental/svg/model/SkSVGSVG.cpp @@ -0,0 +1,10 @@ +/* + * 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 "SkSVGSVG.h" + +SkSVGSVG::SkSVGSVG() : INHERITED(SkSVGTag::svg) { } diff --git a/experimental/svg/model/SkSVGSVG.h b/experimental/svg/model/SkSVGSVG.h new file mode 100644 index 0000000..8e69d14 --- /dev/null +++ b/experimental/svg/model/SkSVGSVG.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGSVG_DEFINED +#define SkSVGSVG_DEFINED + +#include "SkSVGContainer.h" + +class SkSVGSVG : public SkSVGContainer { +public: + virtual ~SkSVGSVG() = default; + + static sk_sp Make() { return sk_sp(new SkSVGSVG()); } + +private: + SkSVGSVG(); + + typedef SkSVGContainer INHERITED; +}; + +#endif // SkSVGSVG_DEFINED diff --git a/experimental/svg/model/SkSVGTransformableNode.cpp b/experimental/svg/model/SkSVGTransformableNode.cpp new file mode 100644 index 0000000..1af89d8 --- /dev/null +++ b/experimental/svg/model/SkSVGTransformableNode.cpp @@ -0,0 +1,26 @@ +/* + * 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 "SkSVGTransformableNode.h" +#include "SkSVGValue.h" + +SkSVGTransformableNode::SkSVGTransformableNode(SkSVGTag tag) + : INHERITED(tag) + , fMatrix(SkMatrix::I()) { } + +void SkSVGTransformableNode::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) { + switch (attr) { + case SkSVGAttribute::transform: + if (const auto* transform = v.as()) { + this->setTransform(*transform); + } + break; + default: + this->INHERITED::onSetAttribute(attr, v); + break; + } +} diff --git a/experimental/svg/model/SkSVGTransformableNode.h b/experimental/svg/model/SkSVGTransformableNode.h new file mode 100644 index 0000000..c345e85 --- /dev/null +++ b/experimental/svg/model/SkSVGTransformableNode.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGTransformableNode_DEFINED +#define SkSVGTransformableNode_DEFINED + +#include "SkMatrix.h" +#include "SkSVGNode.h" + +class SkSVGTransformableNode : public SkSVGNode { +public: + virtual ~SkSVGTransformableNode() = default; + + void setTransform(const SkMatrix& m) { fMatrix = m; } + +protected: + SkSVGTransformableNode(SkSVGTag); + + void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override; + + const SkMatrix& onLocalMatrix() const override { return fMatrix; } + +private: + // FIXME: should be sparse + SkMatrix fMatrix; + + typedef SkSVGNode INHERITED; +}; + +#endif // SkSVGTransformableNode_DEFINED diff --git a/experimental/svg/model/SkSVGValue.cpp b/experimental/svg/model/SkSVGValue.cpp new file mode 100644 index 0000000..674a200 --- /dev/null +++ b/experimental/svg/model/SkSVGValue.cpp @@ -0,0 +1,8 @@ +/* + * 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 "SkSVGValue.h" diff --git a/experimental/svg/model/SkSVGValue.h b/experimental/svg/model/SkSVGValue.h new file mode 100644 index 0000000..3d3cd6e --- /dev/null +++ b/experimental/svg/model/SkSVGValue.h @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGValue_DEFINED +#define SkSVGValue_DEFINED + +#include "SkColor.h" +#include "SkMatrix.h" +#include "SkPath.h" +#include "SkTypes.h" + +class SkSVGValue : public SkNoncopyable { +public: + enum class Type { + Color, + Path, + Transform, + }; + + Type type() const { return fType; } + + template + const T* as() const { + return fType == T::TYPE ? static_cast(this) : nullptr; + } + +protected: + SkSVGValue(Type t) : fType(t) { } + +private: + Type fType; +}; + +template +class SkSVGWrapperValue final : public SkSVGValue { +public: + static constexpr Type TYPE = ValueType; + + explicit SkSVGWrapperValue(const SkiaType& v) + : INHERITED(ValueType) + , fWrappedValue(v) { } + + operator const SkiaType&() const { return fWrappedValue; } + +private: + SkiaType fWrappedValue; + + using INHERITED = SkSVGValue; +}; + +using SkSVGColorValue = SkSVGWrapperValue; +using SkSVGPathValue = SkSVGWrapperValue; +using SkSVGTransformValue = SkSVGWrapperValue; + +#endif // SkSVGValue_DEFINED diff --git a/gyp/samples.gypi b/gyp/samples.gypi index 283d038..7e9c414 100644 --- a/gyp/samples.gypi +++ b/gyp/samples.gypi @@ -94,6 +94,7 @@ '../samplecode/SampleStringArt.cpp', '../samplecode/SampleStrokePath.cpp', '../samplecode/SampleSubpixelTranslate.cpp', + '../samplecode/SampleSVGFile.cpp', '../samplecode/SampleText.cpp', '../samplecode/SampleTextAlpha.cpp', '../samplecode/SampleTextBox.cpp', @@ -135,6 +136,7 @@ 'experimental.gyp:experimental', 'lua.gyp:lua', 'pdf.gyp:pdf', + 'svg.gyp:svgdom', 'views.gyp:views', 'xml.gyp:xml', ], diff --git a/gyp/svg.gyp b/gyp/svg.gyp index 8fc4b97..f5b43f8 100644 --- a/gyp/svg.gyp +++ b/gyp/svg.gyp @@ -31,5 +31,45 @@ ], }, }, + { + 'target_name': 'svgdom', + 'type': 'static_library', + 'standalone_static_library': 1, + 'dependencies': [ + 'skia_lib.gyp:skia_lib', + 'xml.gyp:xml', + ], + 'include_dirs': [ + '<(skia_include_path)/private', + '../experimental/svg/model', + ], + 'sources': [ + '../experimental/svg/model/SkSVGAttribute.h', + '../experimental/svg/model/SkSVGAttribute.cpp', + '../experimental/svg/model/SkSVGContainer.h', + '../experimental/svg/model/SkSVGContainer.cpp', + '../experimental/svg/model/SkSVGDOM.h', + '../experimental/svg/model/SkSVGDOM.cpp', + '../experimental/svg/model/SkSVGG.h', + '../experimental/svg/model/SkSVGNode.h', + '../experimental/svg/model/SkSVGNode.cpp', + '../experimental/svg/model/SkSVGPath.h', + '../experimental/svg/model/SkSVGPath.cpp', + '../experimental/svg/model/SkSVGRenderContext.h', + '../experimental/svg/model/SkSVGRenderContext.cpp', + '../experimental/svg/model/SkSVGSVG.h', + '../experimental/svg/model/SkSVGSVG.cpp', + '../experimental/svg/model/SkSVGTransformableNode.h', + '../experimental/svg/model/SkSVGTransformableNode.cpp', + '../experimental/svg/model/SkSVGValue.h', + '../experimental/svg/model/SkSVGValue.cpp', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '../experimental/svg/model', + ], + }, + }, + ], } diff --git a/include/xml/SkDOM.h b/include/xml/SkDOM.h index 10ca3e7..b6f611a 100644 --- a/include/xml/SkDOM.h +++ b/include/xml/SkDOM.h @@ -79,7 +79,7 @@ public: class AttrIter { public: - AttrIter(const class SkDOM&, const Node*); + AttrIter(const SkDOM&, const Node*); const char* next(const char** value); private: const Attr* fAttr; diff --git a/include/xml/SkXMLParser.h b/include/xml/SkXMLParser.h index 2974fb5..3f69013 100644 --- a/include/xml/SkXMLParser.h +++ b/include/xml/SkXMLParser.h @@ -73,10 +73,10 @@ protected: public: // public for ported implementation, not meant for clients to call - virtual bool startElement(const char elem[]); - virtual bool addAttribute(const char name[], const char value[]); - virtual bool endElement(const char elem[]); - virtual bool text(const char text[], int len); + bool startElement(const char elem[]); + bool addAttribute(const char name[], const char value[]); + bool endElement(const char elem[]); + bool text(const char text[], int len); void* fParser; protected: SkXMLParserError* fError; diff --git a/samplecode/SampleApp.cpp b/samplecode/SampleApp.cpp index 0747fcf..64d460f 100644 --- a/samplecode/SampleApp.cpp +++ b/samplecode/SampleApp.cpp @@ -80,6 +80,17 @@ public: } }; +extern SampleView* CreateSampleSVGFileView(const char filename[]); + +class SVGFileFactory : public SkViewFactory { + SkString fFilename; +public: + SVGFileFactory(const SkString& filename) : fFilename(filename) {} + SkView* operator() () const override { + return CreateSampleSVGFileView(fFilename.c_str()); + } +}; + #ifdef SAMPLE_PDF_FILE_VIEWER extern SampleView* CreateSamplePdfFileViewer(const char filename[]); @@ -678,6 +689,8 @@ DEFINE_int32(msaa, 0, "Request multisampling with this count."); DEFINE_bool(deepColor, false, "Request deep color (10-bit/channel or more) display buffer."); DEFINE_string(pictureDir, "", "Read pictures from here."); DEFINE_string(picture, "", "Path to single picture."); +DEFINE_string(svg, "", "Path to single SVG file."); +DEFINE_string(svgDir, "", "Read SVGs from here."); DEFINE_string(sequence, "", "Path to file containing the desired samples/gms to show."); DEFINE_bool(sort, false, "Sort samples by title."); DEFINE_bool(list, false, "List samples?"); @@ -711,6 +724,19 @@ SampleWindow::SampleWindow(void* hwnd, int argc, char** argv, DeviceManager* dev fCurrIndex = fSamples.count(); *fSamples.append() = new PictFileFactory(path); } + if (!FLAGS_svg.isEmpty()) { + SkString path(FLAGS_svg[0]); + fCurrIndex = fSamples.count(); + *fSamples.append() = new SVGFileFactory(path); + } + if (!FLAGS_svgDir.isEmpty()) { + SkOSFile::Iter iter(FLAGS_svgDir[0], "svg"); + SkString filename; + while (iter.next(&filename)) { + *fSamples.append() = new SVGFileFactory( + SkOSPath::Join(FLAGS_svgDir[0], filename.c_str())); + } + } #ifdef SAMPLE_PDF_FILE_VIEWER if (!FLAGS_pdfPath.isEmpty()) { SkOSFile::Iter iter(FLAGS_pdfPath[0], "pdf"); diff --git a/samplecode/SampleSVGFile.cpp b/samplecode/SampleSVGFile.cpp new file mode 100644 index 0000000..01a1958 --- /dev/null +++ b/samplecode/SampleSVGFile.cpp @@ -0,0 +1,63 @@ +/* + * 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 "SampleCode.h" +#include "SkCanvas.h" +#include "SkDOM.h" +#include "SkStream.h" +#include "SkSVGDOM.h" +#include "SkView.h" + +namespace { + +class SVGFileView : public SampleView { +public: + SVGFileView(const char path[]) { + SkFILEStream svgStream(path); + if (!svgStream.isValid()) { + SkDebugf("file not found: \"path\"\n", path); + return; + } + + SkDOM xmlDom; + if (!xmlDom.build(svgStream)) { + SkDebugf("XML parsing failed: \"path\"\n", path); + return; + } + + fDom = SkSVGDOM::MakeFromDOM(xmlDom, SkSize::Make(this->width(), this->height())); + } + + virtual ~SVGFileView() = default; + +protected: + void onDrawContent(SkCanvas* canvas) override { + if (fDom) { + fDom->render(canvas); + } + } + + void onSizeChange() override { + if (fDom) { + fDom->setContainerSize(SkSize::Make(this->width(), this->height())); + } + + this->INHERITED::onSizeChange(); + } + +private: + sk_sp fDom; + + typedef SampleView INHERITED; +}; + +} // anonymous namespace + +SampleView* CreateSampleSVGFileView(const char filename[]); +SampleView* CreateSampleSVGFileView(const char filename[]) { + return new SVGFileView(filename); +} -- 2.7.4