[svg] Convert text-anchor to a presentation attribute

https://www.w3.org/TR/SVG11/text.html#TextAnchorProperty

Bug: skia:10840
Change-Id: Iff647b62243c42150e873f06215401b5e33705fd
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/330125
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
diff --git a/modules/svg/include/SkSVGAttribute.h b/modules/svg/include/SkSVGAttribute.h
index 22a2262..44342f8 100644
--- a/modules/svg/include/SkSVGAttribute.h
+++ b/modules/svg/include/SkSVGAttribute.h
@@ -95,6 +95,7 @@
     SkTLazy<SkSVGFontStyle>  fFontStyle;
     SkTLazy<SkSVGFontSize>   fFontSize;
     SkTLazy<SkSVGFontWeight> fFontWeight;
+    SkTLazy<SkSVGTextAnchor> fTextAnchor;
 
     // TODO(tdenniston): add SkSVGStopColor
 
diff --git a/modules/svg/include/SkSVGAttributeParser.h b/modules/svg/include/SkSVGAttributeParser.h
index 2e5f11c..57a73c8 100644
--- a/modules/svg/include/SkSVGAttributeParser.h
+++ b/modules/svg/include/SkSVGAttributeParser.h
@@ -38,6 +38,7 @@
     bool parseFontSize(SkSVGFontSize*);
     bool parseFontStyle(SkSVGFontStyle*);
     bool parseFontWeight(SkSVGFontWeight*);
+    bool parseTextAnchor(SkSVGTextAnchor*);
 
 private:
     // Stack-only
diff --git a/modules/svg/include/SkSVGNode.h b/modules/svg/include/SkSVGNode.h
index 9017f88..3e18cf4 100644
--- a/modules/svg/include/SkSVGNode.h
+++ b/modules/svg/include/SkSVGNode.h
@@ -99,6 +99,7 @@
     SVG_PRES_ATTR(FontStyle , SkSVGFontStyle , true)
     SVG_PRES_ATTR(FontSize  , SkSVGFontSize  , true)
     SVG_PRES_ATTR(FontWeight, SkSVGFontWeight, true)
+    SVG_PRES_ATTR(TextAnchor, SkSVGTextAnchor, true)
 
 protected:
     SkSVGNode(SkSVGTag);
diff --git a/modules/svg/include/SkSVGText.h b/modules/svg/include/SkSVGText.h
index 55716cd..ab186c9 100644
--- a/modules/svg/include/SkSVGText.h
+++ b/modules/svg/include/SkSVGText.h
@@ -8,7 +8,6 @@
 #ifndef SkSVGText_DEFINED
 #define SkSVGText_DEFINED
 
-#include "include/core/SkFont.h"
 #include "include/utils/SkTextUtils.h"
 #include "modules/svg/include/SkSVGTransformableNode.h"
 #include "modules/svg/include/SkSVGTypes.h"
@@ -21,10 +20,9 @@
   static sk_sp<SkSVGText> Make() {
     return sk_sp<SkSVGText>(new SkSVGText()); }
 
-  void setX(const SkSVGLength&);
-  void setY(const SkSVGLength&);
-  void setText(const SkSVGStringType&);
-  void setTextAnchor(const SkSVGStringType&);
+  SVG_ATTR(X   , SkSVGLength    , SkSVGLength(0))
+  SVG_ATTR(Y   , SkSVGLength    , SkSVGLength(0))
+  SVG_ATTR(Text, SkSVGStringType, SkSVGStringType())
 
  protected:
   void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
@@ -41,12 +39,6 @@
 
   SkFont resolveFont(const SkSVGRenderContext&) const;
 
-  SkSVGLength        fX = SkSVGLength(0);
-  SkSVGLength        fY = SkSVGLength(0);
-  SkSVGStringType    fText;
-  sk_sp<SkTypeface>  fTypeface;
-  SkTextUtils::Align fTextAlign = SkTextUtils::Align::kLeft_Align;
-
   using INHERITED = SkSVGTransformableNode;
 };
 
