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 #ifdef DEBUG_FRAME_DUMP
48 nsresult
nsRubyFrame::GetFrameName(nsAString
& aResult
) const {
49 return MakeFrameName(u
"Ruby"_ns
, aResult
);
54 void nsRubyFrame::AddInlineMinISize(gfxContext
* aRenderingContext
,
55 nsIFrame::InlineMinISizeData
* aData
) {
56 auto handleChildren
= [aRenderingContext
](auto frame
, auto data
) {
57 for (RubySegmentEnumerator
e(static_cast<nsRubyFrame
*>(frame
)); !e
.AtEnd();
59 e
.GetBaseContainer()->AddInlineMinISize(aRenderingContext
, data
);
62 DoInlineIntrinsicISize(aData
, handleChildren
);
66 void nsRubyFrame::AddInlinePrefISize(gfxContext
* aRenderingContext
,
67 nsIFrame::InlinePrefISizeData
* aData
) {
68 auto handleChildren
= [aRenderingContext
](auto frame
, auto data
) {
69 for (RubySegmentEnumerator
e(static_cast<nsRubyFrame
*>(frame
)); !e
.AtEnd();
71 e
.GetBaseContainer()->AddInlinePrefISize(aRenderingContext
, data
);
74 DoInlineIntrinsicISize(aData
, handleChildren
);
75 aData
->mLineIsEmpty
= false;
78 static nsRubyBaseContainerFrame
* FindRubyBaseContainerAncestor(
80 for (nsIFrame
* ancestor
= aFrame
->GetParent();
81 ancestor
&& ancestor
->IsLineParticipant();
82 ancestor
= ancestor
->GetParent()) {
83 if (ancestor
->IsRubyBaseContainerFrame()) {
84 return static_cast<nsRubyBaseContainerFrame
*>(ancestor
);
91 void nsRubyFrame::Reflow(nsPresContext
* aPresContext
,
92 ReflowOutput
& aDesiredSize
,
93 const ReflowInput
& aReflowInput
,
94 nsReflowStatus
& aStatus
) {
96 DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
97 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aDesiredSize
, aStatus
);
98 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
100 if (!aReflowInput
.mLineLayout
) {
101 NS_ASSERTION(aReflowInput
.mLineLayout
,
102 "No line layout provided to RubyFrame reflow method.");
106 // Grab overflow frames from prev-in-flow and its own.
107 MoveInlineOverflowToChildList(aReflowInput
.mLineLayout
->LineContainerFrame());
112 // Begin the span for the ruby frame
113 WritingMode frameWM
= aReflowInput
.GetWritingMode();
114 WritingMode lineWM
= aReflowInput
.mLineLayout
->GetWritingMode();
115 LogicalMargin borderPadding
=
116 aReflowInput
.ComputedLogicalBorderPadding(frameWM
);
117 nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize
, borderPadding
,
120 nscoord startEdge
= 0;
121 const bool boxDecorationBreakClone
=
122 StyleBorder()->mBoxDecorationBreak
== StyleBoxDecorationBreak::Clone
;
123 if (boxDecorationBreakClone
|| !GetPrevContinuation()) {
124 startEdge
= borderPadding
.IStart(frameWM
);
126 NS_ASSERTION(aReflowInput
.AvailableISize() != NS_UNCONSTRAINEDSIZE
,
127 "should no longer use available widths");
128 nscoord endEdge
= aReflowInput
.AvailableISize() - borderPadding
.IEnd(frameWM
);
129 aReflowInput
.mLineLayout
->BeginSpan(this, &aReflowInput
, startEdge
, endEdge
,
132 for (RubySegmentEnumerator
e(this); !e
.AtEnd(); e
.Next()) {
133 ReflowSegment(aPresContext
, aReflowInput
, aDesiredSize
.BlockStartAscent(),
134 aDesiredSize
.BSize(lineWM
), e
.GetBaseContainer(), aStatus
);
136 if (aStatus
.IsInlineBreak()) {
137 // A break occurs when reflowing the segment.
138 // Don't continue reflowing more segments.
143 ContinuationTraversingState
pullState(this);
144 while (aStatus
.IsEmpty()) {
145 nsRubyBaseContainerFrame
* baseContainer
=
146 PullOneSegment(aReflowInput
.mLineLayout
, pullState
);
147 if (!baseContainer
) {
148 // No more continuations after, finish now.
151 ReflowSegment(aPresContext
, aReflowInput
, aDesiredSize
.BlockStartAscent(),
152 aDesiredSize
.BSize(lineWM
), baseContainer
, aStatus
);
154 // We never handle overflow in ruby.
155 MOZ_ASSERT(!aStatus
.IsOverflowIncomplete());
157 aDesiredSize
.ISize(lineWM
) = aReflowInput
.mLineLayout
->EndSpan(this);
158 if (boxDecorationBreakClone
|| !GetPrevContinuation()) {
159 aDesiredSize
.ISize(lineWM
) += borderPadding
.IStart(frameWM
);
161 if (boxDecorationBreakClone
|| aStatus
.IsComplete()) {
162 aDesiredSize
.ISize(lineWM
) += borderPadding
.IEnd(frameWM
);
165 // Update descendant leadings of ancestor ruby base container.
166 if (nsRubyBaseContainerFrame
* rbc
= FindRubyBaseContainerAncestor(this)) {
167 rbc
->UpdateDescendantLeadings(mLeadings
);
170 ReflowAbsoluteFrames(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
173 void nsRubyFrame::ReflowSegment(nsPresContext
* aPresContext
,
174 const ReflowInput
& aReflowInput
,
175 nscoord aBlockStartAscent
, nscoord aBlockSize
,
176 nsRubyBaseContainerFrame
* aBaseContainer
,
177 nsReflowStatus
& aStatus
) {
178 WritingMode lineWM
= aReflowInput
.mLineLayout
->GetWritingMode();
179 LogicalSize
availSize(lineWM
, aReflowInput
.AvailableISize(),
180 aReflowInput
.AvailableBSize());
181 NS_ASSERTION(!GetWritingMode().IsOrthogonalTo(lineWM
),
182 "Ruby frame writing-mode shouldn't be orthogonal to its line");
184 AutoRubyTextContainerArray
textContainers(aBaseContainer
);
185 const uint32_t rtcCount
= textContainers
.Length();
187 ReflowOutput
baseMetrics(aReflowInput
);
189 aReflowInput
.mLineLayout
->ReflowFrame(aBaseContainer
, aStatus
, &baseMetrics
,
192 if (aStatus
.IsInlineBreakBefore()) {
193 if (aBaseContainer
!= mFrames
.FirstChild()) {
194 // Some segments may have been reflowed before, hence it is not
195 // a break-before for the ruby container.
197 aStatus
.SetInlineLineBreakAfter();
198 aStatus
.SetIncomplete();
199 PushChildrenToOverflow(aBaseContainer
, aBaseContainer
->GetPrevSibling());
200 aReflowInput
.mLineLayout
->SetDirtyNextLine();
202 // This base container is not placed at all, we can skip all
203 // text containers paired with it.
206 if (aStatus
.IsIncomplete()) {
207 // It always promise that if the status is incomplete, there is a
208 // break occurs. Break before has been processed above. However,
209 // it is possible that break after happens with the frame reflow
210 // completed. It happens if there is a force break at the end.
211 MOZ_ASSERT(aStatus
.IsInlineBreakAfter());
212 // Find the previous sibling which we will
213 // insert new continuations after.
216 lastChild
= textContainers
.LastElement();
218 lastChild
= aBaseContainer
;
221 // Create continuations for the base container
222 nsIFrame
* newBaseContainer
= CreateNextInFlow(aBaseContainer
);
223 // newBaseContainer is null if there are existing next-in-flows.
224 // We only need to move and push if there were not.
225 if (newBaseContainer
) {
226 // Move the new frame after all the text containers
227 mFrames
.RemoveFrame(newBaseContainer
);
228 mFrames
.InsertFrame(nullptr, lastChild
, newBaseContainer
);
230 // Create continuations for text containers
231 nsIFrame
* newLastChild
= newBaseContainer
;
232 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
233 nsIFrame
* newTextContainer
= CreateNextInFlow(textContainers
[i
]);
234 MOZ_ASSERT(newTextContainer
,
235 "Next-in-flow of rtc should not exist "
236 "if the corresponding rbc does not");
237 mFrames
.RemoveFrame(newTextContainer
);
238 mFrames
.InsertFrame(nullptr, newLastChild
, newTextContainer
);
239 newLastChild
= newTextContainer
;
242 if (lastChild
!= mFrames
.LastChild()) {
243 // Always push the next frame after the last child in this segment.
244 // It is possible that we pulled it back before our next-in-flow
245 // drain our overflow.
246 PushChildrenToOverflow(lastChild
->GetNextSibling(), lastChild
);
247 aReflowInput
.mLineLayout
->SetDirtyNextLine();
249 } else if (rtcCount
) {
250 DestroyContext
context(PresShell());
251 // If the ruby base container is reflowed completely, the line
252 // layout will remove the next-in-flows of that frame. But the
253 // line layout is not aware of the ruby text containers, hence
254 // it is necessary to remove them here.
255 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
256 if (nsIFrame
* nextRTC
= textContainers
[i
]->GetNextInFlow()) {
257 nextRTC
->GetParent()->DeleteNextInFlowChild(context
, nextRTC
, true);
262 nscoord segmentISize
= baseMetrics
.ISize(lineWM
);
263 const nsSize dummyContainerSize
;
264 LogicalRect baseRect
=
265 aBaseContainer
->GetLogicalRect(lineWM
, dummyContainerSize
);
266 // We need to position our rtc frames on one side or the other of the
267 // base container's rect, using a coordinate space that's relative to
268 // the ruby frame. Right now, the base container's rect's block-axis
269 // position is relative to the block container frame containing the
270 // lines, so here we reset it to the different between the ascents of
271 // the ruby container and the ruby base container, assuming they are
272 // aligned with the baseline.
273 // XXX We may need to add border/padding here. See bug 1055667.
274 baseRect
.BStart(lineWM
) = aBlockStartAscent
- baseMetrics
.BlockStartAscent();
275 // The rect for offsets of text containers.
276 LogicalRect offsetRect
= baseRect
;
277 RubyBlockLeadings descLeadings
= aBaseContainer
->GetDescendantLeadings();
278 offsetRect
.BStart(lineWM
) -= descLeadings
.mStart
;
279 offsetRect
.BSize(lineWM
) += descLeadings
.mStart
+ descLeadings
.mEnd
;
280 Maybe
<LineRelativeDir
> lastLineSide
;
281 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
282 nsRubyTextContainerFrame
* textContainer
= textContainers
[i
];
283 WritingMode rtcWM
= textContainer
->GetWritingMode();
284 nsReflowStatus textReflowStatus
;
285 ReflowOutput
textMetrics(aReflowInput
);
286 ReflowInput
textReflowInput(aPresContext
, aReflowInput
, textContainer
,
287 availSize
.ConvertTo(rtcWM
, lineWM
));
288 textContainer
->Reflow(aPresContext
, textMetrics
, textReflowInput
,
290 // Ruby text containers always return complete reflow status even when
291 // they have continuations, because the breaking has already been
292 // handled when reflowing the base containers.
293 NS_ASSERTION(textReflowStatus
.IsEmpty(),
294 "Ruby text container must not break itself inside");
295 const LogicalSize size
= textMetrics
.Size(lineWM
);
296 textContainer
->SetSize(lineWM
, size
);
298 nscoord reservedISize
= RubyUtils::GetReservedISize(textContainer
);
299 segmentISize
= std::max(segmentISize
, size
.ISize(lineWM
) + reservedISize
);
301 Maybe
<LineRelativeDir
> lineSide
;
302 switch (textContainer
->StyleText()->mRubyPosition
) {
303 case StyleRubyPosition::Over
:
304 lineSide
.emplace(eLineRelativeDirOver
);
306 case StyleRubyPosition::Under
:
307 lineSide
.emplace(eLineRelativeDirUnder
);
309 case StyleRubyPosition::AlternateOver
:
310 if (lastLineSide
.isSome() &&
311 lastLineSide
.value() == eLineRelativeDirOver
) {
312 lineSide
.emplace(eLineRelativeDirUnder
);
314 lineSide
.emplace(eLineRelativeDirOver
);
317 case StyleRubyPosition::AlternateUnder
:
318 if (lastLineSide
.isSome() &&
319 lastLineSide
.value() == eLineRelativeDirUnder
) {
320 lineSide
.emplace(eLineRelativeDirOver
);
322 lineSide
.emplace(eLineRelativeDirUnder
);
326 // XXX inter-character support in bug 1055672
327 MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
329 lastLineSide
= lineSide
;
331 LogicalPoint
position(lineWM
);
332 if (lineSide
.isSome()) {
333 LogicalSide logicalSide
=
334 lineWM
.LogicalSideForLineRelativeDir(lineSide
.value());
335 if (StaticPrefs::layout_css_ruby_intercharacter_enabled() &&
336 rtcWM
.IsVerticalRL() &&
337 lineWM
.GetInlineDir() == WritingMode::eInlineLTR
) {
338 // Inter-character ruby annotations are only supported for vertical-rl
339 // in ltr horizontal writing. Fall back to non-inter-character behavior
342 lineWM
, offsetRect
.ISize(lineWM
),
343 offsetRect
.BSize(lineWM
) > size
.BSize(lineWM
)
344 ? (offsetRect
.BSize(lineWM
) - size
.BSize(lineWM
)) / 2
346 position
= offsetRect
.Origin(lineWM
) + offset
;
347 aReflowInput
.mLineLayout
->AdvanceICoord(size
.ISize(lineWM
));
348 } else if (logicalSide
== eLogicalSideBStart
) {
349 offsetRect
.BStart(lineWM
) -= size
.BSize(lineWM
);
350 offsetRect
.BSize(lineWM
) += size
.BSize(lineWM
);
351 position
= offsetRect
.Origin(lineWM
);
352 } else if (logicalSide
== eLogicalSideBEnd
) {
353 position
= offsetRect
.Origin(lineWM
) +
354 LogicalPoint(lineWM
, 0, offsetRect
.BSize(lineWM
));
355 offsetRect
.BSize(lineWM
) += size
.BSize(lineWM
);
357 MOZ_ASSERT_UNREACHABLE("???");
360 // Using a dummy container-size here, so child positioning may not be
361 // correct. We will fix it in nsLineLayout after the whole line is
363 FinishReflowChild(textContainer
, aPresContext
, textMetrics
,
364 &textReflowInput
, lineWM
, position
, dummyContainerSize
,
365 ReflowChildFlags::Default
);
367 MOZ_ASSERT(baseRect
.ISize(lineWM
) == offsetRect
.ISize(lineWM
),
368 "Annotations should only be placed on the block directions");
370 nscoord deltaISize
= segmentISize
- baseMetrics
.ISize(lineWM
);
371 if (deltaISize
<= 0) {
372 RubyUtils::ClearReservedISize(aBaseContainer
);
374 RubyUtils::SetReservedISize(aBaseContainer
, deltaISize
);
375 aReflowInput
.mLineLayout
->AdvanceICoord(deltaISize
);
378 // Set block leadings of the base container.
379 // The leadings are the difference between the offsetRect and the rect
380 // of this ruby container, which has block start zero and block size
382 nscoord startLeading
= -offsetRect
.BStart(lineWM
);
383 nscoord endLeading
= offsetRect
.BEnd(lineWM
) - aBlockSize
;
384 // XXX When bug 765861 gets fixed, this warning should be upgraded.
385 NS_WARNING_ASSERTION(startLeading
>= 0 && endLeading
>= 0,
386 "Leadings should be non-negative (because adding "
387 "ruby annotation can only increase the size)");
388 mLeadings
.Update(startLeading
, endLeading
);
391 nsRubyBaseContainerFrame
* nsRubyFrame::PullOneSegment(
392 const nsLineLayout
* aLineLayout
, ContinuationTraversingState
& aState
) {
393 // Pull a ruby base container
394 nsIFrame
* baseFrame
= GetNextInFlowChild(aState
);
398 MOZ_ASSERT(baseFrame
->IsRubyBaseContainerFrame());
400 // Get the float containing block of the base frame before we pull it.
401 nsBlockFrame
* oldFloatCB
= nsLayoutUtils::GetFloatContainingBlock(baseFrame
);
402 PullNextInFlowChild(aState
);
404 // Pull all ruby text containers following the base container
406 while ((nextFrame
= GetNextInFlowChild(aState
)) != nullptr &&
407 nextFrame
->IsRubyTextContainerFrame()) {
408 PullNextInFlowChild(aState
);
411 if (nsBlockFrame
* newFloatCB
=
412 do_QueryFrame(aLineLayout
->LineContainerFrame())) {
413 if (oldFloatCB
&& oldFloatCB
!= newFloatCB
) {
414 newFloatCB
->ReparentFloats(baseFrame
, oldFloatCB
, true);
418 return static_cast<nsRubyBaseContainerFrame
*>(baseFrame
);