[svg] Refactor text rendering context plumbing

Instead of relying on RenderContext to pass text rendering options
downstack, introduce a dedicated virtual (onRenderText) and pass options
explicitly.

Root text nodes bridge from onRender() -> onRenderText().

This removes some complexity from RenderContext and incidentally fixes
xml:space = preserve (the value was being dropped during local ctx
copying).

Bug: skia:10840
Change-Id: Ic5fd9e0f9382f52f65108521574fcb2a422b97aa
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/344559
Reviewed-by: Tyler Denniston <tdenniston@google.com>
Commit-Queue: Florin Malita <fmalita@google.com>
diff --git a/modules/svg/include/SkSVGRenderContext.h b/modules/svg/include/SkSVGRenderContext.h
index d459df8..ef54b8e 100644
--- a/modules/svg/include/SkSVGRenderContext.h
+++ b/modules/svg/include/SkSVGRenderContext.h
@@ -20,7 +20,6 @@
 
 class SkCanvas;
 class SkSVGLength;
-class SkSVGTextContext;
 
 class SkSVGLengthContext {
 public:
@@ -62,18 +61,16 @@
 public:
     SkSVGRenderContext(SkCanvas*, const sk_sp<SkFontMgr>&, const SkSVGIDMapper&,
                        const SkSVGLengthContext&, const SkSVGPresentationContext&,
-                       SkSVGTextContext*, const SkSVGNode*);
+                       const SkSVGNode*);
     SkSVGRenderContext(const SkSVGRenderContext&);
     SkSVGRenderContext(const SkSVGRenderContext&, SkCanvas*);
     SkSVGRenderContext(const SkSVGRenderContext&, const SkSVGNode*);
-    SkSVGRenderContext(const SkSVGRenderContext&, SkSVGTextContext&);
     ~SkSVGRenderContext();
 
     const SkSVGLengthContext& lengthContext() const { return *fLengthContext; }
     SkSVGLengthContext* writableLengthContext() { return fLengthContext.writable(); }
 
     const SkSVGPresentationContext& presentationContext() const { return *fPresentationContext; }
-    SkSVGTextContext* textContext() const { return fTextContext; }
 
     SkCanvas* canvas() const { return fCanvas; }
     void saveOnce();
@@ -134,9 +131,6 @@
         return fFontMgr ? fFontMgr : SkFontMgr::RefDefault();
     }
 
-    SkSVGXmlSpace getXmlSpace() const { return fXmlSpace; }
-    void setXmlSpace(SkSVGXmlSpace xs) { fXmlSpace = xs; }
-
 private:
     // Stack-only
     void* operator new(size_t)                               = delete;
@@ -152,7 +146,6 @@
     const SkSVGIDMapper&                          fIDMapper;
     SkTCopyOnFirstWrite<SkSVGLengthContext>       fLengthContext;
     SkTCopyOnFirstWrite<SkSVGPresentationContext> fPresentationContext;
-    SkSVGTextContext*                             fTextContext;
     SkCanvas*                                     fCanvas;
     // The save count on 'fCanvas' at construction time.
     // A restoreToCount() will be issued on destruction.
@@ -161,8 +154,6 @@
     // clipPath, if present for the current context (not inherited).
     SkTLazy<SkPath>                               fClipPath;
 
-    SkSVGXmlSpace                                 fXmlSpace = SkSVGXmlSpace::kDefault;
-
     const SkSVGNode* fNode;
 };
 
diff --git a/modules/svg/include/SkSVGText.h b/modules/svg/include/SkSVGText.h
index 8fb4721..0710ab8 100644
--- a/modules/svg/include/SkSVGText.h
+++ b/modules/svg/include/SkSVGText.h
@@ -8,11 +8,32 @@
 #ifndef SkSVGText_DEFINED
 #define SkSVGText_DEFINED
 
-#include "modules/svg/include/SkSVGContainer.h"
+#include <vector>
+
+#include "modules/svg/include/SkSVGTransformableNode.h"
 #include "modules/svg/include/SkSVGTypes.h"
 
+class SkSVGTextContext;
+
+// Base class for text-rendering nodes.
+class SkSVGTextFragment : public SkSVGTransformableNode {
+public:
+    void renderText(const SkSVGRenderContext&, SkSVGTextContext*, SkSVGXmlSpace) const;
+
+protected:
+    explicit SkSVGTextFragment(SkSVGTag t) : INHERITED(t) {}
+
+    virtual void onRenderText(const SkSVGRenderContext&, SkSVGTextContext*,
+                              SkSVGXmlSpace) const = 0;
+
+private:
+    SkPath onAsPath(const SkSVGRenderContext&) const final;
+
+    using INHERITED = SkSVGTransformableNode;
+};
+
 // Base class for nestable text containers (<text>, <tspan>, etc).
