Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 et sw=2 tw=80: */
3 : /* This Source Code is subject to the terms of the Mozilla Public License
4 : * version 2.0 (the "License"). You can obtain a copy of the License at
5 : * http://mozilla.org/MPL/2.0/. */
6 :
7 : /* rendering object for CSS "display: ruby-base-container" */
8 :
9 : #include "nsRubyBaseContainerFrame.h"
10 : #include "nsRubyTextContainerFrame.h"
11 : #include "nsRubyBaseFrame.h"
12 : #include "nsRubyTextFrame.h"
13 : #include "mozilla/DebugOnly.h"
14 : #include "mozilla/Maybe.h"
15 : #include "mozilla/WritingModes.h"
16 : #include "nsLayoutUtils.h"
17 : #include "nsLineLayout.h"
18 : #include "nsPresContext.h"
19 : #include "nsStyleContext.h"
20 : #include "nsStyleStructInlines.h"
21 : #include "nsTextFrame.h"
22 : #include "RubyUtils.h"
23 :
24 : using namespace mozilla;
25 : using namespace mozilla::gfx;
26 :
27 : //----------------------------------------------------------------------
28 :
29 : // Frame class boilerplate
30 : // =======================
31 :
32 0 : NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame)
33 0 : NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame)
34 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
35 :
36 0 : NS_IMPL_FRAMEARENA_HELPERS(nsRubyBaseContainerFrame)
37 :
38 : nsContainerFrame*
39 0 : NS_NewRubyBaseContainerFrame(nsIPresShell* aPresShell,
40 : nsStyleContext* aContext)
41 : {
42 0 : return new (aPresShell) nsRubyBaseContainerFrame(aContext);
43 : }
44 :
45 :
46 : //----------------------------------------------------------------------
47 :
48 : // nsRubyBaseContainerFrame Method Implementations
49 : // ===============================================
50 :
51 : #ifdef DEBUG_FRAME_DUMP
52 : nsresult
53 0 : nsRubyBaseContainerFrame::GetFrameName(nsAString& aResult) const
54 : {
55 0 : return MakeFrameName(NS_LITERAL_STRING("RubyBaseContainer"), aResult);
56 : }
57 : #endif
58 :
59 : static gfxBreakPriority
60 0 : LineBreakBefore(nsIFrame* aFrame,
61 : DrawTarget* aDrawTarget,
62 : nsIFrame* aLineContainerFrame,
63 : const nsLineList::iterator* aLine)
64 : {
65 0 : for (nsIFrame* child = aFrame; child;
66 0 : child = child->PrincipalChildList().FirstChild()) {
67 0 : if (!child->CanContinueTextRun()) {
68 : // It is not an inline element. We can break before it.
69 0 : return gfxBreakPriority::eNormalBreak;
70 : }
71 0 : if (!child->IsTextFrame()) {
72 0 : continue;
73 : }
74 :
75 0 : auto textFrame = static_cast<nsTextFrame*>(child);
76 : gfxSkipCharsIterator iter =
77 : textFrame->EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
78 0 : aLineContainerFrame, aLine);
79 0 : iter.SetOriginalOffset(textFrame->GetContentOffset());
80 0 : uint32_t pos = iter.GetSkippedOffset();
81 0 : gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated);
82 0 : MOZ_ASSERT(textRun, "fail to build textrun?");
83 0 : if (!textRun || pos >= textRun->GetLength()) {
84 : // The text frame contains no character at all.
85 0 : return gfxBreakPriority::eNoBreak;
86 : }
87 : // Return whether we can break before the first character.
88 0 : if (textRun->CanBreakLineBefore(pos)) {
89 0 : return gfxBreakPriority::eNormalBreak;
90 : }
91 : // Check whether we can wrap word here.
92 0 : const nsStyleText* textStyle = textFrame->StyleText();
93 0 : if (textStyle->WordCanWrap(textFrame) && textRun->IsClusterStart(pos)) {
94 0 : return gfxBreakPriority::eWordWrapBreak;
95 : }
96 : // We cannot break before.
97 0 : return gfxBreakPriority::eNoBreak;
98 : }
99 : // Neither block, nor text frame is found as a leaf. We won't break
100 : // before this base frame. It is the behavior of empty spans.
101 0 : return gfxBreakPriority::eNoBreak;
102 : }
103 :
104 : static void
105 0 : GetIsLineBreakAllowed(nsIFrame* aFrame, bool aIsLineBreakable,
106 : bool* aAllowInitialLineBreak, bool* aAllowLineBreak)
107 : {
108 0 : nsIFrame* parent = aFrame->GetParent();
109 0 : bool lineBreakSuppressed = parent->StyleContext()->ShouldSuppressLineBreak();
110 : // Allow line break between ruby bases when white-space allows,
111 : // we are not inside a nested ruby, and there is no span.
112 0 : bool allowLineBreak = !lineBreakSuppressed &&
113 0 : aFrame->StyleText()->WhiteSpaceCanWrap(aFrame);
114 0 : bool allowInitialLineBreak = allowLineBreak;
115 0 : if (!aFrame->GetPrevInFlow()) {
116 0 : allowInitialLineBreak = !lineBreakSuppressed &&
117 0 : parent->StyleText()->WhiteSpaceCanWrap(parent);
118 : }
119 0 : if (!aIsLineBreakable) {
120 0 : allowInitialLineBreak = false;
121 : }
122 0 : *aAllowInitialLineBreak = allowInitialLineBreak;
123 0 : *aAllowLineBreak = allowLineBreak;
124 0 : }
125 :
126 : /**
127 : * @param aBaseISizeData is an in/out param. This method updates the
128 : * `skipWhitespace` and `trailingWhitespace` fields of the struct with
129 : * the base level frame. Note that we don't need to do the same thing
130 : * for ruby text frames, because they are text run container themselves
131 : * (see nsTextFrame.cpp:BuildTextRuns), and thus no whitespace collapse
132 : * happens across the boundary of those frames.
133 : */
134 : static nscoord
135 0 : CalculateColumnPrefISize(gfxContext* aRenderingContext,
136 : const RubyColumnEnumerator& aEnumerator,
137 : nsIFrame::InlineIntrinsicISizeData* aBaseISizeData)
138 : {
139 0 : nscoord max = 0;
140 0 : uint32_t levelCount = aEnumerator.GetLevelCount();
141 0 : for (uint32_t i = 0; i < levelCount; i++) {
142 0 : nsIFrame* frame = aEnumerator.GetFrameAtLevel(i);
143 0 : if (frame) {
144 0 : nsIFrame::InlinePrefISizeData data;
145 0 : if (i == 0) {
146 0 : data.SetLineContainer(aBaseISizeData->LineContainer());
147 0 : data.mSkipWhitespace = aBaseISizeData->mSkipWhitespace;
148 0 : data.mTrailingWhitespace = aBaseISizeData->mTrailingWhitespace;
149 : } else {
150 : // The line container of ruby text frames is their parent,
151 : // ruby text container frame.
152 0 : data.SetLineContainer(frame->GetParent());
153 : }
154 0 : frame->AddInlinePrefISize(aRenderingContext, &data);
155 0 : MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines");
156 0 : max = std::max(max, data.mCurrentLine);
157 0 : if (i == 0) {
158 0 : aBaseISizeData->mSkipWhitespace = data.mSkipWhitespace;
159 0 : aBaseISizeData->mTrailingWhitespace = data.mTrailingWhitespace;
160 : }
161 : }
162 : }
163 0 : return max;
164 : }
165 :
166 : // FIXME Currently we use pref isize of ruby content frames for
167 : // computing min isize of ruby frame, which may cause problem.
168 : // See bug 1134945.
169 : /* virtual */ void
170 0 : nsRubyBaseContainerFrame::AddInlineMinISize(
171 : gfxContext *aRenderingContext, nsIFrame::InlineMinISizeData *aData)
172 : {
173 0 : AutoRubyTextContainerArray textContainers(this);
174 :
175 0 : for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
176 0 : if (textContainers[i]->IsSpanContainer()) {
177 : // Since spans are not breakable internally, use our pref isize
178 : // directly if there is any span.
179 0 : nsIFrame::InlinePrefISizeData data;
180 0 : data.SetLineContainer(aData->LineContainer());
181 0 : data.mSkipWhitespace = aData->mSkipWhitespace;
182 0 : data.mTrailingWhitespace = aData->mTrailingWhitespace;
183 0 : AddInlinePrefISize(aRenderingContext, &data);
184 0 : aData->mCurrentLine += data.mCurrentLine;
185 0 : if (data.mCurrentLine > 0) {
186 0 : aData->mAtStartOfLine = false;
187 : }
188 0 : aData->mSkipWhitespace = data.mSkipWhitespace;
189 0 : aData->mTrailingWhitespace = data.mTrailingWhitespace;
190 0 : return;
191 : }
192 : }
193 :
194 0 : bool firstFrame = true;
195 : bool allowInitialLineBreak, allowLineBreak;
196 0 : GetIsLineBreakAllowed(this, !aData->mAtStartOfLine,
197 0 : &allowInitialLineBreak, &allowLineBreak);
198 0 : for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
199 : RubyColumnEnumerator enumerator(
200 0 : static_cast<nsRubyBaseContainerFrame*>(frame), textContainers);
201 0 : for (; !enumerator.AtEnd(); enumerator.Next()) {
202 0 : if (firstFrame ? allowInitialLineBreak : allowLineBreak) {
203 0 : nsIFrame* baseFrame = enumerator.GetFrameAtLevel(0);
204 0 : if (baseFrame) {
205 : gfxBreakPriority breakPriority =
206 0 : LineBreakBefore(baseFrame, aRenderingContext->GetDrawTarget(),
207 0 : nullptr, nullptr);
208 0 : if (breakPriority != gfxBreakPriority::eNoBreak) {
209 0 : aData->OptionallyBreak();
210 : }
211 : }
212 : }
213 0 : firstFrame = false;
214 : nscoord isize = CalculateColumnPrefISize(aRenderingContext,
215 0 : enumerator, aData);
216 0 : aData->mCurrentLine += isize;
217 0 : if (isize > 0) {
218 0 : aData->mAtStartOfLine = false;
219 : }
220 : }
221 : }
222 : }
223 :
224 : /* virtual */ void
225 0 : nsRubyBaseContainerFrame::AddInlinePrefISize(
226 : gfxContext *aRenderingContext, nsIFrame::InlinePrefISizeData *aData)
227 : {
228 0 : AutoRubyTextContainerArray textContainers(this);
229 :
230 0 : nscoord sum = 0;
231 0 : for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
232 : RubyColumnEnumerator enumerator(
233 0 : static_cast<nsRubyBaseContainerFrame*>(frame), textContainers);
234 0 : for (; !enumerator.AtEnd(); enumerator.Next()) {
235 0 : sum += CalculateColumnPrefISize(aRenderingContext, enumerator, aData);
236 : }
237 : }
238 0 : for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
239 0 : if (textContainers[i]->IsSpanContainer()) {
240 0 : nsIFrame* frame = textContainers[i]->PrincipalChildList().FirstChild();
241 0 : nsIFrame::InlinePrefISizeData data;
242 0 : frame->AddInlinePrefISize(aRenderingContext, &data);
243 0 : MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines");
244 0 : sum = std::max(sum, data.mCurrentLine);
245 : }
246 : }
247 0 : aData->mCurrentLine += sum;
248 0 : }
249 :
250 : /* virtual */ bool
251 0 : nsRubyBaseContainerFrame::IsFrameOfType(uint32_t aFlags) const
252 : {
253 0 : if (aFlags & eSupportsCSSTransforms) {
254 0 : return false;
255 : }
256 0 : return nsContainerFrame::IsFrameOfType(aFlags &
257 0 : ~(nsIFrame::eLineParticipant));
258 : }
259 :
260 : /* virtual */ bool
261 0 : nsRubyBaseContainerFrame::CanContinueTextRun() const
262 : {
263 0 : return true;
264 : }
265 :
266 : /* virtual */ LogicalSize
267 0 : nsRubyBaseContainerFrame::ComputeSize(gfxContext *aRenderingContext,
268 : WritingMode aWM,
269 : const LogicalSize& aCBSize,
270 : nscoord aAvailableISize,
271 : const LogicalSize& aMargin,
272 : const LogicalSize& aBorder,
273 : const LogicalSize& aPadding,
274 : ComputeSizeFlags aFlags)
275 : {
276 : // Ruby base container frame is inline,
277 : // hence don't compute size before reflow.
278 0 : return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
279 : }
280 :
281 : /* virtual */ nscoord
282 0 : nsRubyBaseContainerFrame::GetLogicalBaseline(WritingMode aWritingMode) const
283 : {
284 0 : return mBaseline;
285 : }
286 :
287 : struct nsRubyBaseContainerFrame::RubyReflowInput
288 : {
289 : bool mAllowInitialLineBreak;
290 : bool mAllowLineBreak;
291 : const AutoRubyTextContainerArray& mTextContainers;
292 : const ReflowInput& mBaseReflowInput;
293 : const nsTArray<UniquePtr<ReflowInput>>& mTextReflowInputs;
294 : };
295 :
296 : /* virtual */ void
297 0 : nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
298 : ReflowOutput& aDesiredSize,
299 : const ReflowInput& aReflowInput,
300 : nsReflowStatus& aStatus)
301 : {
302 0 : MarkInReflow();
303 0 : DO_GLOBAL_REFLOW_COUNT("nsRubyBaseContainerFrame");
304 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
305 0 : aStatus.Reset();
306 :
307 0 : if (!aReflowInput.mLineLayout) {
308 0 : NS_ASSERTION(
309 : aReflowInput.mLineLayout,
310 : "No line layout provided to RubyBaseContainerFrame reflow method.");
311 0 : return;
312 : }
313 :
314 0 : mDescendantLeadings.Reset();
315 :
316 0 : MoveOverflowToChildList();
317 : // Ask text containers to drain overflows
318 0 : AutoRubyTextContainerArray textContainers(this);
319 0 : const uint32_t rtcCount = textContainers.Length();
320 0 : for (uint32_t i = 0; i < rtcCount; i++) {
321 0 : textContainers[i]->MoveOverflowToChildList();
322 : }
323 :
324 0 : WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
325 : LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
326 0 : aReflowInput.AvailableBSize());
327 :
328 : // We have a reflow state and a line layout for each RTC.
329 : // They are conceptually the state of the RTCs, but we don't actually
330 : // reflow those RTCs in this code. These two arrays are holders of
331 : // the reflow states and line layouts.
332 : // Since there are pointers refer to reflow states and line layouts,
333 : // it is necessary to guarantee that they won't be moved. For this
334 : // reason, they are wrapped in UniquePtr here.
335 0 : AutoTArray<UniquePtr<ReflowInput>, RTC_ARRAY_SIZE> reflowInputs;
336 0 : AutoTArray<UniquePtr<nsLineLayout>, RTC_ARRAY_SIZE> lineLayouts;
337 0 : reflowInputs.SetCapacity(rtcCount);
338 0 : lineLayouts.SetCapacity(rtcCount);
339 :
340 : // Begin the line layout for each ruby text container in advance.
341 0 : bool hasSpan = false;
342 0 : for (uint32_t i = 0; i < rtcCount; i++) {
343 0 : nsRubyTextContainerFrame* textContainer = textContainers[i];
344 0 : if (textContainer->IsSpanContainer()) {
345 0 : hasSpan = true;
346 : }
347 :
348 : ReflowInput* reflowInput = new ReflowInput(
349 0 : aPresContext, *aReflowInput.mParentReflowInput, textContainer,
350 0 : availSize.ConvertTo(textContainer->GetWritingMode(), lineWM));
351 0 : reflowInputs.AppendElement(reflowInput);
352 : nsLineLayout* lineLayout = new nsLineLayout(aPresContext,
353 : reflowInput->mFloatManager,
354 : reflowInput, nullptr,
355 0 : aReflowInput.mLineLayout);
356 0 : lineLayout->SetSuppressLineWrap(true);
357 0 : lineLayouts.AppendElement(lineLayout);
358 :
359 : // Line number is useless for ruby text
360 : // XXX nullptr here may cause problem, see comments for
361 : // nsLineLayout::mBlockRI and nsLineLayout::AddFloat
362 0 : lineLayout->Init(nullptr, reflowInput->CalcLineHeight(), -1);
363 0 : reflowInput->mLineLayout = lineLayout;
364 :
365 : // Border and padding are suppressed on ruby text containers.
366 : // If the writing mode is vertical-rl, the horizontal position of
367 : // rt frames will be updated when reflowing this text container,
368 : // hence leave container size 0 here for now.
369 0 : lineLayout->BeginLineReflow(0, 0, reflowInput->ComputedISize(),
370 : NS_UNCONSTRAINEDSIZE,
371 0 : false, false, lineWM, nsSize(0, 0));
372 0 : lineLayout->AttachRootFrameToBaseLineLayout();
373 : }
374 :
375 0 : aReflowInput.mLineLayout->BeginSpan(this, &aReflowInput,
376 : 0, aReflowInput.AvailableISize(),
377 0 : &mBaseline);
378 :
379 : bool allowInitialLineBreak, allowLineBreak;
380 0 : GetIsLineBreakAllowed(this, aReflowInput.mLineLayout->LineIsBreakable(),
381 0 : &allowInitialLineBreak, &allowLineBreak);
382 :
383 0 : nscoord isize = 0;
384 : // Reflow columns excluding any span
385 : RubyReflowInput reflowInput = {
386 0 : allowInitialLineBreak, allowLineBreak && !hasSpan,
387 : textContainers, aReflowInput, reflowInputs
388 0 : };
389 0 : isize = ReflowColumns(reflowInput, aStatus);
390 0 : DebugOnly<nscoord> lineSpanSize = aReflowInput.mLineLayout->EndSpan(this);
391 0 : aDesiredSize.ISize(lineWM) = isize;
392 : // When there are no frames inside the ruby base container, EndSpan
393 : // will return 0. However, in this case, the actual width of the
394 : // container could be non-zero because of non-empty ruby annotations.
395 : // XXX When bug 765861 gets fixed, this warning should be upgraded.
396 0 : NS_WARNING_ASSERTION(
397 : aStatus.IsInlineBreak() || isize == lineSpanSize || mFrames.IsEmpty(),
398 : "bad isize");
399 :
400 : // If there exists any span, the columns must either be completely
401 : // reflowed, or be not reflowed at all.
402 0 : MOZ_ASSERT(aStatus.IsInlineBreakBefore() ||
403 : aStatus.IsComplete() || !hasSpan);
404 0 : if (!aStatus.IsInlineBreakBefore() &&
405 0 : aStatus.IsComplete() && hasSpan) {
406 : // Reflow spans
407 : RubyReflowInput reflowInput = {
408 : false, false, textContainers, aReflowInput, reflowInputs
409 0 : };
410 0 : nscoord spanISize = ReflowSpans(reflowInput);
411 0 : isize = std::max(isize, spanISize);
412 : }
413 :
414 0 : for (uint32_t i = 0; i < rtcCount; i++) {
415 : // It happens before the ruby text container is reflowed, and that
416 : // when it is reflowed, it will just use this size.
417 0 : nsRubyTextContainerFrame* textContainer = textContainers[i];
418 0 : nsLineLayout* lineLayout = lineLayouts[i].get();
419 :
420 0 : RubyUtils::ClearReservedISize(textContainer);
421 0 : nscoord rtcISize = lineLayout->GetCurrentICoord();
422 : // Only span containers and containers with collapsed annotations
423 : // need reserving isize. For normal ruby text containers, their
424 : // children will be expanded properly. We only need to expand their
425 : // own size.
426 0 : if (!textContainer->IsSpanContainer()) {
427 0 : rtcISize = isize;
428 0 : } else if (isize > rtcISize) {
429 0 : RubyUtils::SetReservedISize(textContainer, isize - rtcISize);
430 : }
431 :
432 0 : lineLayout->VerticalAlignLine();
433 0 : textContainer->SetISize(rtcISize);
434 0 : lineLayout->EndLineReflow();
435 : }
436 :
437 : // Border and padding are suppressed on ruby base container,
438 : // create a fake borderPadding for setting BSize.
439 0 : WritingMode frameWM = aReflowInput.GetWritingMode();
440 0 : LogicalMargin borderPadding(frameWM);
441 0 : nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize,
442 0 : borderPadding, lineWM, frameWM);
443 : }
444 :
445 : /**
446 : * This struct stores the continuations after this frame and
447 : * corresponding text containers. It is used to speed up looking
448 : * ahead for nonempty continuations.
449 : */
450 0 : struct MOZ_STACK_CLASS nsRubyBaseContainerFrame::PullFrameState
451 : {
452 : ContinuationTraversingState mBase;
453 : AutoTArray<ContinuationTraversingState, RTC_ARRAY_SIZE> mTexts;
454 : const AutoRubyTextContainerArray& mTextContainers;
455 :
456 : PullFrameState(nsRubyBaseContainerFrame* aBaseContainer,
457 : const AutoRubyTextContainerArray& aTextContainers);
458 : };
459 :
460 : nscoord
461 0 : nsRubyBaseContainerFrame::ReflowColumns(const RubyReflowInput& aReflowInput,
462 : nsReflowStatus& aStatus)
463 : {
464 0 : nsLineLayout* lineLayout = aReflowInput.mBaseReflowInput.mLineLayout;
465 0 : const uint32_t rtcCount = aReflowInput.mTextContainers.Length();
466 0 : nscoord icoord = lineLayout->GetCurrentICoord();
467 0 : MOZ_ASSERT(icoord == 0, "border/padding of rbc should have been suppressed");
468 0 : nsReflowStatus reflowStatus;
469 0 : aStatus.Reset();
470 :
471 0 : uint32_t columnIndex = 0;
472 0 : RubyColumn column;
473 0 : column.mTextFrames.SetCapacity(rtcCount);
474 0 : RubyColumnEnumerator e(this, aReflowInput.mTextContainers);
475 0 : for (; !e.AtEnd(); e.Next()) {
476 0 : e.GetColumn(column);
477 0 : icoord += ReflowOneColumn(aReflowInput, columnIndex, column, reflowStatus);
478 0 : if (!reflowStatus.IsInlineBreakBefore()) {
479 0 : columnIndex++;
480 : }
481 0 : if (reflowStatus.IsInlineBreak()) {
482 0 : break;
483 : }
484 : // We are not handling overflow here.
485 0 : MOZ_ASSERT(reflowStatus.IsEmpty());
486 : }
487 :
488 0 : bool isComplete = false;
489 0 : PullFrameState pullFrameState(this, aReflowInput.mTextContainers);
490 0 : while (!reflowStatus.IsInlineBreak()) {
491 : // We are not handling overflow here.
492 0 : MOZ_ASSERT(reflowStatus.IsEmpty());
493 :
494 : // Try pull some frames from next continuations. This call replaces
495 : // frames in |column| with the frame pulled in each level.
496 0 : PullOneColumn(lineLayout, pullFrameState, column, isComplete);
497 0 : if (isComplete) {
498 : // No more frames can be pulled.
499 0 : break;
500 : }
501 0 : icoord += ReflowOneColumn(aReflowInput, columnIndex, column, reflowStatus);
502 0 : if (!reflowStatus.IsInlineBreakBefore()) {
503 0 : columnIndex++;
504 : }
505 : }
506 :
507 0 : if (!e.AtEnd() && reflowStatus.IsInlineBreakAfter()) {
508 : // The current column has been successfully placed.
509 : // Skip to the next column and mark break before.
510 0 : e.Next();
511 0 : e.GetColumn(column);
512 0 : reflowStatus.SetInlineLineBreakBeforeAndReset();
513 : }
514 0 : if (!e.AtEnd() || (GetNextInFlow() && !isComplete)) {
515 0 : aStatus.SetIncomplete();
516 : }
517 :
518 0 : if (reflowStatus.IsInlineBreakBefore()) {
519 0 : if (!columnIndex || !aReflowInput.mAllowLineBreak) {
520 : // If no column has been placed yet, or we have any span,
521 : // the whole container should be in the next line.
522 0 : aStatus.SetInlineLineBreakBeforeAndReset();
523 0 : return 0;
524 : }
525 0 : aStatus.SetInlineLineBreakAfter();
526 0 : MOZ_ASSERT(aStatus.IsComplete() || aReflowInput.mAllowLineBreak);
527 :
528 : // If we are on an intra-level whitespace column, null values in
529 : // column.mBaseFrame and column.mTextFrames don't represent the
530 : // end of the frame-sibling-chain at that level -- instead, they
531 : // represent an anonymous empty intra-level whitespace box. It is
532 : // likely that there are frames in the next column (which can't be
533 : // intra-level whitespace). Those frames should be pushed as well.
534 0 : Maybe<RubyColumn> nextColumn;
535 0 : if (column.mIsIntraLevelWhitespace && !e.AtEnd()) {
536 0 : e.Next();
537 0 : nextColumn.emplace();
538 0 : e.GetColumn(nextColumn.ref());
539 : }
540 0 : nsIFrame* baseFrame = column.mBaseFrame;
541 0 : if (!baseFrame & nextColumn.isSome()) {
542 0 : baseFrame = nextColumn->mBaseFrame;
543 : }
544 0 : if (baseFrame) {
545 0 : PushChildren(baseFrame, baseFrame->GetPrevSibling());
546 : }
547 0 : for (uint32_t i = 0; i < rtcCount; i++) {
548 0 : nsRubyTextFrame* textFrame = column.mTextFrames[i];
549 0 : if (!textFrame && nextColumn.isSome()) {
550 0 : textFrame = nextColumn->mTextFrames[i];
551 : }
552 0 : if (textFrame) {
553 0 : aReflowInput.mTextContainers[i]->PushChildren(
554 0 : textFrame, textFrame->GetPrevSibling());
555 : }
556 : }
557 0 : } else if (reflowStatus.IsInlineBreakAfter()) {
558 : // |reflowStatus| being break after here may only happen when
559 : // there is a break after the column just pulled, or the whole
560 : // segment has been completely reflowed. In those cases, we do
561 : // not need to push anything.
562 0 : MOZ_ASSERT(e.AtEnd());
563 0 : aStatus.SetInlineLineBreakAfter();
564 : }
565 :
566 0 : return icoord;
567 : }
568 :
569 : nscoord
570 0 : nsRubyBaseContainerFrame::ReflowOneColumn(const RubyReflowInput& aReflowInput,
571 : uint32_t aColumnIndex,
572 : const RubyColumn& aColumn,
573 : nsReflowStatus& aStatus)
574 : {
575 0 : const ReflowInput& baseReflowInput = aReflowInput.mBaseReflowInput;
576 0 : const auto& textReflowInputs = aReflowInput.mTextReflowInputs;
577 0 : nscoord istart = baseReflowInput.mLineLayout->GetCurrentICoord();
578 :
579 0 : if (aColumn.mBaseFrame) {
580 0 : bool allowBreakBefore = aColumnIndex ?
581 0 : aReflowInput.mAllowLineBreak : aReflowInput.mAllowInitialLineBreak;
582 0 : if (allowBreakBefore) {
583 0 : gfxBreakPriority breakPriority = LineBreakBefore(
584 0 : aColumn.mBaseFrame, baseReflowInput.mRenderingContext->GetDrawTarget(),
585 0 : baseReflowInput.mLineLayout->LineContainerFrame(),
586 0 : baseReflowInput.mLineLayout->GetLine());
587 0 : if (breakPriority != gfxBreakPriority::eNoBreak) {
588 : gfxBreakPriority lastBreakPriority =
589 0 : baseReflowInput.mLineLayout->LastOptionalBreakPriority();
590 0 : if (breakPriority >= lastBreakPriority) {
591 : // Either we have been overflow, or we are forced
592 : // to break here, do break before.
593 0 : if (istart > baseReflowInput.AvailableISize() ||
594 0 : baseReflowInput.mLineLayout->NotifyOptionalBreakPosition(
595 0 : aColumn.mBaseFrame, 0, true, breakPriority)) {
596 0 : aStatus.SetInlineLineBreakBeforeAndReset();
597 0 : return 0;
598 : }
599 : }
600 : }
601 : }
602 : }
603 :
604 0 : const uint32_t rtcCount = aReflowInput.mTextContainers.Length();
605 0 : MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount);
606 0 : MOZ_ASSERT(textReflowInputs.Length() == rtcCount);
607 0 : nscoord columnISize = 0;
608 :
609 0 : nsAutoString baseText;
610 0 : if (aColumn.mBaseFrame) {
611 0 : nsLayoutUtils::GetFrameTextContent(aColumn.mBaseFrame, baseText);
612 : }
613 :
614 : // Reflow text frames
615 0 : for (uint32_t i = 0; i < rtcCount; i++) {
616 0 : nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
617 0 : if (textFrame) {
618 0 : nsAutoString annotationText;
619 0 : nsLayoutUtils::GetFrameTextContent(textFrame, annotationText);
620 :
621 : // Per CSS Ruby spec, the content comparison for auto-hiding
622 : // takes place prior to white spaces collapsing (white-space)
623 : // and text transformation (text-transform), and ignores elements
624 : // (considers only the textContent of the boxes). Which means
625 : // using the content tree text comparison is correct.
626 0 : if (annotationText.Equals(baseText)) {
627 0 : textFrame->AddStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE);
628 : } else {
629 0 : textFrame->RemoveStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE);
630 : }
631 0 : RubyUtils::ClearReservedISize(textFrame);
632 :
633 : bool pushedFrame;
634 0 : nsReflowStatus reflowStatus;
635 0 : nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout;
636 0 : nscoord textIStart = lineLayout->GetCurrentICoord();
637 0 : lineLayout->ReflowFrame(textFrame, reflowStatus, nullptr, pushedFrame);
638 0 : if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) {
639 0 : MOZ_ASSERT_UNREACHABLE(
640 : "Any line break inside ruby box should have been suppressed");
641 : // For safety, always drain the overflow list, so that
642 : // no frames are left there after reflow.
643 : textFrame->DrainSelfOverflowList();
644 : }
645 0 : nscoord textISize = lineLayout->GetCurrentICoord() - textIStart;
646 0 : columnISize = std::max(columnISize, textISize);
647 : }
648 : }
649 :
650 : // Reflow the base frame
651 0 : if (aColumn.mBaseFrame) {
652 0 : RubyUtils::ClearReservedISize(aColumn.mBaseFrame);
653 :
654 : bool pushedFrame;
655 0 : nsReflowStatus reflowStatus;
656 0 : nsLineLayout* lineLayout = baseReflowInput.mLineLayout;
657 0 : nscoord baseIStart = lineLayout->GetCurrentICoord();
658 0 : lineLayout->ReflowFrame(aColumn.mBaseFrame, reflowStatus,
659 0 : nullptr, pushedFrame);
660 0 : if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) {
661 0 : MOZ_ASSERT_UNREACHABLE(
662 : "Any line break inside ruby box should have been suppressed");
663 : // For safety, always drain the overflow list, so that
664 : // no frames are left there after reflow.
665 : aColumn.mBaseFrame->DrainSelfOverflowList();
666 : }
667 0 : nscoord baseISize = lineLayout->GetCurrentICoord() - baseIStart;
668 0 : columnISize = std::max(columnISize, baseISize);
669 : }
670 :
671 : // Align all the line layout to the new coordinate.
672 0 : nscoord icoord = istart + columnISize;
673 0 : nscoord deltaISize = icoord - baseReflowInput.mLineLayout->GetCurrentICoord();
674 0 : if (deltaISize > 0) {
675 0 : baseReflowInput.mLineLayout->AdvanceICoord(deltaISize);
676 0 : if (aColumn.mBaseFrame) {
677 0 : RubyUtils::SetReservedISize(aColumn.mBaseFrame, deltaISize);
678 : }
679 : }
680 0 : for (uint32_t i = 0; i < rtcCount; i++) {
681 0 : if (aReflowInput.mTextContainers[i]->IsSpanContainer()) {
682 0 : continue;
683 : }
684 0 : nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout;
685 0 : nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
686 0 : nscoord deltaISize = icoord - lineLayout->GetCurrentICoord();
687 0 : if (deltaISize > 0) {
688 0 : lineLayout->AdvanceICoord(deltaISize);
689 0 : if (textFrame && !textFrame->IsAutoHidden()) {
690 0 : RubyUtils::SetReservedISize(textFrame, deltaISize);
691 : }
692 : }
693 0 : if (aColumn.mBaseFrame && textFrame) {
694 0 : lineLayout->AttachLastFrameToBaseLineLayout();
695 : }
696 : }
697 :
698 0 : return columnISize;
699 : }
700 :
701 0 : nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
702 : nsRubyBaseContainerFrame* aBaseContainer,
703 0 : const AutoRubyTextContainerArray& aTextContainers)
704 : : mBase(aBaseContainer)
705 0 : , mTextContainers(aTextContainers)
706 : {
707 0 : const uint32_t rtcCount = aTextContainers.Length();
708 0 : for (uint32_t i = 0; i < rtcCount; i++) {
709 0 : mTexts.AppendElement(aTextContainers[i]);
710 : }
711 0 : }
712 :
713 : void
714 0 : nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout* aLineLayout,
715 : PullFrameState& aPullFrameState,
716 : RubyColumn& aColumn,
717 : bool& aIsComplete)
718 : {
719 : const AutoRubyTextContainerArray& textContainers =
720 0 : aPullFrameState.mTextContainers;
721 0 : const uint32_t rtcCount = textContainers.Length();
722 :
723 0 : nsIFrame* nextBase = GetNextInFlowChild(aPullFrameState.mBase);
724 0 : MOZ_ASSERT(!nextBase || nextBase->IsRubyBaseFrame());
725 0 : aColumn.mBaseFrame = static_cast<nsRubyBaseFrame*>(nextBase);
726 0 : bool foundFrame = !!aColumn.mBaseFrame;
727 : bool pullingIntraLevelWhitespace =
728 0 : aColumn.mBaseFrame && aColumn.mBaseFrame->IsIntraLevelWhitespace();
729 :
730 0 : aColumn.mTextFrames.ClearAndRetainStorage();
731 0 : for (uint32_t i = 0; i < rtcCount; i++) {
732 : nsIFrame* nextText =
733 0 : textContainers[i]->GetNextInFlowChild(aPullFrameState.mTexts[i]);
734 0 : MOZ_ASSERT(!nextText || nextText->IsRubyTextFrame());
735 0 : nsRubyTextFrame* textFrame = static_cast<nsRubyTextFrame*>(nextText);
736 0 : aColumn.mTextFrames.AppendElement(textFrame);
737 0 : foundFrame = foundFrame || nextText;
738 0 : if (nextText && !pullingIntraLevelWhitespace) {
739 0 : pullingIntraLevelWhitespace = textFrame->IsIntraLevelWhitespace();
740 : }
741 : }
742 : // If there exists any frame in continations, we haven't
743 : // completed the reflow process.
744 0 : aIsComplete = !foundFrame;
745 0 : if (!foundFrame) {
746 0 : return;
747 : }
748 :
749 0 : aColumn.mIsIntraLevelWhitespace = pullingIntraLevelWhitespace;
750 0 : if (pullingIntraLevelWhitespace) {
751 : // We are pulling an intra-level whitespace. Drop all frames which
752 : // are not part of this intra-level whitespace column. (Those frames
753 : // are really part of the *next* column, after the pulled one.)
754 0 : if (aColumn.mBaseFrame && !aColumn.mBaseFrame->IsIntraLevelWhitespace()) {
755 0 : aColumn.mBaseFrame = nullptr;
756 : }
757 0 : for (uint32_t i = 0; i < rtcCount; i++) {
758 0 : nsRubyTextFrame*& textFrame = aColumn.mTextFrames[i];
759 0 : if (textFrame && !textFrame->IsIntraLevelWhitespace()) {
760 0 : textFrame = nullptr;
761 : }
762 : }
763 : } else {
764 : // We are not pulling an intra-level whitespace, which means all
765 : // elements we are going to pull can have non-whitespace content,
766 : // which may contain float which we need to reparent.
767 0 : MOZ_ASSERT(aColumn.begin() != aColumn.end(),
768 : "Ruby column shouldn't be empty");
769 : nsBlockFrame* oldFloatCB =
770 0 : nsLayoutUtils::GetFloatContainingBlock(*aColumn.begin());
771 : #ifdef DEBUG
772 0 : MOZ_ASSERT(oldFloatCB, "Must have found a float containing block");
773 0 : for (nsIFrame* frame : aColumn) {
774 0 : MOZ_ASSERT(nsLayoutUtils::GetFloatContainingBlock(frame) == oldFloatCB,
775 : "All frames in the same ruby column should share "
776 : "the same old float containing block");
777 : }
778 : #endif
779 : nsBlockFrame* newFloatCB =
780 0 : nsLayoutUtils::GetAsBlock(aLineLayout->LineContainerFrame());
781 0 : MOZ_ASSERT(newFloatCB, "Must have a float containing block");
782 0 : if (oldFloatCB != newFloatCB) {
783 0 : for (nsIFrame* frame : aColumn) {
784 0 : newFloatCB->ReparentFloats(frame, oldFloatCB, false);
785 : }
786 : }
787 : }
788 :
789 : // Pull the frames of this column.
790 0 : if (aColumn.mBaseFrame) {
791 0 : DebugOnly<nsIFrame*> pulled = PullNextInFlowChild(aPullFrameState.mBase);
792 0 : MOZ_ASSERT(pulled == aColumn.mBaseFrame, "pulled a wrong frame?");
793 : }
794 0 : for (uint32_t i = 0; i < rtcCount; i++) {
795 0 : if (aColumn.mTextFrames[i]) {
796 : DebugOnly<nsIFrame*> pulled =
797 0 : textContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]);
798 0 : MOZ_ASSERT(pulled == aColumn.mTextFrames[i], "pulled a wrong frame?");
799 : }
800 : }
801 :
802 0 : if (!aIsComplete) {
803 : // We pulled frames from the next line, hence mark it dirty.
804 0 : aLineLayout->SetDirtyNextLine();
805 : }
806 : }
807 :
808 : nscoord
809 0 : nsRubyBaseContainerFrame::ReflowSpans(const RubyReflowInput& aReflowInput)
810 : {
811 0 : nscoord spanISize = 0;
812 0 : for (uint32_t i = 0, iend = aReflowInput.mTextContainers.Length();
813 0 : i < iend; i++) {
814 0 : nsRubyTextContainerFrame* container = aReflowInput.mTextContainers[i];
815 0 : if (!container->IsSpanContainer()) {
816 0 : continue;
817 : }
818 :
819 0 : nsIFrame* rtFrame = container->PrincipalChildList().FirstChild();
820 0 : nsReflowStatus reflowStatus;
821 : bool pushedFrame;
822 0 : nsLineLayout* lineLayout = aReflowInput.mTextReflowInputs[i]->mLineLayout;
823 0 : MOZ_ASSERT(lineLayout->GetCurrentICoord() == 0,
824 : "border/padding of rtc should have been suppressed");
825 0 : lineLayout->ReflowFrame(rtFrame, reflowStatus, nullptr, pushedFrame);
826 0 : MOZ_ASSERT(!reflowStatus.IsInlineBreak() && !pushedFrame,
827 : "Any line break inside ruby box should has been suppressed");
828 0 : spanISize = std::max(spanISize, lineLayout->GetCurrentICoord());
829 : }
830 0 : return spanISize;
831 : }
|