diff --git a/modules/svg/include/SkSVGTypes.h b/modules/svg/include/SkSVGTypes.h
index 6e84f85..840a378 100644
--- a/modules/svg/include/SkSVGTypes.h
+++ b/modules/svg/include/SkSVGTypes.h
@@ -463,4 +463,27 @@
     Scale fScale = kMeet;
 };
 
+class SkSVGTextAnchor {
+public:
+    enum class Type {
+        kStart,
+        kMiddle,
+        kEnd,
+        kInherit,
+    };
+
+    SkSVGTextAnchor() : fType(Type::kInherit) {}
+    explicit SkSVGTextAnchor(Type t) : fType(t) {}
+
+    bool operator==(const SkSVGTextAnchor& other) const {
+        return fType == other.fType;
+    }
+    bool operator!=(const SkSVGTextAnchor& other) const { return !(*this == other); }
+
+    Type type() const { return fType; }
+
+private:
+    Type fType;
+};
+
 #endif // SkSVGTypes_DEFINED
diff --git a/modules/svg/include/SkSVGValue.h b/modules/svg/include/SkSVGValue.h
index b9ea659..aafe332 100644
--- a/modules/svg/include/SkSVGValue.h
+++ b/modules/svg/include/SkSVGValue.h
@@ -38,6 +38,7 @@
         kSpreadMethod,
         kStopColor,
         kString,
+        kTextAnchor,
         kTransform,
         kViewBox,
         kVisibility,
@@ -106,6 +107,7 @@
 using SkSVGFontSizeValue     = SkSVGWrapperValue<SkSVGFontSize     , SkSVGValue::Type::kFontSize  >;
 using SkSVGFontStyleValue    = SkSVGWrapperValue<SkSVGFontStyle    , SkSVGValue::Type::kFontStyle >;
 using SkSVGFontWeightValue   = SkSVGWrapperValue<SkSVGFontWeight   , SkSVGValue::Type::kFontWeight>;
+using SkSVGTextAnchorValue   = SkSVGWrapperValue<SkSVGTextAnchor   , SkSVGValue::Type::kTextAnchor>;
 
 using SkSVGPreserveAspectRatioValue = SkSVGWrapperValue<SkSVGPreserveAspectRatio,
                                                         SkSVGValue::Type::kPreserveAspectRatio>;
diff --git a/modules/svg/src/SkSVGAttribute.cpp b/modules/svg/src/SkSVGAttribute.cpp
index 0ccea49..c9acb46 100644
--- a/modules/svg/src/SkSVGAttribute.cpp
+++ b/modules/svg/src/SkSVGAttribute.cpp
@@ -31,7 +31,8 @@
     result.fFontFamily.init("Sans");
     result.fFontStyle.init(SkSVGFontStyle::Type::kNormal);
     result.fFontSize.init(SkSVGLength(24));
-    result.fFontWeight.init(SkSVGFontWeight(SkSVGFontWeight::Type::kNormal));
+    result.fFontWeight.init(SkSVGFontWeight::Type::kNormal);
+    result.fTextAnchor.init(SkSVGTextAnchor::Type::kStart);
 
     return result;
 }
diff --git a/modules/svg/src/SkSVGAttributeParser.cpp b/modules/svg/src/SkSVGAttributeParser.cpp
index a56e8e7..05c585b 100644
--- a/modules/svg/src/SkSVGAttributeParser.cpp
+++ b/modules/svg/src/SkSVGAttributeParser.cpp
@@ -822,6 +822,26 @@
     return parsedValue && this->parseEOSToken();
 }
 
