Bug 1056516 - let auto hyphen honer soft hyphen when hyphens:auto is set. draft
authorJeremy Chen <jeremychen@mozilla.com>
Tue, 07 Feb 2017 00:50:35 +0800
changeset 479419 0a0f4bfdd2bf31ba9a04c387be29a5223f635bc7
parent 479417 7f1b358fb17dfd982c5e18c34d5735cd481c7f7c
child 544677 60b707d4b15ff19fb6ceb706cf087379a277fd65
push id44247
push userjichen@mozilla.com
push dateMon, 06 Feb 2017 16:51:09 +0000
bugs1056516
milestone54.0a1
Bug 1056516 - let auto hyphen honer soft hyphen when hyphens:auto is set. MozReview-Commit-ID: q7Y0n3pLJi
gfx/src/nsFontMetrics.cpp
gfx/thebes/gfxTextRun.cpp
gfx/thebes/gfxTextRun.h
layout/generic/nsTextFrame.cpp
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -79,17 +79,18 @@ private:
     }
 
     RefPtr<gfxTextRun> mTextRun;
 };
 
 class StubPropertyProvider : public gfxTextRun::PropertyProvider {
 public:
     virtual void GetHyphenationBreaks(gfxTextRun::Range aRange,
-                                      bool* aBreakBefore) {
+                                      bool* aBreakBefore,
+                                      bool* aHasSoftHyphenInSameWord) {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
     }
     virtual mozilla::StyleHyphens GetHyphensOption() {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
         return mozilla::StyleHyphens::None;
     }
     virtual gfxFloat GetHyphenWidth() {
         NS_ERROR("This shouldn't be called because we never enable hyphens");
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -856,22 +856,24 @@ gfxTextRun::BreakAndMeasureText(uint32_t
     Range bufferRange(aStart, aStart +
         std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE));
     PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE];
     bool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0;
     if (haveSpacing) {
         GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
     }
     bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE];
