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