+// https://www.w3.org/TR/SVG11/text.html#TextAnchorProperty
+bool SkSVGAttributeParser::parseTextAnchor(SkSVGTextAnchor* anchor) {
+    static constexpr std::tuple<const char*, SkSVGTextAnchor::Type> gAnchorMap[] = {
+        { "start"  , SkSVGTextAnchor::Type::kStart  },
+        { "middle" , SkSVGTextAnchor::Type::kMiddle },
+        { "end"    , SkSVGTextAnchor::Type::kEnd    },
+        { "inherit", SkSVGTextAnchor::Type::kInherit},
+    };
+
+    bool parsedValue = false;
+    SkSVGTextAnchor::Type type;
+
+    if (this->parseEnumMap(gAnchorMap, &type)) {
+        *anchor = SkSVGTextAnchor(type);
+        parsedValue = true;
+    }
+
+    return parsedValue && this->parseEOSToken();
+}
+
 // https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
 bool SkSVGAttributeParser::parsePreserveAspectRatio(SkSVGPreserveAspectRatio* par) {
     static constexpr std::tuple<const char*, SkSVGPreserveAspectRatio::Align> gAlignMap[] = {
diff --git a/modules/svg/src/SkSVGDOM.cpp b/modules/svg/src/SkSVGDOM.cpp
index b989b70..f9327a3 100644
--- a/modules/svg/src/SkSVGDOM.cpp
+++ b/modules/svg/src/SkSVGDOM.cpp
@@ -308,6 +308,19 @@
     return true;
 }
 
+bool SetTextAnchorAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
+                            const char* stringValue) {
+    SkSVGTextAnchor anchor;
+    SkSVGAttributeParser parser(stringValue);
+
+    if (!parser.parseTextAnchor(&anchor)) {
+        return false;
+    }
+
+    node->setAttribute(attr, SkSVGTextAnchorValue(anchor));
+    return true;
+}
+
 bool SetPreserveAspectRatioAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
                                      const char* stringValue) {
     SkSVGPreserveAspectRatio par;
@@ -439,7 +452,7 @@
     { "stroke-width"       , { SkSVGAttribute::kStrokeWidth      , SetLengthAttribute       }},
     { "style"              , { SkSVGAttribute::kUnknown          , SetStyleAttributes       }},
     { "text"               , { SkSVGAttribute::kText             , SetStringAttribute       }},
-    { "text-anchor"        , { SkSVGAttribute::kTextAnchor       , SetStringAttribute       }},
+    { "text-anchor"        , { SkSVGAttribute::kTextAnchor       , SetTextAnchorAttribute   }},
     { "transform"          , { SkSVGAttribute::kTransform        , SetTransformAttribute    }},
     { "viewBox"            , { SkSVGAttribute::kViewBox          , SetViewBoxAttribute      }},
     { "visibility"         , { SkSVGAttribute::kVisibility       , SetVisibilityAttribute   }},
diff --git a/modules/svg/src/SkSVGNode.cpp b/modules/svg/src/SkSVGNode.cpp
index 07f3a47..21b5a76 100644
--- a/modules/svg/src/SkSVGNode.cpp
+++ b/modules/svg/src/SkSVGNode.cpp
@@ -238,6 +238,11 @@
             this->setStrokeWidth(*strokeWidth);
         }
         break;
+    case SkSVGAttribute::kTextAnchor:
+        if (const SkSVGTextAnchorValue* anchor = v.as<SkSVGTextAnchorValue>()) {
+            this->setTextAnchor(*anchor);
+        }
+        break;
     case SkSVGAttribute::kVisibility:
         if (const SkSVGVisibilityValue* visibility = v.as<SkSVGVisibilityValue>()) {
             this->setVisibility(*visibility);
diff --git a/modules/svg/src/SkSVGRenderContext.cpp b/modules/svg/src/SkSVGRenderContext.cpp
index 4b1b93b..a1b7debc 100644
--- a/modules/svg/src/SkSVGRenderContext.cpp
+++ b/modules/svg/src/SkSVGRenderContext.cpp
@@ -302,6 +302,13 @@
     // Not part of the SkPaint state; applied at render time.
 }
 
