Line data Source code
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/. */
6 :
7 : /* rendering object for CSS "display: ruby" */
8 :
9 : #include "nsRubyFrame.h"
10 :
11 : #include "RubyUtils.h"
12 : #include "mozilla/Maybe.h"
13 : #include "mozilla/WritingModes.h"
14 : #include "nsLineLayout.h"
15 : #include "nsPresContext.h"
16 : #include "nsRubyBaseContainerFrame.h"
17 : #include "nsRubyTextContainerFrame.h"
18 : #include "nsStyleContext.h"
19 :
20 : using namespace mozilla;
21 :
22 : //----------------------------------------------------------------------
23 :
24 : // Frame class boilerplate
25 : // =======================
26 :
27 0 : NS_QUERYFRAME_HEAD(nsRubyFrame)
28 0 : NS_QUERYFRAME_ENTRY(nsRubyFrame)
29 0 : NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
30 :
31 0 : NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame)
32 :
33 : nsContainerFrame*
34 0 : NS_NewRubyFrame(nsIPresShell* aPresShell,
35 : nsStyleContext* aContext)
36 : {
37 0 : return new (aPresShell) nsRubyFrame(aContext);
38 : }
39 :
40 : //----------------------------------------------------------------------
41 :
42 : // nsRubyFrame Method Implementations
43 : // ==================================
44 :
45 : /* virtual */ bool
46 0 : nsRubyFrame::IsFrameOfType(uint32_t aFlags) const
47 : {
48 0 : if (aFlags & eBidiInlineContainer) {
49 0 : return false;
50 : }
51 0 : return nsInlineFrame::IsFrameOfType(aFlags);
52 : }
53 :
54 : #ifdef DEBUG_FRAME_DUMP
55 : nsresult
56 0 : nsRubyFrame::GetFrameName(nsAString& aResult) const
57 : {
58 0 : return MakeFrameName(NS_LITERAL_STRING("Ruby"), aResult);
59 : }
60 : #endif
61 :
62 : /* virtual */ void
63 0 : nsRubyFrame::AddInlineMinISize(gfxContext *aRenderingContext,
64 : nsIFrame::InlineMinISizeData *aData)
65 : {
66 0 : for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
67 0 : for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame));
68 0 : !e.AtEnd(); e.Next()) {
69 0 : e.GetBaseContainer()->AddInlineMinISize(aRenderingContext, aData);
70 : }
71 : }
72 0 : }
73 :
74 : /* virtual */ void
75 0 : nsRubyFrame::AddInlinePrefISize(gfxContext *aRenderingContext,
76 : nsIFrame::InlinePrefISizeData *aData)
77 : {
78 0 : for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
79 0 : for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame));
80 0 : !e.AtEnd(); e.Next()) {
81 0 : e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, aData);
82 : }
83 : }
84 0 : aData->mLineIsEmpty = false;
85 0 : }
86 :
87 : static nsRubyBaseContainerFrame*
88 0 : FindRubyBaseContainerAncestor(nsIFrame* aFrame)
89 : {
90 0 : for (nsIFrame* ancestor = aFrame->GetParent();
91 0 : ancestor && ancestor->IsFrameOfType(nsIFrame::eLineParticipant);
92 : ancestor = ancestor->GetParent()) {
93 0 : if (ancestor->IsRubyBaseContainerFrame()) {
94 0 : return static_cast<nsRubyBaseContainerFrame*>(ancestor);
95 : }
96 : }
97 0 : return nullptr;
98 : }
99 :
100 : /* virtual */ void
101 0 : nsRubyFrame::Reflow(nsPresContext* aPresContext,
102 : ReflowOutput& aDesiredSize,
103 : const ReflowInput& aReflowInput,
104 : nsReflowStatus& aStatus)
105 : {
106 0 : MarkInReflow();
107 0 : DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
108 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
109 :
110 0 : if (!aReflowInput.mLineLayout) {
111 0 : NS_ASSERTION(aReflowInput.mLineLayout,
112 : "No line layout provided to RubyFrame reflow method.");
113 0 : aStatus.Reset();
114 0 : return;
115 : }
116 :
117 : // Grab overflow frames from prev-in-flow and its own.
118 0 : MoveOverflowToChildList();
119 :
120 : // Clear leadings
121 0 : mLeadings.Reset();
122 :
123 : // Since the ruby base container is going to reflow not only the ruby
124 : // base frames, but also the ruby text frames, and then *afterwards*
125 : // we're going to reflow the ruby text containers (which do not reflow
126 : // their children), we need to transfer NS_FRAME_IS_DIRTY status from
127 : // the ruby text containers to their child ruby texts now, both so
128 : // that the ruby texts are marked dirty if needed, and so that the
129 : // ruby text container doesn't mark the ruby text frames dirty *after*
130 : // they're reflowed and leave dirty bits in a clean tree (suppressing
131 : // future reflows, due to lack of a queued reflow to clean them).
132 0 : for (nsIFrame* child : PrincipalChildList()) {
133 0 : if (child->HasAnyStateBits(NS_FRAME_IS_DIRTY) &&
134 0 : child->IsRubyTextContainerFrame()) {
135 0 : for (nsIFrame* grandchild : child->PrincipalChildList()) {
136 0 : grandchild->AddStateBits(NS_FRAME_IS_DIRTY);
137 : }
138 : // Replace NS_FRAME_IS_DIRTY with NS_FRAME_HAS_DIRTY_CHILDREN so
139 : // we still have a dirty marking, but one that we won't transfer
140 : // to children again.
141 0 : child->RemoveStateBits(NS_FRAME_IS_DIRTY);
142 0 : child->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
143 : }
144 : }
145 :
146 : // Begin the span for the ruby frame
147 0 : WritingMode frameWM = aReflowInput.GetWritingMode();
148 0 : WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
149 0 : LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding();
150 0 : nscoord startEdge = 0;
151 : const bool boxDecorationBreakClone =
152 0 : StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone;
153 0 : if (boxDecorationBreakClone || !GetPrevContinuation()) {
154 0 : startEdge = borderPadding.IStart(frameWM);
155 : }
156 0 : NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
157 : "should no longer use available widths");
158 0 : nscoord availableISize = aReflowInput.AvailableISize();
159 0 : availableISize -= startEdge + borderPadding.IEnd(frameWM);
160 0 : aReflowInput.mLineLayout->BeginSpan(this, &aReflowInput,
161 0 : startEdge, availableISize, &mBaseline);
162 :
163 0 : aStatus.Reset();
164 0 : for (RubySegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
165 0 : ReflowSegment(aPresContext, aReflowInput, e.GetBaseContainer(), aStatus);
166 :
167 0 : if (aStatus.IsInlineBreak()) {
168 : // A break occurs when reflowing the segment.
169 : // Don't continue reflowing more segments.
170 0 : break;
171 : }
172 : }
173 :
174 0 : ContinuationTraversingState pullState(this);
175 0 : while (aStatus.IsEmpty()) {
176 : nsRubyBaseContainerFrame* baseContainer =
177 0 : PullOneSegment(aReflowInput.mLineLayout, pullState);
178 0 : if (!baseContainer) {
179 : // No more continuations after, finish now.
180 0 : break;
181 : }
182 0 : ReflowSegment(aPresContext, aReflowInput, baseContainer, aStatus);
183 : }
184 : // We never handle overflow in ruby.
185 0 : MOZ_ASSERT(!aStatus.IsOverflowIncomplete());
186 :
187 0 : aDesiredSize.ISize(lineWM) = aReflowInput.mLineLayout->EndSpan(this);
188 0 : if (boxDecorationBreakClone || !GetPrevContinuation()) {
189 0 : aDesiredSize.ISize(lineWM) += borderPadding.IStart(frameWM);
190 : }
191 0 : if (boxDecorationBreakClone || aStatus.IsComplete()) {
192 0 : aDesiredSize.ISize(lineWM) += borderPadding.IEnd(frameWM);
193 : }
194 :
195 : // Update descendant leadings of ancestor ruby base container.
196 0 : if (nsRubyBaseContainerFrame* rbc = FindRubyBaseContainerAncestor(this)) {
197 0 : rbc->UpdateDescendantLeadings(mLeadings);
198 : }
199 :
200 0 : nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize,
201 0 : borderPadding, lineWM, frameWM);
202 : }
203 :
204 : void
205 0 : nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
206 : const ReflowInput& aReflowInput,
207 : nsRubyBaseContainerFrame* aBaseContainer,
208 : nsReflowStatus& aStatus)
209 : {
210 0 : WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
211 : LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
212 0 : aReflowInput.AvailableBSize());
213 0 : WritingMode rubyWM = GetWritingMode();
214 0 : NS_ASSERTION(!rubyWM.IsOrthogonalTo(lineWM),
215 : "Ruby frame writing-mode shouldn't be orthogonal to its line");
216 :
217 0 : AutoRubyTextContainerArray textContainers(aBaseContainer);
218 0 : const uint32_t rtcCount = textContainers.Length();
219 :
220 0 : ReflowOutput baseMetrics(aReflowInput);
221 : bool pushedFrame;
222 0 : aReflowInput.mLineLayout->ReflowFrame(aBaseContainer, aStatus,
223 0 : &baseMetrics, pushedFrame);
224 :
225 0 : if (aStatus.IsInlineBreakBefore()) {
226 0 : if (aBaseContainer != mFrames.FirstChild()) {
227 : // Some segments may have been reflowed before, hence it is not
228 : // a break-before for the ruby container.
229 0 : aStatus.Reset();
230 0 : aStatus.SetInlineLineBreakAfter();
231 0 : aStatus.SetIncomplete();
232 0 : PushChildren(aBaseContainer, aBaseContainer->GetPrevSibling());
233 0 : aReflowInput.mLineLayout->SetDirtyNextLine();
234 : }
235 : // This base container is not placed at all, we can skip all
236 : // text containers paired with it.
237 0 : return;
238 : }
239 0 : if (aStatus.IsIncomplete()) {
240 : // It always promise that if the status is incomplete, there is a
241 : // break occurs. Break before has been processed above. However,
242 : // it is possible that break after happens with the frame reflow
243 : // completed. It happens if there is a force break at the end.
244 0 : MOZ_ASSERT(aStatus.IsInlineBreakAfter());
245 : // Find the previous sibling which we will
246 : // insert new continuations after.
247 : nsIFrame* lastChild;
248 0 : if (rtcCount > 0) {
249 0 : lastChild = textContainers.LastElement();
250 : } else {
251 0 : lastChild = aBaseContainer;
252 : }
253 :
254 : // Create continuations for the base container
255 0 : nsIFrame* newBaseContainer = CreateNextInFlow(aBaseContainer);
256 : // newBaseContainer is null if there are existing next-in-flows.
257 : // We only need to move and push if there were not.
258 0 : if (newBaseContainer) {
259 : // Move the new frame after all the text containers
260 0 : mFrames.RemoveFrame(newBaseContainer);
261 0 : mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
262 :
263 : // Create continuations for text containers
264 0 : nsIFrame* newLastChild = newBaseContainer;
265 0 : for (uint32_t i = 0; i < rtcCount; i++) {
266 0 : nsIFrame* newTextContainer = CreateNextInFlow(textContainers[i]);
267 0 : MOZ_ASSERT(newTextContainer, "Next-in-flow of rtc should not exist "
268 : "if the corresponding rbc does not");
269 0 : mFrames.RemoveFrame(newTextContainer);
270 0 : mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
271 0 : newLastChild = newTextContainer;
272 : }
273 : }
274 0 : if (lastChild != mFrames.LastChild()) {
275 : // Always push the next frame after the last child in this segment.
276 : // It is possible that we pulled it back before our next-in-flow
277 : // drain our overflow.
278 0 : PushChildren(lastChild->GetNextSibling(), lastChild);
279 0 : aReflowInput.mLineLayout->SetDirtyNextLine();
280 : }
281 : } else {
282 : // If the ruby base container is reflowed completely, the line
283 : // layout will remove the next-in-flows of that frame. But the
284 : // line layout is not aware of the ruby text containers, hence
285 : // it is necessary to remove them here.
286 0 : for (uint32_t i = 0; i < rtcCount; i++) {
287 0 : nsIFrame* nextRTC = textContainers[i]->GetNextInFlow();
288 0 : if (nextRTC) {
289 0 : nextRTC->GetParent()->DeleteNextInFlowChild(nextRTC, true);
290 : }
291 : }
292 : }
293 :
294 0 : nscoord segmentISize = baseMetrics.ISize(lineWM);
295 0 : const nsSize dummyContainerSize;
296 : LogicalRect baseRect =
297 0 : aBaseContainer->GetLogicalRect(lineWM, dummyContainerSize);
298 : // We need to position our rtc frames on one side or the other of the
299 : // base container's rect, using a coordinate space that's relative to
300 : // the ruby frame. Right now, the base container's rect's block-axis
301 : // position is relative to the block container frame containing the
302 : // lines, so we use 0 instead. (i.e. we assume that the base container
303 : // is adjacent to the ruby frame's block-start edge.)
304 : // XXX We may need to add border/padding here. See bug 1055667.
305 0 : baseRect.BStart(lineWM) = 0;
306 : // The rect for offsets of text containers.
307 0 : LogicalRect offsetRect = baseRect;
308 0 : RubyBlockLeadings descLeadings = aBaseContainer->GetDescendantLeadings();
309 0 : offsetRect.BStart(lineWM) -= descLeadings.mStart;
310 0 : offsetRect.BSize(lineWM) += descLeadings.mStart + descLeadings.mEnd;
311 0 : for (uint32_t i = 0; i < rtcCount; i++) {
312 0 : nsRubyTextContainerFrame* textContainer = textContainers[i];
313 0 : WritingMode rtcWM = textContainer->GetWritingMode();
314 0 : nsReflowStatus textReflowStatus;
315 0 : ReflowOutput textMetrics(aReflowInput);
316 : ReflowInput textReflowInput(aPresContext, aReflowInput, textContainer,
317 0 : availSize.ConvertTo(rtcWM, lineWM));
318 : // FIXME We probably shouldn't be using the same nsLineLayout for
319 : // the text containers. But it should be fine now as we are
320 : // not actually using this line layout to reflow something,
321 : // but just read the writing mode from it.
322 0 : textReflowInput.mLineLayout = aReflowInput.mLineLayout;
323 : textContainer->Reflow(aPresContext, textMetrics,
324 0 : textReflowInput, textReflowStatus);
325 : // Ruby text containers always return complete reflow status even when
326 : // they have continuations, because the breaking has already been
327 : // handled when reflowing the base containers.
328 0 : NS_ASSERTION(textReflowStatus.IsEmpty(),
329 : "Ruby text container must not break itself inside");
330 : // The metrics is initialized with reflow state of this ruby frame,
331 : // hence the writing-mode is tied to rubyWM instead of rtcWM.
332 0 : LogicalSize size = textMetrics.Size(rubyWM).ConvertTo(lineWM, rubyWM);
333 0 : textContainer->SetSize(lineWM, size);
334 :
335 0 : nscoord reservedISize = RubyUtils::GetReservedISize(textContainer);
336 0 : segmentISize = std::max(segmentISize, size.ISize(lineWM) + reservedISize);
337 :
338 0 : uint8_t rubyPosition = textContainer->StyleText()->mRubyPosition;
339 0 : MOZ_ASSERT(rubyPosition == NS_STYLE_RUBY_POSITION_OVER ||
340 : rubyPosition == NS_STYLE_RUBY_POSITION_UNDER);
341 0 : Maybe<LogicalSide> side;
342 0 : if (rubyPosition == NS_STYLE_RUBY_POSITION_OVER) {
343 0 : side.emplace(lineWM.LogicalSideForLineRelativeDir(eLineRelativeDirOver));
344 0 : } else if (rubyPosition == NS_STYLE_RUBY_POSITION_UNDER) {
345 0 : side.emplace(lineWM.LogicalSideForLineRelativeDir(eLineRelativeDirUnder));
346 : } else {
347 : // XXX inter-character support in bug 1055672
348 0 : MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
349 : }
350 :
351 0 : LogicalPoint position(lineWM);
352 0 : if (side.isSome()) {
353 0 : if (side.value() == eLogicalSideBStart) {
354 0 : offsetRect.BStart(lineWM) -= size.BSize(lineWM);
355 0 : offsetRect.BSize(lineWM) += size.BSize(lineWM);
356 0 : position = offsetRect.Origin(lineWM);
357 0 : } else if (side.value() == eLogicalSideBEnd) {
358 0 : position = offsetRect.Origin(lineWM) +
359 0 : LogicalPoint(lineWM, 0, offsetRect.BSize(lineWM));
360 0 : offsetRect.BSize(lineWM) += size.BSize(lineWM);
361 : } else {
362 0 : MOZ_ASSERT_UNREACHABLE("???");
363 : }
364 : }
365 : // Using a dummy container-size here, so child positioning may not be
366 : // correct. We will fix it in nsLineLayout after the whole line is
367 : // reflowed.
368 : FinishReflowChild(textContainer, aPresContext, textMetrics,
369 0 : &textReflowInput, lineWM, position, dummyContainerSize, 0);
370 : }
371 0 : MOZ_ASSERT(baseRect.ISize(lineWM) == offsetRect.ISize(lineWM),
372 : "Annotations should only be placed on the block directions");
373 :
374 0 : nscoord deltaISize = segmentISize - baseMetrics.ISize(lineWM);
375 0 : if (deltaISize <= 0) {
376 0 : RubyUtils::ClearReservedISize(aBaseContainer);
377 : } else {
378 0 : RubyUtils::SetReservedISize(aBaseContainer, deltaISize);
379 0 : aReflowInput.mLineLayout->AdvanceICoord(deltaISize);
380 : }
381 :
382 : // Set block leadings of the base container
383 0 : nscoord startLeading = baseRect.BStart(lineWM) - offsetRect.BStart(lineWM);
384 0 : nscoord endLeading = offsetRect.BEnd(lineWM) - baseRect.BEnd(lineWM);
385 : // XXX When bug 765861 gets fixed, this warning should be upgraded.
386 0 : NS_WARNING_ASSERTION(startLeading >= 0 && endLeading >= 0,
387 : "Leadings should be non-negative (because adding "
388 : "ruby annotation can only increase the size)");
389 0 : mLeadings.Update(startLeading, endLeading);
390 : }
391 :
392 : nsRubyBaseContainerFrame*
393 0 : nsRubyFrame::PullOneSegment(const nsLineLayout* aLineLayout,
394 : ContinuationTraversingState& aState)
395 : {
396 : // Pull a ruby base container
397 0 : nsIFrame* baseFrame = GetNextInFlowChild(aState);
398 0 : if (!baseFrame) {
399 0 : return nullptr;
400 : }
401 0 : MOZ_ASSERT(baseFrame->IsRubyBaseContainerFrame());
402 :
403 : // Get the float containing block of the base frame before we pull it.
404 : nsBlockFrame* oldFloatCB =
405 0 : nsLayoutUtils::GetFloatContainingBlock(baseFrame);
406 0 : PullNextInFlowChild(aState);
407 :
408 : // Pull all ruby text containers following the base container
409 : nsIFrame* nextFrame;
410 0 : while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
411 0 : nextFrame->IsRubyTextContainerFrame()) {
412 0 : PullNextInFlowChild(aState);
413 : }
414 :
415 0 : if (nsBlockFrame* newFloatCB =
416 0 : nsLayoutUtils::GetAsBlock(aLineLayout->LineContainerFrame())) {
417 0 : if (oldFloatCB && oldFloatCB != newFloatCB) {
418 0 : newFloatCB->ReparentFloats(baseFrame, oldFloatCB, true);
419 : }
420 : }
421 :
422 0 : return static_cast<nsRubyBaseContainerFrame*>(baseFrame);
423 : }
|