-class SkSVGTextContainer : public SkSVGContainer {
+class SkSVGTextContainer : public SkSVGTextFragment {
 public:
     // TODO: these should be arrays
     SVG_ATTR(X, SkSVGLength, SkSVGLength(0))
@@ -25,11 +46,14 @@
 
 private:
     void appendChild(sk_sp<SkSVGNode>) final;
-    bool onPrepareToRender(SkSVGRenderContext*) const final;
+    void onRender(const SkSVGRenderContext&) const final;
+    void onRenderText(const SkSVGRenderContext&, SkSVGTextContext*, SkSVGXmlSpace) const final;
 
     bool parseAndSetAttribute(const char*, const char*) override;
 
-    using INHERITED = SkSVGContainer;
+    std::vector<sk_sp<SkSVGTextFragment>> fChildren;
+
+    using INHERITED = SkSVGTextFragment;
 };
 
 class SkSVGText final : public SkSVGTextContainer {
@@ -39,8 +63,6 @@
 private:
     SkSVGText() : INHERITED(SkSVGTag::kText) {}
 
-    void onRender(const SkSVGRenderContext&) const override;
-
     using INHERITED = SkSVGTextContainer;
 };
 
@@ -54,7 +76,7 @@
     using INHERITED = SkSVGTextContainer;
 };
 
-class SkSVGTextLiteral final : public SkSVGNode {
+class SkSVGTextLiteral final : public SkSVGTextFragment {
 public:
     static sk_sp<SkSVGTextLiteral> Make() {
         return sk_sp<SkSVGTextLiteral>(new SkSVGTextLiteral());
@@ -65,12 +87,12 @@
 private:
     SkSVGTextLiteral() : INHERITED(SkSVGTag::kTextLiteral) {}
 
-    void onRender(const SkSVGRenderContext&) const override;
-    SkPath onAsPath(const SkSVGRenderContext&) const override;
+    void onRender(const SkSVGRenderContext&) const override {}
+    void onRenderText(const SkSVGRenderContext&, SkSVGTextContext*, SkSVGXmlSpace) const override;
 
     void appendChild(sk_sp<SkSVGNode>) override {}
 
-    using INHERITED = SkSVGNode;
+    using INHERITED = SkSVGTextFragment;
 };
 
 #endif  // SkSVGText_DEFINED
diff --git a/modules/svg/src/SkSVGDOM.cpp b/modules/svg/src/SkSVGDOM.cpp
index a9c0346..54ee152 100644
--- a/modules/svg/src/SkSVGDOM.cpp
+++ b/modules/svg/src/SkSVGDOM.cpp
@@ -408,8 +408,7 @@
     if (fRoot) {
         SkSVGLengthContext       lctx(fContainerSize);
         SkSVGPresentationContext pctx;
-        fRoot->render(SkSVGRenderContext(canvas, fFontMgr, fIDMapper,
-                                         lctx, pctx, nullptr, nullptr));
+        fRoot->render(SkSVGRenderContext(canvas, fFontMgr, fIDMapper, lctx, pctx, nullptr));
     }
 }
 
diff --git a/modules/svg/src/SkSVGRenderContext.cpp b/modules/svg/src/SkSVGRenderContext.cpp
index d1b33a7..63c2c04 100644
--- a/modules/svg/src/SkSVGRenderContext.cpp
+++ b/modules/svg/src/SkSVGRenderContext.cpp
@@ -312,7 +312,7 @@
     SkCanvas fakeCanvas(0, 0);
     SkSVGRenderContext fake(&fakeCanvas, nullptr, SkSVGIDMapper(),
                             SkSVGLengthContext(SkSize::Make(0, 0)),
-                            *this, nullptr, nullptr);
+                            *this, nullptr);
 
     commitToPaint<SkSVGAttribute::kFill>(fInherited, fake, this);
     commitToPaint<SkSVGAttribute::kFillOpacity>(fInherited, fake, this);
@@ -329,13 +329,11 @@
                                        const SkSVGIDMapper& mapper,
                                        const SkSVGLengthContext& lctx,
                                        const SkSVGPresentationContext& pctx,
-                                       SkSVGTextContext* tctx,
                                        const SkSVGNode* node)
     : fFontMgr(fmgr)
     , fIDMapper(mapper)
     , fLengthContext(lctx)
     , fPresentationContext(pctx)
-    , fTextContext(tctx)
     , fCanvas(canvas)
     , fCanvasSaveCount(canvas->getSaveCount())
     , fNode(node) {}
@@ -346,7 +344,6 @@
                          other.fIDMapper,
                          *other.fLengthContext,
                          *other.fPresentationContext,
-                         other.fTextContext,
                          other.fNode) {}
 
 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, SkCanvas* canvas)
