[svg] Text rotate support

Implement support for text 'rotate' attribute:
https://www.w3.org/TR/SVG11/text.html#TSpanElementRotateAttribute.

Unlike other character-positioning attributes (x/y/dx/dy), rotate

  - is not cumulative
  - only applies to its respective node scope (does not affect other
    fragments in the current chunk)
  - has different padding semantics: if there are fewer rotate
    values than characters, the remaining characters use the last
    specified value from the closest ancestor

To the last point, we now have to discriminate three states:

  - unspecified (default -> 0)
  - explicit value for the given character index
  - implicit value (last value in the closest ancestor)

Local implicit values override implicit ancestor values -- but not
explicit ancestor values.

High level changes:

  - plumb 'rotate' attribute
  - expand per-character position info (ShapeBuffer) to include rotation
  - expand per-glyph position info (RunRec) to include rotation
  - expand PosAttrs to include rotation and add specific inheritance
    rules (see above)
  - pass computed rotation values to RSX blob buffers

Bug: skia:10840
Change-Id: Ia19ec5e8bb6fea06d49a9bd72ace575c2ffd100e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/348877
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
diff --git a/modules/svg/include/SkSVGAttributeParser.h b/modules/svg/include/SkSVGAttributeParser.h
index 41f772f..ecaab8a 100644
--- a/modules/svg/include/SkSVGAttributeParser.h
+++ b/modules/svg/include/SkSVGAttributeParser.h
@@ -8,6 +8,8 @@
 #ifndef SkSVGAttributeParser_DEFINED
 #define SkSVGAttributeParser_DEFINED
 
+#include <vector>
+
 #include "include/private/SkNoncopyable.h"
 #include "modules/svg/include/SkSVGTypes.h"
 #include "src/core/SkTLazy.h"
@@ -108,6 +110,9 @@
     template <typename Func, typename T>
     bool parseParenthesized(const char* prefix, Func, T* result);
 
