Bug 1446650 - support the SVG 2 side attribute for textPaths r=dholbert
authorRobert Longson <longsonr@gmail.com>
Thu, 22 Mar 2018 18:43:50 +0000
changeset 409497 962be3a60013250b9fea1b3ead5e0c9c16b4629b
parent 409496 4cf06e778dcffd807842cc0ec36b1239bcf60202
child 409498 d8480fd61e805a0fd997f8e5320f0de759451eba
push id101223
push userlongsonr@gmail.com
push dateThu, 22 Mar 2018 18:44:05 +0000
treeherdermozilla-inbound@962be3a60013 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1446650
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1446650 - support the SVG 2 side attribute for textPaths r=dholbert
dom/svg/SVGTextPathElement.cpp
dom/svg/SVGTextPathElement.h
layout/reftests/svg/reftest.list
layout/reftests/svg/textPath-side-attribute-01.svg
layout/svg/SVGTextFrame.cpp
--- a/dom/svg/SVGTextPathElement.cpp
+++ b/dom/svg/SVGTextPathElement.cpp
@@ -43,31 +43,41 @@ nsSVGEnumMapping SVGTextPathElement::sMe
 };
 
 nsSVGEnumMapping SVGTextPathElement::sSpacingMap[] = {
   {&nsGkAtoms::_auto, TEXTPATH_SPACINGTYPE_AUTO},
   {&nsGkAtoms::exact, TEXTPATH_SPACINGTYPE_EXACT},
   {nullptr, 0}
 };
 
-nsSVGElement::EnumInfo SVGTextPathElement::sEnumInfo[3] =
+nsSVGEnumMapping SVGTextPathElement::sSideMap[] = {
+  {&nsGkAtoms::left, TEXTPATH_SIDETYPE_LEFT},
+  {&nsGkAtoms::right, TEXTPATH_SIDETYPE_RIGHT},
+  {nullptr, 0}
+};
+
+nsSVGElement::EnumInfo SVGTextPathElement::sEnumInfo[4] =
 {
   // from SVGTextContentElement:
   { &nsGkAtoms::lengthAdjust,
     sLengthAdjustMap,
     LENGTHADJUST_SPACING
   },
   // from SVGTextPathElement:
   { &nsGkAtoms::method,
     sMethodMap,
     TEXTPATH_METHODTYPE_ALIGN
   },
   { &nsGkAtoms::spacing,
     sSpacingMap,
     TEXTPATH_SPACINGTYPE_EXACT
+  },
+  { &nsGkAtoms::side_,
+    sSideMap,
+    TEXTPATH_SIDETYPE_LEFT
   }
 };
 
 nsSVGElement::StringInfo SVGTextPathElement::sStringInfo[2] =
 {
   { &nsGkAtoms::href, kNameSpaceID_None, true },
   { &nsGkAtoms::href, kNameSpaceID_XLink, true }
 };
@@ -108,16 +118,22 @@ SVGTextPathElement::Method()
 }
 
 already_AddRefed<SVGAnimatedEnumeration>
 SVGTextPathElement::Spacing()
 {
   return mEnumAttributes[SPACING].ToDOMAnimatedEnum(this);
 }
 
