Bug 1089431 part 2 - Break between ruby bases according to line-breaking rules. r=jfkthame
authorXidorn Quan <quanxunzhen@gmail.com>
Wed, 11 Feb 2015 10:26:56 +1100
changeset 228558 eada1a2a1b28db0cedc06da6b079df5ba7cf22c8
parent 228557 27ea871e6e5c457cb340efa5ae0c4a59a0768736
child 228559 e72b4bc2eddb4cd46024590d4f604e59a3f57334
push id28264
push usercbook@mozilla.com
push dateWed, 11 Feb 2015 13:58:35 +0000
treeherdermozilla-central@38058cb42a0e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs1089431
milestone38.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 1089431 part 2 - Break between ruby bases according to line-breaking rules. r=jfkthame
layout/generic/nsRubyBaseContainerFrame.cpp
--- a/layout/generic/nsRubyBaseContainerFrame.cpp
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -9,16 +9,17 @@
 #include "nsRubyBaseContainerFrame.h"
 #include "nsContentUtils.h"
 #include "nsLineLayout.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsStyleStructInlines.h"
 #include "WritingModes.h"
 #include "RubyUtils.h"
+#include "nsTextFrame.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/DebugOnly.h"
 
 using namespace mozilla;
 
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
@@ -305,16 +306,17 @@ nsRubyBaseContainerFrame::ComputeSize(ns
 /* virtual */ nscoord
 nsRubyBaseContainerFrame::GetLogicalBaseline(WritingMode aWritingMode) const
 {
   return mBaseline;
 }
 
 struct nsRubyBaseContainerFrame::ReflowState
 {
+  bool mAllowInitialLineBreak;
   bool mAllowLineBreak;
   const TextContainerArray& mTextContainers;
   const nsHTMLReflowState& mBaseReflowState;
   const nsTArray<UniquePtr<nsHTMLReflowState>>& mTextReflowStates;
 };
 
 // Check whether the given extra isize can fit in the line in base level.
 static bool
@@ -413,67 +415,54 @@ nsRubyBaseContainerFrame::Reflow(nsPresC
   // Allow line break between ruby bases when white-space allows,
   // we are not inside a nested ruby, and there is no span.
   bool allowLineBreak = !inNestedRuby && StyleText()->WhiteSpaceCanWrap(this);
   bool allowInitialLineBreak = allowLineBreak;
   if (!GetPrevInFlow()) {
     allowInitialLineBreak = !inNestedRuby &&
       parent->StyleText()->WhiteSpaceCanWrap(parent);
   }
-  if (allowInitialLineBreak && aReflowState.mLineLayout->LineIsBreakable() &&
-      aReflowState.mLineLayout->NotifyOptionalBreakPosition(
-        this, 0, 0 <= aReflowState.AvailableISize(),
-        gfxBreakPriority::eNormalBreak)) {
-    aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+  if (!aReflowState.mLineLayout->LineIsBreakable()) {
+    allowInitialLineBreak = false;
   }
 
   nscoord isize = 0;
-  if (aStatus == NS_FRAME_COMPLETE) {
-    // Reflow columns excluding any span
-    ReflowState reflowState = {
-      allowLineBreak && !hasSpan, textContainers, aReflowState, reflowStates
-    };
-    isize = ReflowColumns(reflowState, aStatus);
-  }
+  // Reflow columns excluding any span
+  ReflowState reflowState = {
+    allowInitialLineBreak, allowLineBreak && !hasSpan,
+    textContainers, aReflowState, reflowStates
+  };
+  isize = ReflowColumns(reflowState, aStatus);
   DebugOnly<nscoord> lineSpanSize = aReflowState.mLineLayout->EndSpan(this);
   aDesiredSize.ISize(lineWM) = isize;
   // When there are no frames inside the ruby base container, EndSpan
   // will return 0. However, in this case, the actual width of the
   // container could be non-zero because of non-empty ruby annotations.
   MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
              isize == lineSpanSize || mFrames.IsEmpty());
 
   // If there exists any span, the columns must either be completely
   // reflowed, or be not reflowed at all.
   MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
              NS_FRAME_IS_COMPLETE(aStatus) || !hasSpan);
   if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
       NS_FRAME_IS_COMPLETE(aStatus) && hasSpan) {
     // Reflow spans
     ReflowState reflowState = {
-      false, textContainers, aReflowState, reflowStates
+      false, false, textContainers, aReflowState, reflowStates
     };
     nscoord spanISize = ReflowSpans(reflowState);
     nscoord deltaISize = spanISize - isize;
     if (deltaISize > 0) {
       if (allowLineBreak && ShouldBreakBefore(aReflowState, deltaISize)) {
         aStatus = NS_INLINE_LINE_BREAK_BEFORE();
       } else {
         isize = spanISize;
       }
     }
-    // When there are spans, ReflowColumns and ReflowOneColumn won't
-    // record any optional break position. We have to record one
-    // at the end of this segment.
-    if (!NS_INLINE_IS_BREAK(aStatus) && allowLineBreak &&
-        aReflowState.mLineLayout->NotifyOptionalBreakPosition(
-          this, INT32_MAX, isize <= aReflowState.AvailableISize(),
-          gfxBreakPriority::eNormalBreak)) {
-      aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
-    }
   }
 
   for (uint32_t i = 0; i < rtcCount; i++) {
     // It happens before the ruby text container is reflowed, and that
     // when it is reflowed, it will just use this size.
     nsRubyTextContainerFrame* textContainer = textContainers[i];
     nsLineLayout* lineLayout = lineLayouts[i].get();
 
@@ -622,25 +611,75 @@ nsRubyBaseContainerFrame::ReflowColumns(
     // not need to push anything.
     MOZ_ASSERT(e.AtEnd());
     aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
   }
 
   return icoord;
 }
 