+template <>
+void commitToPaint<SkSVGAttribute::kTextAnchor>(const SkSVGPresentationAttributes&,
+                                                const SkSVGRenderContext&,
+                                                SkSVGPresentationContext*) {
+    // Not part of the SkPaint state; applied at render time.
+}
+
 }  // namespace
 
 SkSVGPresentationContext::SkSVGPresentationContext()
@@ -403,6 +410,7 @@
     ApplyLazyInheritedAttribute(StrokeMiterLimit);
     ApplyLazyInheritedAttribute(StrokeOpacity);
     ApplyLazyInheritedAttribute(StrokeWidth);
+    ApplyLazyInheritedAttribute(TextAnchor);
     ApplyLazyInheritedAttribute(Visibility);
     ApplyLazyInheritedAttribute(Color);
 
diff --git a/modules/svg/src/SkSVGText.cpp b/modules/svg/src/SkSVGText.cpp
index 4dde8b8..6cbf5d6 100644
--- a/modules/svg/src/SkSVGText.cpp
+++ b/modules/svg/src/SkSVGText.cpp
@@ -16,22 +16,6 @@
 
 SkSVGText::SkSVGText() : INHERITED(SkSVGTag::kText) {}
 
-void SkSVGText::setX(const SkSVGLength& x) { fX = x; }
-
-void SkSVGText::setY(const SkSVGLength& y) { fY = y; }
-
-void SkSVGText::setText(const SkSVGStringType& text) { fText = text; }
-
-void SkSVGText::setTextAnchor(const SkSVGStringType& text_anchor) {
-  if (strcmp(text_anchor.c_str(), "start") == 0) {
-    fTextAlign = SkTextUtils::Align::kLeft_Align;
-  } else if (strcmp(text_anchor.c_str(), "middle") == 0) {
-    fTextAlign = SkTextUtils::Align::kCenter_Align;
-  } else if (strcmp(text_anchor.c_str(), "end") == 0) {
-    fTextAlign = SkTextUtils::Align::kRight_Align;
-  }
-}
-
 SkFont SkSVGText::resolveFont(const SkSVGRenderContext& ctx) const {
     auto weight = [](const SkSVGFontWeight& w) {
         switch (w.type()) {
@@ -92,14 +76,27 @@
 void SkSVGText::onRender(const SkSVGRenderContext& ctx) const {
     const auto font = this->resolveFont(ctx);
 
+    const auto text_align = [](const SkSVGTextAnchor& anchor) {
+        switch (anchor.type()) {
+            case SkSVGTextAnchor::Type::kStart : return SkTextUtils::Align::kLeft_Align;
+            case SkSVGTextAnchor::Type::kMiddle: return SkTextUtils::Align::kCenter_Align;
+            case SkSVGTextAnchor::Type::kEnd   : return SkTextUtils::Align::kRight_Align;
+            case SkSVGTextAnchor::Type::kInherit:
+                SkASSERT(false);
+                return SkTextUtils::Align::kLeft_Align;
+        }
+        SkUNREACHABLE;
+    };
+
+    const auto align = text_align(*ctx.presentationContext().fInherited.fTextAnchor);
     if (const SkPaint* fillPaint = ctx.fillPaint()) {
         SkTextUtils::DrawString(ctx.canvas(), fText.c_str(), fX.value(), fY.value(), font,
-                                *fillPaint, fTextAlign);
+                                *fillPaint, align);
     }
 
     if (const SkPaint* strokePaint = ctx.strokePaint()) {
         SkTextUtils::DrawString(ctx.canvas(), fText.c_str(), fX.value(), fY.value(), font,
-                                *strokePaint, fTextAlign);
+                                *strokePaint, align);
     }
 }
 
@@ -129,12 +126,6 @@
         this->setText(*text);
       }
       break;
-    case SkSVGAttribute::kTextAnchor:
-      if (const auto* text_anchor = v.as<SkSVGStringValue>()) {
-        this->setTextAnchor(*text_anchor);
-      }
-      break;
-      break;
     default:
       this->INHERITED::onSetAttribute(attr, v);
   }