Bug 1056516 - let auto hyphen honor manual hyphen when hyphens:auto is set. draft
authorJeremy Chen <jeremychen@mozilla.com>
Sun, 19 Feb 2017 17:36:56 +0800
changeset 486702 36f2d0d0c4598ede924bc272fb6d252309c15907
parent 486701 95f62f8176cffe20e2b3633ee0858b8050519cd8
child 486703 15328cc24e42756a4106a93c13350a3f92f9af52
push id46037
push userjichen@mozilla.com
push dateSun, 19 Feb 2017 09:37:40 +0000
bugs1056516
milestone54.0a1
Bug 1056516 - let auto hyphen honor manual hyphen when hyphens:auto is set. According to CSS Text 3 - 6.1. Hyphenation Control: Automatic hyphenation opportunities within a word must be ignored if the word contains a conditional hyphen. After breaking at such opportunities, if a portion of that word is still too long to fit on one line, an automatic hyphenation opportunity may be used. We can't get the info about if there exists soft hyphen(s) in a word unless we get to PropertyProvider::GetHyphenationBreaks. At that timing, we also import the auto hyphen info, which means we can get both soft hyphen and auto hyphen info there. That's the reason why I'm putting the detection logic in the same place. Once we get the extra info that we need, we can do the honoring thing in BreakAndMeasureText accordingly. MozReview-Commit-ID: HcPUVHTdGeo
gfx/thebes/gfxTextRun.cpp
gfx/thebes/gfxTextRun.h
layout/generic/nsTextFrame.cpp
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -873,63 +873,91 @@ gfxTextRun::BreakAndMeasureText(uint32_t
     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;
     int32_t lastBreak = -1;
     int32_t lastBreakTrimmableChars = -1;
     gfxFloat lastBreakTrimmableAdvance = -1;
+    // Cache the last candidate break
+    int32_t lastCandidateBreak = -1;
+    int32_t lastCandidateBreakTrimmableChars = -1;
+    gfxFloat lastCandidateBreakTrimmableAdvance = -1;
+    bool lastCandidateBreakUsedHyphenation = false;
+    gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak;
     bool aborted = false;
     uint32_t end = aStart + aMaxLength;
     bool lastBreakUsedHyphenation = false;
-
     Range ligatureRange(aStart, end);
     ShrinkToLigatureBoundaries(&ligatureRange);
 
     uint32_t i;
     for (i = aStart; i < end; ++i) {
         // 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] != HyphenType::None;
+            bool atAutoHyphenWithManualHyphenInSameWord = atHyphenationBreak &&
+                hyphenBuffer[i - bufferRange.start] == HyphenType::AutoWithManualInSameWord;
             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) {
+                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;
                 }
 
                 width += advance;
                 advance = 0;
                 if (width - trimmableAdvance > aWidth) {
                     // No more text fits. Abort
                     aborted = true;
                     break;
                 }
+                // There are various kinds of break opportunities:
+                // 1. word wrap break,
+                // 2. natural break,
+                // 3. manual hyphenation break,
+                // 4. auto hyphenation break without any manual hyphenation
+                //    in the same word,
+                // 5. auto hyphenation break with another manual hyphenation
+                //    in the same word.
+                // Allow all of them except the last one to be a candidate.
+                // So, we can ensure that we don't use an automatic
+                // hyphenation opportunity within a word that contains another
+                // manual hyphenation, unless it is the only choice.
+                if (wordWrapping ||
+                    !atAutoHyphenWithManualHyphenInSameWord) {
+                    lastCandidateBreak = lastBreak;
+                    lastCandidateBreakTrimmableChars = lastBreakTrimmableChars;
+                    lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance;
+                    lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation;
+                    lastCandidateBreakPriority = *aBreakPriority;
+                }
             }
         }
 
         gfxFloat charAdvance;
         if (i >= ligatureRange.start && i < ligatureRange.end) {
             charAdvance = GetAdvanceForGlyphs(Range(i, i + 1));
             if (haveSpacing) {
                 PropertyProvider::Spacing *space =
@@ -961,16 +989,23 @@ gfxTextRun::BreakAndMeasureText(uint32_t
     // 1) all the text fit (width <= aWidth)
     // 2) some of the text fit up to a break opportunity (width > aWidth && lastBreak >= 0)
     // 3) none of the text fits before a break opportunity (width > aWidth && lastBreak < 0)
     uint32_t charsFit;
     bool usedHyphenation = false;
     if (width - trimmableAdvance <= aWidth) {
         charsFit = aMaxLength;
     } else if (lastBreak >= 0) {
+        if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) {
+            lastBreak = lastCandidateBreak;
+            lastBreakTrimmableChars = lastCandidateBreakTrimmableChars;
+            lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance;
+            lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation;
+            *aBreakPriority = lastCandidateBreakPriority;
+        }
         charsFit = lastBreak - aStart;
         trimmableChars = lastBreakTrimmableChars;
         trimmableAdvance = lastBreakTrimmableAdvance;
         usedHyphenation = lastBreakUsedHyphenation;
     } else {
         charsFit = aMaxLength;
     }
 
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -173,17 +173,18 @@ public:
      * breaks are the same as the old
      */
     virtual bool SetPotentialLineBreaks(Range aRange,
                                         const uint8_t* aBreakBefore);
 
     enum class HyphenType : uint8_t {
       None,
       Manual,
-      Auto
+      AutoWithManualInSameWord,
+      AutoWithoutManualInSameWord
     };
 
     /**
      * Layout provides PropertyProvider objects. These allow detection of
      * potential line break points and computation of spacing. We pass the data
      * this way to allow lazy data acquisition; for example BreakAndMeasureText
      * will want to only ask for properties of text it's actually looking at.
      *
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -3574,16 +3574,26 @@ gfxFloat
 PropertyProvider::GetHyphenWidth()
 {
   if (mHyphenWidth < 0) {
     mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
   }
   return mHyphenWidth + mLetterSpacing;
 }
 
+static inline bool
+IS_HYPHEN(char16_t u)
+{
+  return (u == char16_t('-') ||
+          u == 0x058A || // ARMENIAN HYPHEN
+          u == 0x2010 || // HYPHEN
+          u == 0x2012 || // FIGURE DASH
+          u == 0x2013);  // EN DASH
+}
+
 void
 PropertyProvider::GetHyphenationBreaks(Range aRange,
                                        nsTArray<HyphenType>& aBreakBefore)
 {
   NS_PRECONDITION(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
 
   if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
@@ -3626,19 +3636,47 @@ PropertyProvider::GetHyphenationBreaks(R
           (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
            run.GetSkippedOffset() > mStart.GetSkippedOffset()) ?
           HyphenType::Manual : HyphenType::None;
       allowHyphenBreakBeforeNextChar = false;
     }
   }
 
   if (mTextStyle->mHyphens == StyleHyphens::Auto) {
+    uint32_t mostRecentWordBoundary = 0;
+    bool hasManualHyphenInSameWord = aBreakBefore[0] == HyphenType::Manual;
     for (uint32_t i = 0; i < aRange.Length(); ++i) {
-      if (mTextRun->CanHyphenateBefore(aRange.start + i)) {
-        aBreakBefore[i] = HyphenType::Auto;
+      int32_t fragIndex = mFrag->GetLength() > aRange.end ?
+                          aRange.start + i : i;
+      if (mFrag->CharAt(fragIndex) == ' ') {
+        mostRecentWordBoundary = i;
+        hasManualHyphenInSameWord = false;
+      }
+      if (IS_HYPHEN(mFrag->CharAt(fragIndex))) {
+        aBreakBefore[i] = HyphenType::Manual;
+      }
+      if (!hasManualHyphenInSameWord &&
+          aBreakBefore[i] == HyphenType::Manual) {
+        hasManualHyphenInSameWord = true;
+        // This is the first manual hyphen in the current word. We can only
+        // know if the current word has a manual hyphen until now. So, we need
+        // to run a sub loop to update the hyphen types between the start of
+        // the current word and this manual hyphen.
+        for (uint32_t j = mostRecentWordBoundary + 1; j < i; j++) {
+          if (aBreakBefore[j] == HyphenType::AutoWithoutManualInSameWord) {
+            aBreakBefore[j] = HyphenType::AutoWithManualInSameWord;
+          }
+        }
+        continue;
+      }
+      if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
+          aBreakBefore[i] != HyphenType::Manual) {
+        aBreakBefore[i] = hasManualHyphenInSameWord
+                          ? HyphenType::AutoWithManualInSameWord
+                          : HyphenType::AutoWithoutManualInSameWord;
       }
     }
   }
 }
 
 void
 PropertyProvider::InitializeForDisplay(bool aTrimAfter)
 {