[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/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));
 }