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