+already_AddRefed<SVGAnimatedEnumeration>
+SVGTextPathElement::Side()
+{
+  return mEnumAttributes[SIDE].ToDOMAnimatedEnum(this);
+}
+
 //----------------------------------------------------------------------
 // nsIContent methods
 
 NS_IMETHODIMP_(bool)
 SVGTextPathElement::IsAttributeMapped(const nsAtom* name) const
 {
   static const MappedAttributeEntry* const map[] = {
     sColorMap,
--- a/dom/svg/SVGTextPathElement.h
+++ b/dom/svg/SVGTextPathElement.h
@@ -16,16 +16,20 @@ class nsAtom;
 class nsIContent;
 
 nsresult NS_NewSVGTextPathElement(nsIContent **aResult,
                                   already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 namespace mozilla {
 namespace dom {
 
+// textPath side types
+static const uint16_t TEXTPATH_SIDETYPE_LEFT    = 1;
+static const uint16_t TEXTPATH_SIDETYPE_RIGHT   = 2;
+
 typedef SVGTextContentElement SVGTextPathElementBase;
 
 class SVGTextPathElement final : public SVGTextPathElementBase
 {
 friend class ::SVGTextFrame;
 
 protected:
   friend nsresult (::NS_NewSVGTextPathElement(nsIContent **aResult,
@@ -39,37 +43,39 @@ public:
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
   // WebIDL
   already_AddRefed<SVGAnimatedLength> StartOffset();
   already_AddRefed<SVGAnimatedEnumeration> Method();
   already_AddRefed<SVGAnimatedEnumeration> Spacing();
+  already_AddRefed<SVGAnimatedEnumeration> Side();
   already_AddRefed<SVGAnimatedString> Href();
 
  protected:
 
   virtual LengthAttributesInfo GetLengthInfo() override;
   virtual EnumAttributesInfo GetEnumInfo() override;
   virtual StringAttributesInfo GetStringInfo() override;
 
   enum { /* TEXTLENGTH, */ STARTOFFSET = 1 };
   nsSVGLength2 mLengthAttributes[2];
   virtual nsSVGLength2* LengthAttributes() override
     { return mLengthAttributes; }
   static LengthInfo sLengthInfo[2];
 
-  enum { /* LENGTHADJUST, */ METHOD = 1, SPACING };
-  nsSVGEnum mEnumAttributes[3];
+  enum { /* LENGTHADJUST, */ METHOD = 1, SPACING, SIDE };
+  nsSVGEnum mEnumAttributes[4];
   virtual nsSVGEnum* EnumAttributes() override
     { return mEnumAttributes; }
   static nsSVGEnumMapping sMethodMap[];
   static nsSVGEnumMapping sSpacingMap[];
-  static EnumInfo sEnumInfo[3];
+  static nsSVGEnumMapping sSideMap[];
+  static EnumInfo sEnumInfo[4];
 
   enum { HREF, XLINK_HREF };
   nsSVGString mStringAttributes[2];
   static StringInfo sStringInfo[2];
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -472,16 +472,17 @@ fuzzy(16,3) == text-stroke-scaling-02b.h
 == text-stroke-scaling-02a.html text-stroke-scaling-02b.html
 == textPath-01.svg textPath-01-ref.svg
 == textPath-02.svg pass.svg
 fuzzy-if(skiaContent,1,610) == textPath-03.svg pass.svg
 == textPath-04.svg pass.svg
 == textPath-05.html pass.svg
 == textPath-06.svg pass.svg
 == textPath-line-01.svg textPath-line-01-ref.svg
+== textPath-side-attribute-01.svg pass.svg
 
 == text-white-space-01.svg text-white-space-01-ref.svg
 
 == thin-stroke-01.svg pass.svg
 
 == transform-outer-svg-01.svg transform-outer-svg-01-ref.svg
 
 == tspan-dxdy-01.svg tspan-dxdy-ref.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/textPath-side-attribute-01.svg
@@ -0,0 +1,45 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <defs>
+    <path id="path1" d="M100 100 h 300"/>
+    <!-- path2 is drawn in the same place but the opposite direction to path1
+         so using it should give the same result as side="right" -->
+    <path id="path2" d="M400 100 h -300"/>
+  </defs>
+
+  <rect fill="lime" width="100%" height="100%"/>
+
+  <text font-size="30" fill="red">
+    <textPath side="right" href="#path1">Text on a path.</textPath>
+  </text>
+
+  <text font-size="30" fill="lime" stroke="lime" stroke-width="4">
+    <textPath href="#path2">Text on a path.</textPath>
+  </text>
+
+  <text transform="translate(0, 50)" font-size="30" fill="red">
+    <textPath href="#path2">Text on a path.</textPath>
+  </text>
+
+  <text transform="translate(0, 50)" font-size="30" fill="lime" stroke="lime" stroke-width="4">
+    <textPath side="right" href="#path1">Text on a path.</textPath>
+  </text>
+
+  <text transform="translate(0, 100)" font-size="30" fill="red">
+    <textPath href="#path2">Text on a path.</textPath>
+  </text>
+
+  <text transform="translate(0, 100)" font-size="30" fill="lime" stroke="lime" stroke-width="4">
+    <textPath href="#path1">Text on a path.
+      <set attributeName="side" to="right"/>
+    </textPath>
+  </text>
+
+  <text transform="translate(0, 150)" font-size="30" fill="red">
+    <textPath href="#path1">Text on a path.</textPath>
+  </text>
+
+  <text transform="translate(0, 150)" font-size="30" fill="lime" stroke="lime" stroke-width="4">
+    <textPath side="left" href="#path1">Text on a path.</textPath>
+  </text>
+
+</svg>
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -3376,17 +3376,18 @@ SVGTextFrame::MutationObserver::Attribut
 
 void
 SVGTextFrame::HandleAttributeChangeInDescendant(Element* aElement,
                                                 int32_t aNameSpaceID,
                                                 nsAtom* aAttribute)
 {
   if (aElement->IsSVGElement(nsGkAtoms::textPath)) {
     if (aNameSpaceID == kNameSpaceID_None &&
-        aAttribute == nsGkAtoms::startOffset) {
+        (aAttribute == nsGkAtoms::startOffset ||
+         aAttribute == nsGkAtoms::side_)) {
       NotifyGlyphMetricsChange();
     } else if ((aNameSpaceID == kNameSpaceID_XLink ||
                 aNameSpaceID == kNameSpaceID_None) &&
                aAttribute == nsGkAtoms::href) {
       // Blow away our reference, if any
       nsIFrame* childElementFrame = aElement->GetPrimaryFrame();
       if (childElementFrame) {
         childElementFrame->DeleteProperty(
@@ -5087,17 +5088,20 @@ SVGTextFrame::DoTextPathLayout()
       it.AdvancePastCurrentTextPathFrame();
       uint32_t end = it.TextElementCharIndex();
       for (uint32_t i = start; i < end; i++) {
         mPositions[i].mHidden = true;
       }
       continue;
     }
 
-    nsIContent* textPath = textPathFrame->GetContent();
+    dom::SVGTextPathElement* textPath =
+      static_cast<dom::SVGTextPathElement*>(textPathFrame->GetContent());
+    RefPtr<SVGAnimatedEnumeration> sideEnum = textPath->Side();
+    uint16_t side = sideEnum->AnimVal();
 
     gfxFloat offset = GetStartOffset(textPathFrame);
     Float pathLength = path->ComputeLength();
 
     // Loop for each text frame in the text path.
     do {
       uint32_t i = it.TextElementCharIndex();
       gfxFloat halfAdvance =
@@ -5108,17 +5112,23 @@ SVGTextFrame::DoTextPathLayout()
                                 : mPositions[i].mPosition.x) +
                       sign * halfAdvance + offset;
 
       // Hide the character if it falls off the end of the path.
       mPositions[i].mHidden = midx < 0 || midx > pathLength;
 
       // Position the character on the path at the right angle.
       Point tangent; // Unit vector tangent to the point we find.
-      Point pt = path->ComputePointAtLength(Float(midx), &tangent);
+      Point pt;
+      if (side == TEXTPATH_SIDETYPE_RIGHT) {
+        pt = path->ComputePointAtLength(Float(pathLength - midx), &tangent);
+        tangent = -tangent;
+      } else {
+        pt = path->ComputePointAtLength(Float(midx), &tangent);
+      }
       Float rotation = vertical ? atan2f(-tangent.x, tangent.y)
                                 : atan2f(tangent.y, tangent.x);
       Point normal(-tangent.y, tangent.x); // Unit vector normal to the point.
       Point offsetFromPath = normal * (vertical ? -mPositions[i].mPosition.x
                                                 : mPositions[i].mPosition.y);
       pt += offsetFromPath;
       Point direction = tangent * sign;
       mPositions[i].mPosition = ThebesPoint(pt) - ThebesPoint(direction) * halfAdvance;