@@ -355,7 +352,6 @@
                          other.fIDMapper,
                          *other.fLengthContext,
                          *other.fPresentationContext,
-                         other.fTextContext,
                          other.fNode) {}
 
 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, const SkSVGNode* node)
@@ -364,18 +360,8 @@
                          other.fIDMapper,
                          *other.fLengthContext,
                          *other.fPresentationContext,
-                         other.fTextContext,
                          node) {}
 
-SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, SkSVGTextContext& tctx)
-    : SkSVGRenderContext(other.fCanvas,
-                         other.fFontMgr,
-                         other.fIDMapper,
-                         *other.fLengthContext,
-                         *other.fPresentationContext,
-                         &tctx,
-                         other.fNode) {}
-
 SkSVGRenderContext::~SkSVGRenderContext() {
     fCanvas->restoreToCount(fCanvasSaveCount);
 }
diff --git a/modules/svg/src/SkSVGText.cpp b/modules/svg/src/SkSVGText.cpp
index a6ba964..4e2d91b 100644
--- a/modules/svg/src/SkSVGText.cpp
+++ b/modules/svg/src/SkSVGText.cpp
@@ -118,7 +118,7 @@
     {}
 
     // Queues codepoints for rendering.
-    void appendFragment(const SkString& txt, const SkSVGRenderContext& ctx) {
+    void appendFragment(const SkString& txt, const SkSVGRenderContext& ctx, SkSVGXmlSpace xs) {
         // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
         // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S
         auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar {
@@ -151,8 +151,6 @@
             return ch;
         };
 
-        const auto xmlSpace = ctx.getXmlSpace();
-
         SkSTArray<128, char, true> filtered;
         filtered.reserve_back(SkToInt(txt.size()));
 
@@ -161,7 +159,7 @@
 
         while (ch_ptr < ch_end) {
             auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end);
-            ch = (xmlSpace == SkSVGXmlSpace::kDefault)
+            ch = (xs == SkSVGXmlSpace::kDefault)
                     ? filterWSDefault(ch)
                     : filterWSPreserve(ch);
 
@@ -275,22 +273,40 @@
     bool                            fPrevCharSpace = true; // WS filter state
 };
 
+void SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
+                                   SkSVGXmlSpace xs) const {
+    SkSVGRenderContext localContext(ctx, this);
+
+    if (this->onPrepareToRender(&localContext)) {
+        this->onRenderText(localContext, tctx, xs);
+    }
+}
+
+SkPath SkSVGTextFragment::onAsPath(const SkSVGRenderContext&) const {
+    // TODO
+    return SkPath();
+}
+
 void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
     // Only allow text nodes.
     switch (child->tag()) {
     case SkSVGTag::kText:
     case SkSVGTag::kTextLiteral:
     case SkSVGTag::kTSpan:
-        this->INHERITED::appendChild(child);
+        fChildren.push_back(
+            sk_sp<SkSVGTextFragment>(static_cast<SkSVGTextFragment*>(child.release())));
         break;
     default:
         break;
     }
 }
 
-bool SkSVGTextContainer::onPrepareToRender(SkSVGRenderContext* ctx) const {
-    ctx->setXmlSpace(this->getXmlSpace());
-    return this->INHERITED::onPrepareToRender(ctx);
+void SkSVGTextContainer::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
+                                      SkSVGXmlSpace) const {
+    for (const auto& frag : fChildren) {
+        // Containers always override xml:space with the local value.
+        frag->renderText(ctx, tctx, this->getXmlSpace());
+    }
 }
 
 // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
@@ -311,26 +327,18 @@
            this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
 }
 
-void SkSVGText::onRender(const SkSVGRenderContext& ctx) const {
-    // <text> establishes a new text layout context.
+void SkSVGTextContainer::onRender(const SkSVGRenderContext& ctx) const {
+    // Root text nodes establish a new text layout context.
     SkSVGTextContext tctx(*this, ctx);
 
-    SkSVGRenderContext local_ctx(ctx, tctx);
-    this->INHERITED::onRender(local_ctx);
+    this->onRenderText(ctx, &tctx, this->getXmlSpace());
 
     tctx.flushChunk(ctx);
 }
 
-void SkSVGTextLiteral::onRender(const SkSVGRenderContext& ctx) const {
-    auto* tctx = ctx.textContext();
-    if (!tctx) {
-        return;
-    }
+void SkSVGTextLiteral::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
+                                    SkSVGXmlSpace xs) const {
+    SkASSERT(tctx);
 
-    tctx->appendFragment(this->getText(), ctx);
-}
-
-SkPath SkSVGTextLiteral::onAsPath(const SkSVGRenderContext&) const {
-    // TODO
-    return SkPath();
+    tctx->appendFragment(this->getText(), ctx, xs);
 }