+static gfxBreakPriority
+LineBreakBefore(const nsHTMLReflowState& aReflowState, nsRubyBaseFrame* aFrame)
+{
+  for (nsIFrame* child = aFrame; child;
+       child = child->GetFirstPrincipalChild()) {
+    if (!child->CanContinueTextRun()) {
+      // It is not an inline element. We can break before it.
+      return gfxBreakPriority::eNormalBreak;
+    }
+    if (child->GetType() != nsGkAtoms::textFrame) {
+      continue;
+    }
+
+    auto textFrame = static_cast<nsTextFrame*>(child);
+    gfxSkipCharsIterator iter =
+      textFrame->EnsureTextRun(nsTextFrame::eInflated,
+                               aReflowState.rendContext->ThebesContext(),
+                               aReflowState.mLineLayout->LineContainerFrame(),
+                               aReflowState.mLineLayout->GetLine());
+    iter.SetOriginalOffset(textFrame->GetContentOffset());
+    uint32_t pos = iter.GetSkippedOffset();
+    gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated);
+    // Return whether we can break before the first character.
+    if (textRun->CanBreakLineBefore(pos)) {
+      return gfxBreakPriority::eNormalBreak;
+    }
+    // Check whether we can wrap word here.
+    const nsStyleText* textStyle = textFrame->StyleText();
+    if (textStyle->WordCanWrap(textFrame) && textRun->IsClusterStart(pos)) {
+      return gfxBreakPriority::eWordWrapBreak;
+    }
+    // We cannot break before.
+    return gfxBreakPriority::eNoBreak;
+  }
+  // Neither block, nor text frame is found as a leaf. We won't break
+  // before this base frame. It is the behavior of empty spans.
+  return gfxBreakPriority::eNoBreak;
+}
+
 nscoord
 nsRubyBaseContainerFrame::ReflowOneColumn(const ReflowState& aReflowState,
                                           uint32_t aColumnIndex,
                                           const RubyColumn& aColumn,
                                           nsReflowStatus& aStatus)
 {
   const nsHTMLReflowState& baseReflowState = aReflowState.mBaseReflowState;
   const auto& textReflowStates = aReflowState.mTextReflowStates;
 
+  if (aColumn.mBaseFrame) {
+    int32_t pos = baseReflowState.mLineLayout->
+      GetForcedBreakPosition(aColumn.mBaseFrame);
+    MOZ_ASSERT(pos == -1 || pos == 0,
+               "It should either break before, or not break at all.");
+    if (pos >= 0) {
+      aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+      return 0;
+    }
+  }
+
   const uint32_t rtcCount = aReflowState.mTextContainers.Length();
   MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount);
   MOZ_ASSERT(textReflowStates.Length() == rtcCount);
   nscoord istart = baseReflowState.mLineLayout->GetCurrentICoord();
   nscoord columnISize = 0;
 
   nsAutoString baseText;
   if (aColumn.mBaseFrame) {
@@ -700,16 +739,39 @@ nsRubyBaseContainerFrame::ReflowOneColum
     nsLineLayout* lineLayout = baseReflowState.mLineLayout;
     nscoord baseIStart = lineLayout->GetCurrentICoord();
     lineLayout->ReflowFrame(aColumn.mBaseFrame, reflowStatus,
                             nullptr, pushedFrame);
     MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus) && !pushedFrame,
                "Any line break inside ruby box should has been suppressed");
     nscoord baseISize = lineLayout->GetCurrentICoord() - baseIStart;
     columnISize = std::max(columnISize, baseISize);