+    template <typename T>
+    bool parseList(std::vector<T>*);
+
     template <typename T, typename TArray>
     bool parseEnumMap(const TArray& arr, T* result) {
         for (size_t i = 0; i < SK_ARRAY_COUNT(arr); ++i) {
diff --git a/modules/svg/include/SkSVGText.h b/modules/svg/include/SkSVGText.h
index 1e2dd2a..4fbdc1c 100644
--- a/modules/svg/include/SkSVGText.h
+++ b/modules/svg/include/SkSVGText.h
@@ -39,6 +39,7 @@
     SVG_ATTR(Y, std::vector<SkSVGLength>, {})
     SVG_ATTR(Dx, std::vector<SkSVGLength>, {})
     SVG_ATTR(Dy, std::vector<SkSVGLength>, {})
+    SVG_ATTR(Rotate, std::vector<SkSVGNumberType>, {})
 
     SVG_ATTR(XmlSpace, SkSVGXmlSpace, SkSVGXmlSpace::kDefault)
 
diff --git a/modules/svg/src/SkSVGAttributeParser.cpp b/modules/svg/src/SkSVGAttributeParser.cpp
index 2a408e8..1f8cb66 100644
--- a/modules/svg/src/SkSVGAttributeParser.cpp
+++ b/modules/svg/src/SkSVGAttributeParser.cpp
@@ -5,8 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include <vector>
-
 #include "include/private/SkTPin.h"
 #include "include/utils/SkParse.h"
 #include "modules/svg/include/SkSVGAttributeParser.h"
@@ -920,20 +918,30 @@
 }
 
 // https://www.w3.org/TR/SVG11/types.html#DataTypeCoordinates
-template <>
-bool SkSVGAttributeParser::parse(std::vector<SkSVGLength>* lengths) {
-    SkASSERT(lengths->empty());
+template <typename T>
+bool SkSVGAttributeParser::parseList(std::vector<T>* vals) {
+    SkASSERT(vals->empty());
 
-    SkSVGLength length;
+    T v;
     for (;;) {
-        if (!this->parse(&length)) {
+        if (!this->parse(&v)) {
             break;
         }
 
-        lengths->push_back(length);
+        vals->push_back(v);
 
         this->parseCommaWspToken();
     }
 
-    return !lengths->empty() && this->parseEOSToken();
+    return !vals->empty() && this->parseEOSToken();
+}
+
+template <>
+bool SkSVGAttributeParser::parse(std::vector<SkSVGLength>* lengths) {
+    return this->parseList(lengths);
+}
+
+template <>
+bool SkSVGAttributeParser::parse(std::vector<SkSVGNumberType>* numbers) {
+    return this->parseList(numbers);
 }
diff --git a/modules/svg/src/SkSVGText.cpp b/modules/svg/src/SkSVGText.cpp
index f96ba3f..79ac161 100644
--- a/modules/svg/src/SkSVGText.cpp
+++ b/modules/svg/src/SkSVGText.cpp
@@ -121,6 +121,7 @@
     , fY(ResolveLengths(lctx, txt.getY(), SkSVGLengthContext::LengthType::kVertical))
     , fDx(ResolveLengths(lctx, txt.getDx(), SkSVGLengthContext::LengthType::kHorizontal))
     , fDy(ResolveLengths(lctx, txt.getDy(), SkSVGLengthContext::LengthType::kVertical))
+    , fRotate(txt.getRotate())
 {
     fTextContext->fPosResolver = this;
 }
@@ -144,7 +145,8 @@
         const auto hasAllLocal = localCharIndex < fX.size() &&
                                  localCharIndex < fY.size() &&
                                  localCharIndex < fDx.size() &&
-                                 localCharIndex < fDy.size();
+                                 localCharIndex < fDy.size() &&
+                                 localCharIndex < fRotate.size();
         if (!hasAllLocal && fParent) {
             attrs = fParent->resolve(charIndex);
         }
@@ -162,6 +164,32 @@
             attrs[PosAttrs::kDy] = fDy[localCharIndex];
         }
 
+        // Rotation semantics are interestingly different [1]:
+        //
+        //   - values are not cumulative
+        //   - if explicit values are present at any level in the ancestor chain, those take
+        //     precedence (closest ancestor)
+        //   - last specified value applies to all remaining chars (closest ancestor)
+        //   - these rules apply at node scope (not chunk scope)
+        //
+        // This means we need to discriminate between explicit rotation (rotate value provided for
+        // current char) and implicit rotation (ancestor has some values - but not for the requested
+        // char - we use the last specified value).
+        //
+        // [1] https://www.w3.org/TR/SVG11/text.html#TSpanElementRotateAttribute
+        if (!fRotate.empty()) {
+            if (localCharIndex < fRotate.size()) {
+                // Explicit rotation value overrides anything in the ancestor chain.
+                attrs[PosAttrs::kRotate] = fRotate[localCharIndex];
+                attrs.setImplicitRotate(false);
+            } else if (!attrs.has(PosAttrs::kRotate) || attrs.isImplicitRotate()){
+                // Local implicit rotation (last specified value) overrides ancestor implicit
+                // rotation.
+                attrs[PosAttrs::kRotate] = fRotate.back();
+                attrs.setImplicitRotate(true);
+            }
+        }
+
         if (!attrs.hasAny()) {
             // Once we stop producing explicit position data, there is no reason to
             // continue trying for higher indices.  We can suppress future lookups.
@@ -172,10 +200,10 @@
     return attrs;
 }
 
-void SkSVGTextContext::ShapeBuffer::append(SkUnichar ch, SkVector pos) {
+void SkSVGTextContext::ShapeBuffer::append(SkUnichar ch, PositionAdjustment pos) {
     // relative pos adjustments are cumulative
     if (!fUtf8PosAdjust.empty()) {
-        pos += fUtf8PosAdjust.back();
+        pos.offset += fUtf8PosAdjust.back().offset;
     }
 
     char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence];
@@ -274,8 +302,11 @@
         }
 
         fShapeBuffer.append(ch, {
-            pos.has(PosAttrs::kDx) ? pos[PosAttrs::kDx] : 0,
-            pos.has(PosAttrs::kDy) ? pos[PosAttrs::kDy] : 0,
+            {
+                pos.has(PosAttrs::kDx) ? pos[PosAttrs::kDx] : 0,
+                pos.has(PosAttrs::kDy) ? pos[PosAttrs::kDy] : 0,
+            },
+            pos.has(PosAttrs::kRotate) ? pos[PosAttrs::kRotate] : 0,
         });
 
         fPrevCharSpace = (ch == ' ');
@@ -298,9 +329,8 @@
         std::copy(run.glyphs.get(), run.glyphs.get() + run.glyphCount, buf.glyphs);
         for (size_t i = 0; i < run.glyphCount; ++i) {
             const auto& pos = run.glyphPos[i];
-            const auto rotation = 0.0f; // TODO
-            buf.xforms()[i] = SkRSXform::MakeFromRadians(/*scale=*/1,
-                                                         rotation,
+            buf.xforms()[i] = SkRSXform::MakeFromRadians(/*scale=*/ 1,
+                                                         SkDegreesToRadians(run.glyphRot[i]),
                                                          pos.fX, pos.fY, 0, 0);
         }
 
@@ -331,6 +361,7 @@
         fCurrentStroke ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr,
         std::make_unique<SkGlyphID[]>(ri.glyphCount),
         std::make_unique<SkPoint[]  >(ri.glyphCount),
+        std::make_unique<float[]    >(ri.glyphCount),
         ri.glyphCount,
         ri.fAdvance,
     });
@@ -348,15 +379,20 @@
 }
 
 void SkSVGTextContext::commitRunBuffer(const RunInfo& ri) {
+    const auto& current_run = fRuns.back();
+
     // apply position adjustments
     for (size_t i = 0; i < ri.glyphCount; ++i) {
         const auto utf8_index = fShapeClusterBuffer[i];
-        fRuns.back().glyphPos[i] += fShapeBuffer.fUtf8PosAdjust[SkToInt(utf8_index)];
+        const auto& pos = fShapeBuffer.fUtf8PosAdjust[SkToInt(utf8_index)];
+
+        current_run.glyphPos[i] += pos.offset;
+        current_run.glyphRot[i]  = pos.rotation;
     }
 
     // Position adjustments are cumulative - we only need to advance the current chunk
     // with the last value.
-    fChunkAdvance += ri.fAdvance + fShapeBuffer.fUtf8PosAdjust.back();
+    fChunkAdvance += ri.fAdvance + fShapeBuffer.fUtf8PosAdjust.back().offset;
 }
 
 void SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
