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/. */
7 /* rendering object for CSS "display: ruby" */
8 #include "nsRubyFrame.h"
9 #include "nsLineLayout.h"
10 #include "nsPresContext.h"
11 #include "nsStyleContext.h"
12 #include "WritingModes.h"
13 #include "RubyUtils.h"
14 #include "RubyReflowState.h"
15 #include "nsRubyBaseContainerFrame.h"
16 #include "nsRubyTextContainerFrame.h"
18 using namespace mozilla
;
20 //----------------------------------------------------------------------
22 // Frame class boilerplate
23 // =======================
25 NS_QUERYFRAME_HEAD(nsRubyFrame
)
26 NS_QUERYFRAME_ENTRY(nsRubyFrame
)
27 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
29 NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame
)
32 NS_NewRubyFrame(nsIPresShell
* aPresShell
,
33 nsStyleContext
* aContext
)
35 return new (aPresShell
) nsRubyFrame(aContext
);
38 //----------------------------------------------------------------------
40 // nsRubyFrame Method Implementations
41 // ==================================
44 nsRubyFrame::GetType() const
46 return nsGkAtoms::rubyFrame
;
50 nsRubyFrame::IsFrameOfType(uint32_t aFlags
) const
52 return nsContainerFrame::IsFrameOfType(aFlags
&
53 ~(nsIFrame::eLineParticipant
));
56 #ifdef DEBUG_FRAME_DUMP
58 nsRubyFrame::GetFrameName(nsAString
& aResult
) const
60 return MakeFrameName(NS_LITERAL_STRING("Ruby"), aResult
);
65 * This enumerator enumerates each segment.
67 class MOZ_STACK_CLASS SegmentEnumerator
70 explicit SegmentEnumerator(nsRubyFrame
* aRubyFrame
);
73 bool AtEnd() const { return !mBaseContainer
; }
75 nsRubyBaseContainerFrame
* GetBaseContainer() const
77 return mBaseContainer
;
81 nsRubyBaseContainerFrame
* mBaseContainer
;
84 SegmentEnumerator::SegmentEnumerator(nsRubyFrame
* aRubyFrame
)
86 nsIFrame
* frame
= aRubyFrame
->GetFirstPrincipalChild();
88 frame
->GetType() == nsGkAtoms::rubyBaseContainerFrame
);
89 mBaseContainer
= static_cast<nsRubyBaseContainerFrame
*>(frame
);
93 SegmentEnumerator::Next()
95 MOZ_ASSERT(mBaseContainer
);
96 nsIFrame
* frame
= mBaseContainer
->GetNextSibling();
97 while (frame
&& frame
->GetType() != nsGkAtoms::rubyBaseContainerFrame
) {
98 frame
= frame
->GetNextSibling();
100 mBaseContainer
= static_cast<nsRubyBaseContainerFrame
*>(frame
);
104 nsRubyFrame::AddInlineMinISize(nsRenderingContext
*aRenderingContext
,
105 nsIFrame::InlineMinISizeData
*aData
)
108 for (SegmentEnumerator
e(this); !e
.AtEnd(); e
.Next()) {
109 max
= std::max(max
, e
.GetBaseContainer()->GetMinISize(aRenderingContext
));
111 aData
->currentLine
+= max
;
115 nsRubyFrame::AddInlinePrefISize(nsRenderingContext
*aRenderingContext
,
116 nsIFrame::InlinePrefISizeData
*aData
)
119 for (SegmentEnumerator
e(this); !e
.AtEnd(); e
.Next()) {
120 sum
+= e
.GetBaseContainer()->GetPrefISize(aRenderingContext
);
122 aData
->currentLine
+= sum
;
125 /* virtual */ LogicalSize
126 nsRubyFrame::ComputeSize(nsRenderingContext
*aRenderingContext
,
128 const LogicalSize
& aCBSize
,
129 nscoord aAvailableISize
,
130 const LogicalSize
& aMargin
,
131 const LogicalSize
& aBorder
,
132 const LogicalSize
& aPadding
,
133 ComputeSizeFlags aFlags
)
135 // Ruby frame is inline, hence don't compute size before reflow.
136 return LogicalSize(aWM
, NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
139 /* virtual */ nscoord
140 nsRubyFrame::GetLogicalBaseline(WritingMode aWritingMode
) const
146 nsRubyFrame::CanContinueTextRun() const
152 nsRubyFrame::Reflow(nsPresContext
* aPresContext
,
153 nsHTMLReflowMetrics
& aDesiredSize
,
154 const nsHTMLReflowState
& aReflowState
,
155 nsReflowStatus
& aStatus
)
157 DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
158 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aDesiredSize
, aStatus
);
160 if (!aReflowState
.mLineLayout
) {
161 NS_ASSERTION(aReflowState
.mLineLayout
,
162 "No line layout provided to RubyFrame reflow method.");
163 aStatus
= NS_FRAME_COMPLETE
;
167 // Grab overflow frames from prev-in-flow and its own.
168 MoveOverflowToChildList();
171 mBStartLeading
= mBEndLeading
= 0;
173 // Begin the span for the ruby frame
174 WritingMode frameWM
= aReflowState
.GetWritingMode();
175 WritingMode lineWM
= aReflowState
.mLineLayout
->GetWritingMode();
176 LogicalMargin borderPadding
= aReflowState
.ComputedLogicalBorderPadding();
177 nscoord startEdge
= borderPadding
.IStart(frameWM
);
178 nscoord endEdge
= aReflowState
.AvailableISize() - borderPadding
.IEnd(frameWM
);
179 NS_ASSERTION(aReflowState
.AvailableISize() != NS_UNCONSTRAINEDSIZE
,
180 "should no longer use available widths");
181 aReflowState
.mLineLayout
->BeginSpan(this, &aReflowState
,
182 startEdge
, endEdge
, &mBaseline
);
184 aStatus
= NS_FRAME_COMPLETE
;
185 for (SegmentEnumerator
e(this); !e
.AtEnd(); e
.Next()) {
186 ReflowSegment(aPresContext
, aReflowState
, e
.GetBaseContainer(), aStatus
);
188 if (NS_INLINE_IS_BREAK(aStatus
)) {
189 // A break occurs when reflowing the segment.
190 // Don't continue reflowing more segments.
195 ContinuationTraversingState
pullState(this);
196 while (aStatus
== NS_FRAME_COMPLETE
) {
197 nsRubyBaseContainerFrame
* baseContainer
= PullOneSegment(pullState
);
198 if (!baseContainer
) {
199 // No more continuations after, finish now.
202 ReflowSegment(aPresContext
, aReflowState
, baseContainer
, aStatus
);
204 // We never handle overflow in ruby.
205 MOZ_ASSERT(!NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus
));
207 aDesiredSize
.ISize(lineWM
) = aReflowState
.mLineLayout
->EndSpan(this);
208 nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize
, aReflowState
,
209 borderPadding
, lineWM
, frameWM
);
214 SanityCheckRubyPosition(int8_t aRubyPosition
)
216 uint8_t horizontalPosition
= aRubyPosition
&
217 (NS_STYLE_RUBY_POSITION_LEFT
| NS_STYLE_RUBY_POSITION_RIGHT
);
218 MOZ_ASSERT(horizontalPosition
== NS_STYLE_RUBY_POSITION_LEFT
||
219 horizontalPosition
== NS_STYLE_RUBY_POSITION_RIGHT
);
220 uint8_t verticalPosition
= aRubyPosition
&
221 (NS_STYLE_RUBY_POSITION_OVER
| NS_STYLE_RUBY_POSITION_UNDER
|
222 NS_STYLE_RUBY_POSITION_INTER_CHARACTER
);
223 MOZ_ASSERT(verticalPosition
== NS_STYLE_RUBY_POSITION_OVER
||
224 verticalPosition
== NS_STYLE_RUBY_POSITION_UNDER
||
225 verticalPosition
== NS_STYLE_RUBY_POSITION_INTER_CHARACTER
);
230 nsRubyFrame::ReflowSegment(nsPresContext
* aPresContext
,
231 const nsHTMLReflowState
& aReflowState
,
232 nsRubyBaseContainerFrame
* aBaseContainer
,
233 nsReflowStatus
& aStatus
)
235 WritingMode lineWM
= aReflowState
.mLineLayout
->GetWritingMode();
236 LogicalSize
availSize(lineWM
, aReflowState
.AvailableISize(),
237 aReflowState
.AvailableBSize());
239 nsAutoTArray
<nsRubyTextContainerFrame
*, RTC_ARRAY_SIZE
> textContainers
;
240 for (RubyTextContainerIterator
iter(aBaseContainer
); !iter
.AtEnd(); iter
.Next()) {
241 textContainers
.AppendElement(iter
.GetTextContainer());
243 const uint32_t rtcCount
= textContainers
.Length();
244 RubyReflowState
rubyReflowState(lineWM
, textContainers
);
246 nsHTMLReflowMetrics
baseMetrics(aReflowState
);
248 aReflowState
.mLineLayout
->SetRubyReflowState(&rubyReflowState
);
249 aReflowState
.mLineLayout
->ReflowFrame(aBaseContainer
, aStatus
,
250 &baseMetrics
, pushedFrame
);
252 if (NS_INLINE_IS_BREAK_BEFORE(aStatus
)) {
253 if (aBaseContainer
!= mFrames
.FirstChild()) {
254 // Some segments may have been reflowed before, hence it is not
255 // a break-before for the ruby container.
256 aStatus
= NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_NOT_COMPLETE
);
257 PushChildren(aBaseContainer
, aBaseContainer
->GetPrevSibling());
258 aReflowState
.mLineLayout
->SetDirtyNextLine();
260 // This base container is not placed at all, we can skip all
261 // text containers paired with it.
264 if (NS_FRAME_IS_NOT_COMPLETE(aStatus
)) {
265 // It always promise that if the status is incomplete, there is a
266 // break occurs. Break before has been processed above. However,
267 // it is possible that break after happens with the frame reflow
268 // completed. It happens if there is a force break at the end.
269 MOZ_ASSERT(NS_INLINE_IS_BREAK_AFTER(aStatus
));
270 // Find the previous sibling which we will
271 // insert new continuations after.
274 lastChild
= textContainers
.LastElement();
276 lastChild
= aBaseContainer
;
279 // Create continuations for the base container
280 nsIFrame
* newBaseContainer
= CreateNextInFlow(aBaseContainer
);
281 // newBaseContainer is null if there are existing next-in-flows.
282 // We only need to move and push if there were not.
283 if (newBaseContainer
) {
284 // Move the new frame after all the text containers
285 mFrames
.RemoveFrame(newBaseContainer
);
286 mFrames
.InsertFrame(nullptr, lastChild
, newBaseContainer
);
288 // Create continuations for text containers
289 nsIFrame
* newLastChild
= newBaseContainer
;
290 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
291 nsIFrame
* newTextContainer
= CreateNextInFlow(textContainers
[i
]);
292 MOZ_ASSERT(newTextContainer
, "Next-in-flow of rtc should not exist "
293 "if the corresponding rbc does not");
294 mFrames
.RemoveFrame(newTextContainer
);
295 mFrames
.InsertFrame(nullptr, newLastChild
, newTextContainer
);
296 newLastChild
= newTextContainer
;
299 if (lastChild
!= mFrames
.LastChild()) {
300 // Always push the next frame after the last child in this segment.
301 // It is possible that we pulled it back before our next-in-flow
302 // drain our overflow.
303 PushChildren(lastChild
->GetNextSibling(), lastChild
);
304 aReflowState
.mLineLayout
->SetDirtyNextLine();
307 // If the ruby base container is reflowed completely, the line
308 // layout will remove the next-in-flows of that frame. But the
309 // line layout is not aware of the ruby text containers, hence
310 // it is necessary to remove them here.
311 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
312 nsIFrame
* nextRTC
= textContainers
[i
]->GetNextInFlow();
314 nextRTC
->GetParent()->DeleteNextInFlowChild(nextRTC
, true);
319 nscoord segmentISize
= baseMetrics
.ISize(lineWM
);
320 nsRect baseRect
= aBaseContainer
->GetRect();
321 // We need to position our rtc frames on one side or the other of the
322 // base container's rect, using a coordinate space that's relative to
323 // the ruby frame. Right now, the base container's rect's block-axis
324 // position is relative to the block container frame containing the
325 // lines, so we use 0 instead. (i.e. we assume that the base container
326 // is adjacent to the ruby frame's block-start edge.)
327 // XXX We may need to add border/padding here. See bug 1055667.
328 (lineWM
.IsVertical() ? baseRect
.x
: baseRect
.y
) = 0;
329 // The rect for offsets of text containers.
330 nsRect offsetRect
= baseRect
;
331 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
332 nsRubyTextContainerFrame
* textContainer
= textContainers
[i
];
333 rubyReflowState
.AdvanceCurrentContainerIndex();
335 nsReflowStatus textReflowStatus
;
336 nsHTMLReflowMetrics
textMetrics(aReflowState
);
337 nsHTMLReflowState
textReflowState(aPresContext
, aReflowState
,
338 textContainer
, availSize
);
339 textReflowState
.mRubyReflowState
= &rubyReflowState
;
340 // FIXME We probably shouldn't be using the same nsLineLayout for
341 // the text containers. But it should be fine now as we are
342 // not actually using this line layout to reflow something,
343 // but just read the writing mode from it.
344 textReflowState
.mLineLayout
= aReflowState
.mLineLayout
;
345 textContainer
->Reflow(aPresContext
, textMetrics
,
346 textReflowState
, textReflowStatus
);
347 // Ruby text containers always return NS_FRAME_COMPLETE even when
348 // they have continuations, because the breaking has already been
349 // handled when reflowing the base containers.
350 NS_ASSERTION(textReflowStatus
== NS_FRAME_COMPLETE
,
351 "Ruby text container must not break itself inside");
352 nscoord isize
= textMetrics
.ISize(lineWM
);
353 nscoord bsize
= textMetrics
.BSize(lineWM
);
354 textContainer
->SetSize(LogicalSize(lineWM
, isize
, bsize
));
356 nscoord reservedISize
= RubyUtils::GetReservedISize(textContainer
);
357 segmentISize
= std::max(segmentISize
, isize
+ reservedISize
);
360 uint8_t rubyPosition
= textContainer
->StyleText()->mRubyPosition
;
362 SanityCheckRubyPosition(rubyPosition
);
364 if (lineWM
.IsVertical()) {
365 // writing-mode is vertical, so bsize is the annotation's *width*
366 if (rubyPosition
& NS_STYLE_RUBY_POSITION_LEFT
) {
367 x
= offsetRect
.X() - bsize
;
368 offsetRect
.SetLeftEdge(x
);
370 x
= offsetRect
.XMost();
371 offsetRect
.SetRightEdge(x
+ bsize
);
375 // writing-mode is horizontal, so bsize is the annotation's *height*
377 if (rubyPosition
& NS_STYLE_RUBY_POSITION_OVER
) {
378 y
= offsetRect
.Y() - bsize
;
379 offsetRect
.SetTopEdge(y
);
380 } else if (rubyPosition
& NS_STYLE_RUBY_POSITION_UNDER
) {
381 y
= offsetRect
.YMost();
382 offsetRect
.SetBottomEdge(y
+ bsize
);
384 // XXX inter-character support in bug 1055672
385 MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
389 FinishReflowChild(textContainer
, aPresContext
, textMetrics
,
390 &textReflowState
, x
, y
, 0);
393 nscoord deltaISize
= segmentISize
- baseMetrics
.ISize(lineWM
);
394 if (deltaISize
<= 0) {
395 RubyUtils::ClearReservedISize(aBaseContainer
);
397 RubyUtils::SetReservedISize(aBaseContainer
, deltaISize
);
398 aReflowState
.mLineLayout
->AdvanceICoord(deltaISize
);
401 // Set block leadings of the base container
402 LogicalMargin
leadings(lineWM
, offsetRect
- baseRect
);
403 NS_ASSERTION(leadings
.BStart(lineWM
) >= 0 && leadings
.BEnd(lineWM
) >= 0,
404 "Leadings should be non-negative (because adding "
405 "ruby annotation can only increase the size)");
406 mBStartLeading
= std::max(mBStartLeading
, leadings
.BStart(lineWM
));
407 mBEndLeading
= std::max(mBEndLeading
, leadings
.BEnd(lineWM
));
410 nsRubyBaseContainerFrame
*
411 nsRubyFrame::PullOneSegment(ContinuationTraversingState
& aState
)
413 // Pull a ruby base container
414 nsIFrame
* baseFrame
= PullNextInFlowChild(aState
);
418 MOZ_ASSERT(baseFrame
->GetType() == nsGkAtoms::rubyBaseContainerFrame
);
420 // Pull all ruby text containers following the base container
422 while ((nextFrame
= GetNextInFlowChild(aState
)) != nullptr &&
423 nextFrame
->GetType() == nsGkAtoms::rubyTextContainerFrame
) {
424 PullNextInFlowChild(aState
);
427 return static_cast<nsRubyBaseContainerFrame
*>(baseFrame
);