+
+    bool allowBreakBefore = aColumnIndex ?
+      aReflowState.mAllowLineBreak : aReflowState.mAllowInitialLineBreak;
+    if (allowBreakBefore) {
+      bool shouldBreakBefore = false;
+      gfxBreakPriority breakPriority =
+        LineBreakBefore(baseReflowState, aColumn.mBaseFrame);
+      if (breakPriority != gfxBreakPriority::eNoBreak) {
+        int32_t offset;
+        gfxBreakPriority lastBreakPriority;
+        baseReflowState.mLineLayout->
+          GetLastOptionalBreakPosition(&offset, &lastBreakPriority);
+        shouldBreakBefore = breakPriority >= lastBreakPriority;
+      }
+      if (shouldBreakBefore) {
+        bool fits = istart <= baseReflowState.AvailableISize();
+        DebugOnly<bool> breakBefore =
+          baseReflowState.mLineLayout->NotifyOptionalBreakPosition(
+            aColumn.mBaseFrame, 0, fits, breakPriority);
+        MOZ_ASSERT(!breakBefore, "The break notified here should have "
+                   "triggered at the start of this method.");
+      }
+    }
   }
 
   // Align all the line layout to the new coordinate.
   nscoord icoord = istart + columnISize;
   nscoord deltaISize = icoord - baseReflowState.mLineLayout->GetCurrentICoord();
   if (deltaISize > 0) {
     baseReflowState.mLineLayout->AdvanceICoord(deltaISize);
     if (aColumn.mBaseFrame) {
@@ -729,23 +791,16 @@ nsRubyBaseContainerFrame::ReflowOneColum
         RubyUtils::SetReservedISize(textFrame, deltaISize);
       }
     }
     if (aColumn.mBaseFrame && textFrame) {
       lineLayout->AttachLastFrameToBaseLineLayout();
     }
   }
 
-  if (aReflowState.mAllowLineBreak &&
-      baseReflowState.mLineLayout->NotifyOptionalBreakPosition(
-        this, aColumnIndex + 1, icoord <= baseReflowState.AvailableISize(),
-        gfxBreakPriority::eNormalBreak)) {
-    aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
-  }
-
   return columnISize;
 }
 
 nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
     nsRubyBaseContainerFrame* aBaseContainer,
     const TextContainerArray& aTextContainers)
   : mBase(aBaseContainer)
   , mTextContainers(aTextContainers)