bug 621918 - part 2 - make SVG text respect character clusters. r=roc a=roc
authorJonathan Kew <jfkthame@gmail.com>
Tue, 11 Jan 2011 11:17:00 +0000
changeset 60286 119ef8681a16fd7eb79d515cebaa4800faab4bf3
parent 60285 8e1487c6e663aa104986c40058c2ded96f686a67
child 60287 3f8bee2e48a728b438053ea78b64766342f436b5
push id17912
push userjkew@mozilla.com
push dateTue, 11 Jan 2011 11:17:43 +0000
treeherdermozilla-central@3f8bee2e48a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, roc
bugs621918
milestone2.0b10pre
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 621918 - part 2 - make SVG text respect character clusters. r=roc a=roc
layout/svg/base/src/nsSVGGlyphFrame.cpp
--- a/layout/svg/base/src/nsSVGGlyphFrame.cpp
+++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp
@@ -60,17 +60,17 @@ using namespace mozilla;
 struct CharacterPosition {
   gfxPoint pos;
   gfxFloat angle;
   PRBool draw;
 };
   
 /**
  * This is a do-it-all helper class. It supports iterating through the
- * drawable characters of a string. For each character, it can set up
+ * drawable character clusters of a string. For each cluster, it can set up
  * a graphics context with a transform appropriate for drawing the
  * character, or a transform appropriate for emitting geometry in the
  * text metrics coordinate system (which differs from the drawing
  * coordinate system by a scale factor of AppUnitPerCSSPixels). These
  * transforms include offsets and rotations of characters along paths, and
  * the mPosition of the nsSVGGlyphFrame.
  * 
  * This helper also creates the textrun as needed. It supports detecting
@@ -79,29 +79,28 @@ struct CharacterPosition {
  * takes care of setting up the global transform if requested. It also
  * provides direct access to the character path position data for the
  * DOM APIs that need that.
  * 
  * If an error occurs, for example, a canvas TM is not available because
  * the element is in a <defs> section, then the CharacterIterator will
  * behave as if the frame has no drawable characters.
  *
- * XXX should make this iterate clusters instead
  * XXX needs RTL love
  * XXX might want to make AdvanceToCharacter constant time (e.g. by
  * caching advances and/or the CharacterPosition array across DOM
  * API calls) to ensure that calling Get*OfChar (etc) for each character
  * in the text is O(N)
  */
 class CharacterIterator
 {
 public:
   /**
-   * Sets up the iterator so that NextChar will return the first drawable
-   * char.
+   * Sets up the iterator so that NextCluster will return the first drawable
+   * cluster.
    * @param aForceGlobalTransform passed on to EnsureTextRun (see below)
    */
   CharacterIterator(nsSVGGlyphFrame *aSource, PRBool aForceGlobalTransform);
   /**
    * This matrix will be applied to aContext in the SetupFor methods below,
    * before any glyph translation/rotation.
    */
   void SetInitialMatrix(gfxContext *aContext) {
@@ -135,22 +134,29 @@ public:
    * We are scaling the glyphs up/down to the size we want so we need to
    * inverse scale the outline widths of those glyphs so they are invariant
    */
   void SetLineWidthForDrawing(gfxContext *aContext) {
     aContext->SetLineWidth(aContext->CurrentLineWidth() / mDrawScale);
   }
 
   /**
-   * Returns the index of the next char in the string that should be
-   * drawn, or -1 if there is no such character.
+   * Returns the index of the next cluster in the string that should be
+   * drawn, or -1 if there is no such cluster.
    */
-  PRInt32 NextChar();
+  PRInt32 NextCluster();
+
   /**
-   * Repeated calls NextChar until it returns aIndex (i.e. aIndex is the
+   * Returns the length of the current cluster (usually 1, unless there
+   * are combining marks)
+   */
+  PRInt32 ClusterLength();
+
+  /**
+   * Repeated calls NextCluster until it returns aIndex (i.e. aIndex is the
    * current drawable character). Returns false if that never happens
    * (because aIndex is before or equal to the current character, or
    * out of bounds, or not drawable).
    */
   PRBool AdvanceToCharacter(PRInt32 aIndex);
 
   /**
    * Resets the iterator to the beginning of the string.
@@ -565,57 +571,60 @@ nsSVGGlyphFrame::AddCharactersToPath(Cha
   aIter->SetLineWidthForDrawing(aContext);
   if (aIter->SetupForDirectTextRunDrawing(aContext)) {
     mTextRun->DrawToPath(aContext, gfxPoint(0, 0), 0,
                          mTextRun->GetLength(), nsnull, nsnull);
     return;
   }
 
   PRInt32 i;
-  while ((i = aIter->NextChar()) >= 0) {
+  while ((i = aIter->NextCluster()) >= 0) {
     aIter->SetupForDrawing(aContext);
-    mTextRun->DrawToPath(aContext, gfxPoint(0, 0), i, 1, nsnull, nsnull);
+    mTextRun->DrawToPath(aContext, gfxPoint(0, 0), i, aIter->ClusterLength(),
+                         nsnull, nsnull);
   }
 }
 
 void
 nsSVGGlyphFrame::AddBoundingBoxesToPath(CharacterIterator *aIter,
                                         gfxContext *aContext)
 {
   if (aIter->SetupForDirectTextRunMetrics(aContext)) {
     gfxTextRun::Metrics metrics =
       mTextRun->MeasureText(0, mTextRun->GetLength(),
                             gfxFont::LOOSE_INK_EXTENTS, nsnull, nsnull);
     aContext->Rectangle(metrics.mBoundingBox);
     return;
   }
 
   PRInt32 i;
-  while ((i = aIter->NextChar()) >= 0) {
+  while ((i = aIter->NextCluster()) >= 0) {
     aIter->SetupForMetrics(aContext);
     gfxTextRun::Metrics metrics =
-      mTextRun->MeasureText(i, 1, gfxFont::LOOSE_INK_EXTENTS, nsnull, nsnull);
+      mTextRun->MeasureText(i, aIter->ClusterLength(),
+                            gfxFont::LOOSE_INK_EXTENTS, nsnull, nsnull);
     aContext->Rectangle(metrics.mBoundingBox);
   }
 }
 
 void
 nsSVGGlyphFrame::FillCharacters(CharacterIterator *aIter,
                                 gfxContext *aContext)
 {
   if (aIter->SetupForDirectTextRunDrawing(aContext)) {
     mTextRun->Draw(aContext, gfxPoint(0, 0), 0,
                    mTextRun->GetLength(), nsnull, nsnull);
     return;
   }
 
   PRInt32 i;
-  while ((i = aIter->NextChar()) >= 0) {
+  while ((i = aIter->NextCluster()) >= 0) {
     aIter->SetupForDrawing(aContext);
-    mTextRun->Draw(aContext, gfxPoint(0, 0), i, 1, nsnull, nsnull);
+    mTextRun->Draw(aContext, gfxPoint(0, 0), i, aIter->ClusterLength(),
+                   nsnull, nsnull);
   }
 }
 
 gfxRect
 nsSVGGlyphFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace)
 {
   mOverrideCanvasTM = NS_NewSVGMatrix(aToBBoxUserspace);
 
@@ -1359,24 +1368,18 @@ nsSVGGlyphFrame::GetCharNumAtPosition(ns
   point->GetY(&yPos);
 
   nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
   CharacterIterator iter(this, PR_FALSE);
 
   PRInt32 i;
   PRInt32 last = -1;
   gfxPoint pt(xPos, yPos);
-  while ((i = iter.NextChar()) >= 0) {
-    // iter is the beginning of a cluster (or of the entire run);
-    // look ahead for the next cluster start, then measure the entire cluster
-    PRInt32 limit = i + 1;
-    while (limit < (PRInt32)mTextRun->GetLength() &&
-           !mTextRun->IsClusterStart(limit)) {
-      ++limit;
-    }
+  while ((i = iter.NextCluster()) >= 0) {
+    PRInt32 limit = i + iter.ClusterLength();
     gfxTextRun::Metrics metrics =
       mTextRun->MeasureText(i, limit - i, gfxFont::LOOSE_INK_EXTENTS,
                             nsnull, nsnull);
 
     // the SVG spec tells us to divide the width of the cluster equally among
     // its chars, so we'll step through the chars, allocating a share of the
     // total advance to each
     PRInt32 current, end, step;
@@ -1401,21 +1404,16 @@ nsSVGGlyphFrame::GetCharNumAtPosition(ns
         // Can't return yet; if there's glyph overlap, the last character
         // to be rendered wins, so we still have to check the rest...
         last = current;
         break; // ...but we don't need to check more slices of this cluster
       }
       current += step;
       leftEdge += width;
     }
-
-    // move iter past any trailing chars of the cluster
-    while (++i < limit) {
-      iter.NextChar();
-    }
   }
 
   return last;
 }
 
 NS_IMETHODIMP_(nsISVGGlyphFragmentLeaf *)
 nsSVGGlyphFrame::GetFirstGlyphFragment()
 {
@@ -1478,19 +1476,20 @@ PRBool
 nsSVGGlyphFrame::ContainsPoint(const nsPoint &aPoint)
 {
   nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
   SetupGlobalTransform(tmpCtx);
   CharacterIterator iter(this, PR_TRUE);
   iter.SetInitialMatrix(tmpCtx);
   
   PRInt32 i;
-  while ((i = iter.NextChar()) >= 0) {
+  while ((i = iter.NextCluster()) >= 0) {
     gfxTextRun::Metrics metrics =
-      mTextRun->MeasureText(i, 1, gfxFont::LOOSE_INK_EXTENTS, nsnull, nsnull);
+      mTextRun->MeasureText(i, iter.ClusterLength(),
+                            gfxFont::LOOSE_INK_EXTENTS, nsnull, nsnull);
     iter.SetupForMetrics(tmpCtx);
     tmpCtx->Rectangle(metrics.mBoundingBox);
   }
 
   tmpCtx->IdentityMatrix();
   return tmpCtx->PointInFill(gfxPoint(PresContext()->AppUnitsToGfxUnits(aPoint.x),
                                       PresContext()->AppUnitsToGfxUnits(aPoint.y)));
 }
@@ -1697,17 +1696,17 @@ CharacterIterator::SetupForDirectTextRun
     return PR_FALSE;
   aContext->SetMatrix(mInitialMatrix);
   aContext->Translate(mSource->mPosition);
   aContext->Scale(aScale, aScale);
   return PR_TRUE;
 }
 
 PRInt32
-CharacterIterator::NextChar()
+CharacterIterator::NextCluster()
 {
   if (mInError) {
 #ifdef DEBUG
     if (mCurrentChar != -1) {
       PRBool pastEnd = (mCurrentChar >= PRInt32(mSource->mTextRun->GetLength()));
       NS_ABORT_IF_FALSE(pastEnd, "Past the end of CharacterIterator. Missing Reset?");
     }
 #endif
@@ -1722,25 +1721,43 @@ CharacterIterator::NextChar()
     }
     ++mCurrentChar;
 
     if (mCurrentChar >= PRInt32(mSource->mTextRun->GetLength())) {
       mInError = PR_TRUE;
       return -1;
     }
 
-    if (mPositions.IsEmpty() || mPositions[mCurrentChar].draw)
+    if (mSource->mTextRun->IsClusterStart(mCurrentChar) &&
+        (mPositions.IsEmpty() || mPositions[mCurrentChar].draw)) {
       return mCurrentChar;
+    }
   }
 }
 
+PRInt32
+CharacterIterator::ClusterLength()
+{
+  if (mInError) {
+    return 0;
+  }
+
+  PRInt32 i = mCurrentChar;
+  while (++i < mSource->mTextRun->GetLength()) {
+    if (mSource->mTextRun->IsClusterStart(i)) {
+      break;
+    }
+  }
+  return i - mCurrentChar;
+}
+
 PRBool
 CharacterIterator::AdvanceToCharacter(PRInt32 aIndex)
 {
-  while (NextChar() != -1) {
+  while (NextCluster() != -1) {
     if (mCurrentChar == aIndex)
       return PR_TRUE;
   }
   return PR_FALSE;
 }
 
 void
 CharacterIterator::SetupFor(gfxContext *aContext, float aScale)