@@ -414,6 +450,9 @@
            this->setY(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("y", name, value)) ||
            this->setDx(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dx", name, value)) ||
            this->setDy(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dy", name, value)) ||
+           this->setRotate(SkSVGAttributeParser::parse<std::vector<SkSVGNumberType>>("rotate",
+                                                                                     name,
+                                                                                     value)) ||
            this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
 }
 
diff --git a/modules/svg/src/SkSVGTextPriv.h b/modules/svg/src/SkSVGTextPriv.h
index 4842ccc..2238b94 100644
--- a/modules/svg/src/SkSVGTextPriv.h
+++ b/modules/svg/src/SkSVGTextPriv.h
@@ -31,10 +31,11 @@
     public:
         // TODO: rotate
         enum Attr : size_t {
-            kX  = 0,
-            kY  = 1,
-            kDx = 2,
-            kDy = 3,
+            kX      = 0,
+            kY      = 1,
+            kDx     = 2,
+            kDy     = 3,
+            kRotate = 4,
         };
 
         float  operator[](Attr a) const { return fStorage[a]; }
@@ -42,13 +43,21 @@
 
         bool has(Attr a) const { return fStorage[a] != kNone; }
         bool hasAny()    const {
-            return this->has(kX) || this->has(kY) || this->has(kDx) || this->has(kDy);
+            return this->has(kX)
+                || this->has(kY)
+                || this->has(kDx)
+                || this->has(kDy)
+                || this->has(kRotate);
         }
 
+        void setImplicitRotate(bool imp) { fImplicitRotate = imp; }
+        bool isImplicitRotate() const { return fImplicitRotate; }
+
     private:
         static constexpr auto kNone = std::numeric_limits<float>::infinity();
 
-        float fStorage[4] = { kNone, kNone, kNone, kNone };
+        float fStorage[5]     = { kNone, kNone, kNone, kNone, kNone };
+        bool  fImplicitRotate = false;
     };
 
     // Helper for cascading position attribute resolution (x, y, dx, dy, rotate) [1]:
@@ -71,13 +80,14 @@
         PosAttrs resolve(size_t charIndex) const;
 
     private:
-        SkSVGTextContext*        fTextContext;
-        const ScopedPosResolver* fParent;          // parent resolver (fallback)
-        const size_t             fCharIndexOffset; // start index for the current resolver
-        const std::vector<float> fX,
-                                 fY,
-                                 fDx,
-                                 fDy;
+        SkSVGTextContext*         fTextContext;
+        const ScopedPosResolver*  fParent;          // parent resolver (fallback)
+        const size_t              fCharIndexOffset; // start index for the current resolver
+        const std::vector<float>  fX,
+                                  fY,
+                                  fDx,
+                                  fDy;
+        const std::vector<float>& fRotate;
 
         // cache for the last known index with explicit positioning
         mutable size_t           fLastPosIndex = std::numeric_limits<size_t>::max();
@@ -93,9 +103,15 @@
     void flushChunk(const SkSVGRenderContext& ctx);
 
 private:
+    struct PositionAdjustment {
+        SkVector offset;
+        float    rotation;
+    };
+
     struct ShapeBuffer {
-        SkSTArray<128, char    , true> fUtf8;
-        SkSTArray<128, SkVector, true> fUtf8PosAdjust; // per-utf8-char cumulative pos adjustments
+        SkSTArray<128, char              , true> fUtf8;
+        // per-utf8-char cumulative pos adjustments
+        SkSTArray<128, PositionAdjustment, true> fUtf8PosAdjust;
 
         void reserve(size_t size) {
             fUtf8.reserve_back(SkToInt(size));
@@ -107,7 +123,7 @@
             fUtf8PosAdjust.reset();
         }
 
-        void append(SkUnichar, SkVector);
+        void append(SkUnichar, PositionAdjustment);
     };
 
     struct RunRec {
@@ -116,6 +132,7 @@
                                      strokePaint;
         std::unique_ptr<SkGlyphID[]> glyphs;
         std::unique_ptr<SkPoint[]>   glyphPos;
+        std::unique_ptr<float[]>     glyphRot;
         size_t                       glyphCount;
         SkVector                     advance;
     };