1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* rendering object for CSS "display: ruby" */
8 
9 #include "nsRubyFrame.h"
10 
11 #include "RubyUtils.h"
12 #include "mozilla/ComputedStyle.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/StaticPrefs_layout.h"
16 #include "mozilla/WritingModes.h"
17 #include "nsLineLayout.h"
18 #include "nsPresContext.h"
19 #include "nsRubyBaseContainerFrame.h"
20 #include "nsRubyTextContainerFrame.h"
21 
22 using namespace mozilla;
23 
24 //----------------------------------------------------------------------
25 
26 // Frame class boilerplate
27 // =======================
28 
29 NS_QUERYFRAME_HEAD(nsRubyFrame)
NS_QUERYFRAME_ENTRY(nsRubyFrame)30   NS_QUERYFRAME_ENTRY(nsRubyFrame)
31 NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
32 
33 NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame)
34 
35 nsContainerFrame* NS_NewRubyFrame(PresShell* aPresShell,
36                                   ComputedStyle* aStyle) {
37   return new (aPresShell) nsRubyFrame(aStyle, aPresShell->GetPresContext());
38 }
39 
40 //----------------------------------------------------------------------
41 
42 // nsRubyFrame Method Implementations
43 // ==================================
44 
45 /* virtual */
IsFrameOfType(uint32_t aFlags) const46 bool nsRubyFrame::IsFrameOfType(uint32_t aFlags) const {
47   if (aFlags & eBidiInlineContainer) {
48     return false;
49   }
50   return nsInlineFrame::IsFrameOfType(aFlags);
51 }
52 
53 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const54 nsresult nsRubyFrame::GetFrameName(nsAString& aResult) const {
55   return MakeFrameName(NS_LITERAL_STRING("Ruby"), aResult);
56 }
57 #endif
58 
59 /* virtual */
AddInlineMinISize(gfxContext * aRenderingContext,nsIFrame::InlineMinISizeData * aData)60 void nsRubyFrame::AddInlineMinISize(gfxContext* aRenderingContext,
61                                     nsIFrame::InlineMinISizeData* aData) {
62   for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
63     for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
64          e.Next()) {
65       e.GetBaseContainer()->AddInlineMinISize(aRenderingContext, aData);
66     }
67   }
68 }
69 
70 /* virtual */
AddInlinePrefISize(gfxContext * aRenderingContext,nsIFrame::InlinePrefISizeData * aData)71 void nsRubyFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
72                                      nsIFrame::InlinePrefISizeData* aData) {
73   for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
74     for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
75          e.Next()) {
76       e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, aData);
77     }
78   }
79   aData->mLineIsEmpty = false;
80 }
81 
FindRubyBaseContainerAncestor(nsIFrame * aFrame)82 static nsRubyBaseContainerFrame* FindRubyBaseContainerAncestor(
83     nsIFrame* aFrame) {
84   for (nsIFrame* ancestor = aFrame->GetParent();
85        ancestor && ancestor->IsFrameOfType(nsIFrame::eLineParticipant);
86        ancestor = ancestor->GetParent()) {
87     if (ancestor->IsRubyBaseContainerFrame()) {
88       return static_cast<nsRubyBaseContainerFrame*>(ancestor);
89     }
90   }
91   return nullptr;
92 }
93 
94 /* virtual */
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)95 void nsRubyFrame::Reflow(nsPresContext* aPresContext,
96                          ReflowOutput& aDesiredSize,
97                          const ReflowInput& aReflowInput,
98                          nsReflowStatus& aStatus) {
99   MarkInReflow();
100   DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
101   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
102   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
103 
104   if (!aReflowInput.mLineLayout) {
105     NS_ASSERTION(aReflowInput.mLineLayout,
106                  "No line layout provided to RubyFrame reflow method.");
107     return;
108   }
109 
110   // Grab overflow frames from prev-in-flow and its own.
111   MoveInlineOverflowToChildList(aReflowInput.mLineLayout->LineContainerFrame());
112 
113   // Clear leadings
114   mLeadings.Reset();
115 
116   // Begin the span for the ruby frame
117   WritingMode frameWM = aReflowInput.GetWritingMode();
118   WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
119   LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding();
120   nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
121                                          lineWM, frameWM);
122 
123   nscoord startEdge = 0;
124   const bool boxDecorationBreakClone =
125       StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone;
126   if (boxDecorationBreakClone || !GetPrevContinuation()) {
127     startEdge = borderPadding.IStart(frameWM);
128   }
129   NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
130                "should no longer use available widths");
131   nscoord availableISize = aReflowInput.AvailableISize();
132   availableISize -= startEdge + borderPadding.IEnd(frameWM);
133   aReflowInput.mLineLayout->BeginSpan(this, &aReflowInput, startEdge,
134                                       availableISize, &mBaseline);
135 
136   for (RubySegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
137     ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
138                   aDesiredSize.BSize(lineWM), e.GetBaseContainer(), aStatus);
139 
140     if (aStatus.IsInlineBreak()) {
141       // A break occurs when reflowing the segment.
142       // Don't continue reflowing more segments.
143       break;
144     }
145   }
146 
147   ContinuationTraversingState pullState(this);
148   while (aStatus.IsEmpty()) {
149     nsRubyBaseContainerFrame* baseContainer =
150         PullOneSegment(aReflowInput.mLineLayout, pullState);
151     if (!baseContainer) {
152       // No more continuations after, finish now.
153       break;
154     }
155     ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
156                   aDesiredSize.BSize(lineWM), baseContainer, aStatus);
157   }
158   // We never handle overflow in ruby.
159   MOZ_ASSERT(!aStatus.IsOverflowIncomplete());
160 
161   aDesiredSize.ISize(lineWM) = aReflowInput.mLineLayout->EndSpan(this);
162   if (boxDecorationBreakClone || !GetPrevContinuation()) {
163     aDesiredSize.ISize(lineWM) += borderPadding.IStart(frameWM);
164   }
165   if (boxDecorationBreakClone || aStatus.IsComplete()) {
166     aDesiredSize.ISize(lineWM) += borderPadding.IEnd(frameWM);
167   }
168 
169   // Update descendant leadings of ancestor ruby base container.
170   if (nsRubyBaseContainerFrame* rbc = FindRubyBaseContainerAncestor(this)) {
171     rbc->UpdateDescendantLeadings(mLeadings);
172   }
173 }
174 
ReflowSegment(nsPresContext * aPresContext,const ReflowInput & aReflowInput,nscoord aBlockStartAscent,nscoord aBlockSize,nsRubyBaseContainerFrame * aBaseContainer,nsReflowStatus & aStatus)175 void nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
176                                 const ReflowInput& aReflowInput,
177                                 nscoord aBlockStartAscent, nscoord aBlockSize,
178                                 nsRubyBaseContainerFrame* aBaseContainer,
179                                 nsReflowStatus& aStatus) {
180   WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
181   LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
182                         aReflowInput.AvailableBSize());
183   WritingMode rubyWM = GetWritingMode();
184   NS_ASSERTION(!rubyWM.IsOrthogonalTo(lineWM),
185                "Ruby frame writing-mode shouldn't be orthogonal to its line");
186 
187   AutoRubyTextContainerArray textContainers(aBaseContainer);
188   const uint32_t rtcCount = textContainers.Length();
189 
190   ReflowOutput baseMetrics(aReflowInput);
191   bool pushedFrame;
192   aReflowInput.mLineLayout->ReflowFrame(aBaseContainer, aStatus, &baseMetrics,
193                                         pushedFrame);
194 
195   if (aStatus.IsInlineBreakBefore()) {
196     if (aBaseContainer != mFrames.FirstChild()) {
197       // Some segments may have been reflowed before, hence it is not
198       // a break-before for the ruby container.
199       aStatus.Reset();
200       aStatus.SetInlineLineBreakAfter();
201       aStatus.SetIncomplete();
202       PushChildrenToOverflow(aBaseContainer, aBaseContainer->GetPrevSibling());
203       aReflowInput.mLineLayout->SetDirtyNextLine();
204     }
205     // This base container is not placed at all, we can skip all
206     // text containers paired with it.
207     return;
208   }
209   if (aStatus.IsIncomplete()) {
210     // It always promise that if the status is incomplete, there is a
211     // break occurs. Break before has been processed above. However,
212     // it is possible that break after happens with the frame reflow
213     // completed. It happens if there is a force break at the end.
214     MOZ_ASSERT(aStatus.IsInlineBreakAfter());
215     // Find the previous sibling which we will
216     // insert new continuations after.
217     nsIFrame* lastChild;
218     if (rtcCount > 0) {
219       lastChild = textContainers.LastElement();
220     } else {
221       lastChild = aBaseContainer;
222     }
223 
224     // Create continuations for the base container
225     nsIFrame* newBaseContainer = CreateNextInFlow(aBaseContainer);
226     // newBaseContainer is null if there are existing next-in-flows.
227     // We only need to move and push if there were not.
228     if (newBaseContainer) {
229       // Move the new frame after all the text containers
230       mFrames.RemoveFrame(newBaseContainer);
231       mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
232 
233       // Create continuations for text containers
234       nsIFrame* newLastChild = newBaseContainer;
235       for (uint32_t i = 0; i < rtcCount; i++) {
236         nsIFrame* newTextContainer = CreateNextInFlow(textContainers[i]);
237         MOZ_ASSERT(newTextContainer,
238                    "Next-in-flow of rtc should not exist "
239                    "if the corresponding rbc does not");
240         mFrames.RemoveFrame(newTextContainer);
241         mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
242         newLastChild = newTextContainer;
243       }
244     }
245     if (lastChild != mFrames.LastChild()) {
246       // Always push the next frame after the last child in this segment.
247       // It is possible that we pulled it back before our next-in-flow
248       // drain our overflow.
249       PushChildrenToOverflow(lastChild->GetNextSibling(), lastChild);
250       aReflowInput.mLineLayout->SetDirtyNextLine();
251     }
252   } else {
253     // If the ruby base container is reflowed completely, the line
254     // layout will remove the next-in-flows of that frame. But the
255     // line layout is not aware of the ruby text containers, hence
256     // it is necessary to remove them here.
257     for (uint32_t i = 0; i < rtcCount; i++) {
258       nsIFrame* nextRTC = textContainers[i]->GetNextInFlow();
259       if (nextRTC) {
260         nextRTC->GetParent()->DeleteNextInFlowChild(nextRTC, true);
261       }
262     }
263   }
264 
265   nscoord segmentISize = baseMetrics.ISize(lineWM);
266   const nsSize dummyContainerSize;
267   LogicalRect baseRect =
268       aBaseContainer->GetLogicalRect(lineWM, dummyContainerSize);
269   // We need to position our rtc frames on one side or the other of the
270   // base container's rect, using a coordinate space that's relative to
271   // the ruby frame. Right now, the base container's rect's block-axis
272   // position is relative to the block container frame containing the
273   // lines, so here we reset it to the different between the ascents of
274   // the ruby container and the ruby base container, assuming they are
275   // aligned with the baseline.
276   // XXX We may need to add border/padding here. See bug 1055667.
277   baseRect.BStart(lineWM) = aBlockStartAscent - baseMetrics.BlockStartAscent();
278   // The rect for offsets of text containers.
279   LogicalRect offsetRect = baseRect;
280   RubyBlockLeadings descLeadings = aBaseContainer->GetDescendantLeadings();
281   offsetRect.BStart(lineWM) -= descLeadings.mStart;
282   offsetRect.BSize(lineWM) += descLeadings.mStart + descLeadings.mEnd;
283   for (uint32_t i = 0; i < rtcCount; i++) {
284     nsRubyTextContainerFrame* textContainer = textContainers[i];
285     WritingMode rtcWM = textContainer->GetWritingMode();
286     nsReflowStatus textReflowStatus;
287     ReflowOutput textMetrics(aReflowInput);
288     ReflowInput textReflowInput(aPresContext, aReflowInput, textContainer,
289                                 availSize.ConvertTo(rtcWM, lineWM));
290     textContainer->Reflow(aPresContext, textMetrics, textReflowInput,
291                           textReflowStatus);
292     // Ruby text containers always return complete reflow status even when
293     // they have continuations, because the breaking has already been
294     // handled when reflowing the base containers.
295     NS_ASSERTION(textReflowStatus.IsEmpty(),
296                  "Ruby text container must not break itself inside");
297     // The metrics is initialized with reflow input of this ruby frame,
298     // hence the writing-mode is tied to rubyWM instead of rtcWM.
299     LogicalSize size = textMetrics.Size(rubyWM).ConvertTo(lineWM, rubyWM);
300     textContainer->SetSize(lineWM, size);
301 
302     nscoord reservedISize = RubyUtils::GetReservedISize(textContainer);
303     segmentISize = std::max(segmentISize, size.ISize(lineWM) + reservedISize);
304 
305     auto rubyPosition = textContainer->StyleText()->mRubyPosition;
306     MOZ_ASSERT(rubyPosition == StyleRubyPosition::Over ||
307                rubyPosition == StyleRubyPosition::Under);
308     Maybe<LogicalSide> side;
309     if (rubyPosition == StyleRubyPosition::Over) {
310       side.emplace(lineWM.LogicalSideForLineRelativeDir(eLineRelativeDirOver));
311     } else if (rubyPosition == StyleRubyPosition::Under) {
312       side.emplace(lineWM.LogicalSideForLineRelativeDir(eLineRelativeDirUnder));
313     } else {
314       // XXX inter-character support in bug 1055672
315       MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
316     }
317 
318     LogicalPoint position(lineWM);
319     if (side.isSome()) {
320       if (StaticPrefs::layout_css_ruby_intercharacter_enabled() &&
321           rtcWM.IsVerticalRL() &&
322           lineWM.GetInlineDir() == WritingMode::eInlineLTR) {
323         // Inter-character ruby annotations are only supported for vertical-rl
324         // in ltr horizontal writing. Fall back to non-inter-character behavior
325         // otherwise.
326         LogicalPoint offset(
327             lineWM, offsetRect.ISize(lineWM),
328             offsetRect.BSize(lineWM) > size.BSize(lineWM)
329                 ? (offsetRect.BSize(lineWM) - size.BSize(lineWM)) / 2
330                 : 0);
331         position = offsetRect.Origin(lineWM) + offset;
332         aReflowInput.mLineLayout->AdvanceICoord(size.ISize(lineWM));
333       } else if (side.value() == eLogicalSideBStart) {
334         offsetRect.BStart(lineWM) -= size.BSize(lineWM);
335         offsetRect.BSize(lineWM) += size.BSize(lineWM);
336         position = offsetRect.Origin(lineWM);
337       } else if (side.value() == eLogicalSideBEnd) {
338         position = offsetRect.Origin(lineWM) +
339                    LogicalPoint(lineWM, 0, offsetRect.BSize(lineWM));
340         offsetRect.BSize(lineWM) += size.BSize(lineWM);
341       } else {
342         MOZ_ASSERT_UNREACHABLE("???");
343       }
344     }
345     // Using a dummy container-size here, so child positioning may not be
346     // correct. We will fix it in nsLineLayout after the whole line is
347     // reflowed.
348     FinishReflowChild(textContainer, aPresContext, textMetrics,
349                       &textReflowInput, lineWM, position, dummyContainerSize,
350                       ReflowChildFlags::Default);
351   }
352   MOZ_ASSERT(baseRect.ISize(lineWM) == offsetRect.ISize(lineWM),
353              "Annotations should only be placed on the block directions");
354 
355   nscoord deltaISize = segmentISize - baseMetrics.ISize(lineWM);
356   if (deltaISize <= 0) {
357     RubyUtils::ClearReservedISize(aBaseContainer);
358   } else {
359     RubyUtils::SetReservedISize(aBaseContainer, deltaISize);
360     aReflowInput.mLineLayout->AdvanceICoord(deltaISize);
361   }
362 
363   // Set block leadings of the base container.
364   // The leadings are the difference between the offsetRect and the rect
365   // of this ruby container, which has block start zero and block size
366   // aBlockSize.
367   nscoord startLeading = -offsetRect.BStart(lineWM);
368   nscoord endLeading = offsetRect.BEnd(lineWM) - aBlockSize;
369   // XXX When bug 765861 gets fixed, this warning should be upgraded.
370   NS_WARNING_ASSERTION(startLeading >= 0 && endLeading >= 0,
371                        "Leadings should be non-negative (because adding "
372                        "ruby annotation can only increase the size)");
373   mLeadings.Update(startLeading, endLeading);
374 }
375 
PullOneSegment(const nsLineLayout * aLineLayout,ContinuationTraversingState & aState)376 nsRubyBaseContainerFrame* nsRubyFrame::PullOneSegment(
377     const nsLineLayout* aLineLayout, ContinuationTraversingState& aState) {
378   // Pull a ruby base container
379   nsIFrame* baseFrame = GetNextInFlowChild(aState);
380   if (!baseFrame) {
381     return nullptr;
382   }
383   MOZ_ASSERT(baseFrame->IsRubyBaseContainerFrame());
384 
385   // Get the float containing block of the base frame before we pull it.
386   nsBlockFrame* oldFloatCB = nsLayoutUtils::GetFloatContainingBlock(baseFrame);
387   PullNextInFlowChild(aState);
388 
389   // Pull all ruby text containers following the base container
390   nsIFrame* nextFrame;
391   while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
392          nextFrame->IsRubyTextContainerFrame()) {
393     PullNextInFlowChild(aState);
394   }
395 
396   if (nsBlockFrame* newFloatCB =
397           do_QueryFrame(aLineLayout->LineContainerFrame())) {
398     if (oldFloatCB && oldFloatCB != newFloatCB) {
399       newFloatCB->ReparentFloats(baseFrame, oldFloatCB, true);
400     }
401   }
402 
403   return static_cast<nsRubyBaseContainerFrame*>(baseFrame);
404 }
405