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 // ==================================
48 bool nsRubyFrame::IsFrameOfType(uint32_t aFlags
) const {
49 if (aFlags
& eBidiInlineContainer
) {
52 return nsInlineFrame::IsFrameOfType(aFlags
);
55 #ifdef DEBUG_FRAME_DUMP
56 nsresult
nsRubyFrame::GetFrameName(nsAString
& aResult
) const {
57 return MakeFrameName(u
"Ruby"_ns
, aResult
);
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();
67 e
.GetBaseContainer()->AddInlineMinISize(aRenderingContext
, data
);
70 DoInlineIntrinsicISize(aData
, handleChildren
);
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();
79 e
.GetBaseContainer()->AddInlinePrefISize(aRenderingContext
, data
);
82 DoInlineIntrinsicISize(aData
, handleChildren
);
83 aData
->mLineIsEmpty
= false;
86 static nsRubyBaseContainerFrame
* FindRubyBaseContainerAncestor(
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
);
99 void nsRubyFrame::Reflow(nsPresContext
* aPresContext
,
100 ReflowOutput
& aDesiredSize
,
101 const ReflowInput
& aReflowInput
,
102 nsReflowStatus
& aStatus
) {
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.");
114 // Grab overflow frames from prev-in-flow and its own.
115 MoveInlineOverflowToChildList(aReflowInput
.mLineLayout
->LineContainerFrame());
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
,
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
,
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.
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.
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
);
197 aReflowInput
.mLineLayout
->ReflowFrame(aBaseContainer
, aStatus
, &baseMetrics
,
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.
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.
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.
224 lastChild
= textContainers
.LastElement();
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
,
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
);
314 case StyleRubyPosition::Under
:
315 lineSide
.emplace(eLineRelativeDirUnder
);
317 case StyleRubyPosition::AlternateOver
:
318 if (lastLineSide
.isSome() &&
319 lastLineSide
.value() == eLineRelativeDirOver
) {
320 lineSide
.emplace(eLineRelativeDirUnder
);
322 lineSide
.emplace(eLineRelativeDirOver
);
325 case StyleRubyPosition::AlternateUnder
:
326 if (lastLineSide
.isSome() &&
327 lastLineSide
.value() == eLineRelativeDirUnder
) {
328 lineSide
.emplace(eLineRelativeDirOver
);
330 lineSide
.emplace(eLineRelativeDirUnder
);
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
350 lineWM
, offsetRect
.ISize(lineWM
),
351 offsetRect
.BSize(lineWM
) > size
.BSize(lineWM
)
352 ? (offsetRect
.BSize(lineWM
) - size
.BSize(lineWM
)) / 2
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
);
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
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
);
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
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
);
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
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
);