+    bool hasSoftHyphenInSameWord[MEASUREMENT_BUFFER_SIZE];
     bool haveHyphenation = aProvider &&
         (aProvider->GetHyphensOption() == StyleHyphens::Auto ||
          (aProvider->GetHyphensOption() == StyleHyphens::Manual &&
           (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
     if (haveHyphenation) {
-        aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer);
+        aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer,
+                                        hasSoftHyphenInSameWord);
     }
 
     gfxFloat width = 0;
     gfxFloat advance = 0;
     // The number of space characters that can be trimmed or hang at a soft-wrap
     uint32_t trimmableChars = 0;
     // The amount of space removed by ignoring trimmableChars
     gfxFloat trimmableAdvance = 0;
@@ -891,48 +893,54 @@ gfxTextRun::BreakAndMeasureText(uint32_t
             // Fetch more spacing and hyphenation data
             bufferRange.start = i;
             bufferRange.end = std::min(aStart + aMaxLength,
                                        i + MEASUREMENT_BUFFER_SIZE);
             if (haveSpacing) {
                 GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
             }
             if (haveHyphenation) {
-                aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer);
+                aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer,
+                                                hasSoftHyphenInSameWord);
             }
         }
 
         // There can't be a word-wrap break opportunity at the beginning of the
         // line: if the width is too small for even one character to fit, it
         // could be the first and last break opportunity on the line, and that
         // would trigger an infinite loop.
         if (aSuppressBreak != eSuppressAllBreaks &&
             (aSuppressBreak != eSuppressInitialBreak || i > aStart)) {
             bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1;
             bool atHyphenationBreak = !atNaturalBreak &&
                 haveHyphenation && hyphenBuffer[i - bufferRange.start];
+            bool atAutoHyphenWithSoftHyphenInSameWord = atHyphenationBreak &&
+                hasSoftHyphenInSameWord[i - bufferRange.start];
             bool atBreak = atNaturalBreak || atHyphenationBreak;
             bool wordWrapping =
                 aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() &&
                 *aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
 
             if (atBreak || wordWrapping) {
                 gfxFloat hyphenatedAdvance = advance;
                 if (atHyphenationBreak) {
                     hyphenatedAdvance += aProvider->GetHyphenWidth();
                 }
 
-                if (lastBreak < 0 || width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
-                    // We can break here.
-                    lastBreak = i;
-                    lastBreakTrimmableChars = trimmableChars;
-                    lastBreakTrimmableAdvance = trimmableAdvance;
-                    lastBreakUsedHyphenation = atHyphenationBreak;
-                    *aBreakPriority = atBreak ? gfxBreakPriority::eNormalBreak
-                                              : gfxBreakPriority::eWordWrapBreak;
+                if (lastBreak < 0 ||
+                    width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
+                    if (lastBreak < 0 || !atAutoHyphenWithSoftHyphenInSameWord) {
+                        // We can break here.
+                        lastBreak = i;
+                        lastBreakTrimmableChars = trimmableChars;
+                        lastBreakTrimmableAdvance = trimmableAdvance;
+                        lastBreakUsedHyphenation = atHyphenationBreak;
+                        *aBreakPriority = atBreak ? gfxBreakPriority::eNormalBreak
+                                                  : gfxBreakPriority::eWordWrapBreak;
+                    }
                 }
 
                 width += advance;
                 advance = 0;
                 if (width - trimmableAdvance > aWidth) {
                     // No more text fits. Abort
                     aborted = true;
                     break;
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -184,17 +184,18 @@ public:
      * NOTE that requested spacing may not actually be applied, if the textrun
      * is unable to apply it in some context. Exception: spacing around a
      * whitespace character MUST always be applied.
      */
     class PropertyProvider {
     public:
         // Detect hyphenation break opportunities in the given range; breaks
         // not at cluster boundaries will be ignored.
-        virtual void GetHyphenationBreaks(Range aRange, bool *aBreakBefore) = 0;
+        virtual void GetHyphenationBreaks(Range aRange, bool *aBreakBefore,
+                                          bool *aHasSoftHyphenInSameWord) = 0;
 
         // Returns the provider's hyphenation setting, so callers can decide
         // whether it is necessary to call GetHyphenationBreaks.
         // Result is an StyleHyphens value.
         virtual mozilla::StyleHyphens GetHyphensOption() = 0;
 
         // Returns the extra width that will be consumed by a hyphen. This should
         // be constant for a given textrun.
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -3110,17 +3110,18 @@ public:
 
   // Call this after construction if you're not going to reflow the text
   void InitializeForDisplay(bool aTrimAfter);
 
   void InitializeForMeasure();
 
   virtual void GetSpacing(Range aRange, Spacing* aSpacing);
   virtual gfxFloat GetHyphenWidth();
-  virtual void GetHyphenationBreaks(Range aRange, bool* aBreakBefore);
+  virtual void GetHyphenationBreaks(Range aRange, bool* aBreakBefore,
+                                    bool *aHasSoftHyphenInSameWord);
   virtual StyleHyphens GetHyphensOption() {
     return mTextStyle->mHyphens;
   }
 
   virtual already_AddRefed<DrawTarget> GetDrawTarget() {
     return CreateReferenceDrawTarget(GetFrame());
   }
 
@@ -3548,21 +3549,23 @@ PropertyProvider::GetHyphenWidth()
 {
   if (mHyphenWidth < 0) {
     mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
   }
   return mHyphenWidth + mLetterSpacing;
 }
 
 void
-PropertyProvider::GetHyphenationBreaks(Range aRange, bool* aBreakBefore)
+PropertyProvider::GetHyphenationBreaks(Range aRange, bool* aBreakBefore,
+                                       bool *aHasSoftHyphenInSameWord)
 {
   NS_PRECONDITION(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
 
+  memset(aHasSoftHyphenInSameWord, false, aRange.Length() * sizeof(bool));
   if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
       mTextStyle->mHyphens == StyleHyphens::None)
   {
     memset(aBreakBefore, false, aRange.Length() * sizeof(bool));
     return;
   }
 
   // Iterate through the original-string character runs
@@ -3583,27 +3586,62 @@ PropertyProvider::GetHyphenationBreaks(R
     if (run.IsSkipped()) {
       // Check if there's a soft hyphen which would let us hyphenate before
       // the next non-skipped character. Don't look at soft hyphens followed
       // by other skipped characters, we won't use them.
       allowHyphenBreakBeforeNextChar =
         mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
     } else {
       int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
-      memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool));
+      memset(aBreakBefore + runOffsetInSubstring, false,
+             run.GetRunLength()*sizeof(bool));
       // Don't allow hyphen breaks at the start of the line
       aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar &&
           (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
            run.GetSkippedOffset() > mStart.GetSkippedOffset());
       allowHyphenBreakBeforeNextChar = false;
     }
   }
 
   if (mTextStyle->mHyphens == StyleHyphens::Auto) {
+    nsTArray<bool> isWordBoundary;
+    isWordBoundary.SetLength(aRange.Length());
+    memset(isWordBoundary.Elements(), false, aRange.Length() * sizeof(bool));
+    // Get info about boundarirs between words
     for (uint32_t i = 0; i < aRange.Length(); ++i) {
+      if (mFrag->CharAt(aRange.start + i) == ' ') {
+        isWordBoundary[i] = true;
+      }
+    }
+    // Use word boundary to determine if an auto hyphen is with another
+    // soft hyphen in the same word.
+    for (uint32_t i = 0; i < aRange.Length(); ++i) {
+      if (!isWordBoundary[i]) {
+        uint32_t j;
+        bool hasSoftHyphenInSameWord = false;
+        for (j = i + 1; !isWordBoundary[j] && j + 1 < aRange.Length(); j++) {
+          MOZ_ASSERT(j < aRange.Length(), "invalid j!!");
+          if (aBreakBefore[j]) {
+            hasSoftHyphenInSameWord = true;
+          }
+        }
+        for (uint32_t k = i; k <= j; k++) {
+          MOZ_ASSERT(k < aRange.Length(), "invalid k!!");
+          if (mTextRun->CanHyphenateBefore(aRange.start + k)) {
+            // Don't set aHasSoftHyphenInSameWord for the soft hyphen position.
+            if (!aBreakBefore[k] && hasSoftHyphenInSameWord) {
+              aHasSoftHyphenInSameWord[k] = true;
+            }
+            aBreakBefore[k] = true;
+          }
+        }
+        i = j + 1;
+        continue;
+      }
+
       if (mTextRun->CanHyphenateBefore(aRange.start + i)) {
         aBreakBefore[i] = true;
       }
     }
   }
 }
 
 void
@@ -8337,23 +8375,28 @@ nsTextFrame::AddInlineMinISizeForFlow(ns
       aData->OptionallyBreak();
     }
     aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
     aData->mTrailingWhitespace = 0;
     return;
   }
 
   AutoTArray<bool,BIG_TEXT_NODE_SIZE> hyphBuffer;
+  AutoTArray<bool,BIG_TEXT_NODE_SIZE> autoHyphBuffer;
   bool *hyphBreakBefore = nullptr;
+  bool *hasSoftHyphenInSameWord = nullptr;
   if (hyphenating) {
     hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start,
                                                 fallible);
+    hasSoftHyphenInSameWord = autoHyphBuffer.AppendElements(flowEndInTextRun - start,
+                                                            fallible);
     if (hyphBreakBefore) {
       provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
-                                    hyphBreakBefore);
+                                    hyphBreakBefore,
+                                    hasSoftHyphenInSameWord);
     }
   }
 
   for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
     bool preformattedNewline = false;
     bool preformattedTab = false;
     if (i < flowEndInTextRun) {
       // XXXldb Shouldn't we be including the newline as part of the