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/. */
6 
7 /* rendering object for CSS "display: ruby-text-container" */
8 
9 #include "nsRubyTextContainerFrame.h"
10 
11 #include "mozilla/ComputedStyle.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/UniquePtr.h"
14 #include "mozilla/WritingModes.h"
15 #include "nsLayoutUtils.h"
16 #include "nsLineLayout.h"
17 #include "nsPresContext.h"
18 
19 using namespace mozilla;
20 
21 //----------------------------------------------------------------------
22 
23 // Frame class boilerplate
24 // =======================
25 
26 NS_QUERYFRAME_HEAD(nsRubyTextContainerFrame)
NS_QUERYFRAME_ENTRY(nsRubyTextContainerFrame)27   NS_QUERYFRAME_ENTRY(nsRubyTextContainerFrame)
28 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
29 
30 NS_IMPL_FRAMEARENA_HELPERS(nsRubyTextContainerFrame)
31 
32 nsContainerFrame* NS_NewRubyTextContainerFrame(PresShell* aPresShell,
33                                                ComputedStyle* aStyle) {
34   return new (aPresShell)
35       nsRubyTextContainerFrame(aStyle, aPresShell->GetPresContext());
36 }
37 
38 //----------------------------------------------------------------------
39 
40 // nsRubyTextContainerFrame Method Implementations
41 // ===============================================
42 
43 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const44 nsresult nsRubyTextContainerFrame::GetFrameName(nsAString& aResult) const {
45   return MakeFrameName(u"RubyTextContainer"_ns, aResult);
46 }
47 #endif
48 
49 /* virtual */
IsFrameOfType(uint32_t aFlags) const50 bool nsRubyTextContainerFrame::IsFrameOfType(uint32_t aFlags) const {
51   if (aFlags & (eSupportsCSSTransforms | eSupportsContainLayoutAndPaint |
52                 eSupportsAspectRatio)) {
53     return false;
54   }
55   return nsContainerFrame::IsFrameOfType(aFlags);
56 }
57 
58 /* virtual */
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)59 void nsRubyTextContainerFrame::SetInitialChildList(ChildListID aListID,
60                                                    nsFrameList& aChildList) {
61   nsContainerFrame::SetInitialChildList(aListID, aChildList);
62   if (aListID == kPrincipalList) {
63     UpdateSpanFlag();
64   }
65 }
66 
67 /* virtual */
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)68 void nsRubyTextContainerFrame::AppendFrames(ChildListID aListID,
69                                             nsFrameList& aFrameList) {
70   nsContainerFrame::AppendFrames(aListID, aFrameList);
71   UpdateSpanFlag();
72 }
73 
74 /* virtual */
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)75 void nsRubyTextContainerFrame::InsertFrames(
76     ChildListID aListID, nsIFrame* aPrevFrame,
77     const nsLineList::iterator* aPrevFrameLine, nsFrameList& aFrameList) {
78   nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
79                                  aFrameList);
80   UpdateSpanFlag();
81 }
82 
83 /* virtual */
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)84 void nsRubyTextContainerFrame::RemoveFrame(ChildListID aListID,
85                                            nsIFrame* aOldFrame) {
86   nsContainerFrame::RemoveFrame(aListID, aOldFrame);
87   UpdateSpanFlag();
88 }
89 
UpdateSpanFlag()90 void nsRubyTextContainerFrame::UpdateSpanFlag() {
91   bool isSpan = false;
92   // The continuation checks are safe here because spans never break.
93   if (!GetPrevContinuation() && !GetNextContinuation()) {
94     nsIFrame* onlyChild = mFrames.OnlyChild();
95     if (onlyChild && onlyChild->IsPseudoFrame(GetContent())) {
96       // Per CSS Ruby spec, if the only child of an rtc frame is
97       // a pseudo rt frame, it spans all bases in the segment.
98       isSpan = true;
99     }
100   }
101 
102   if (isSpan) {
103     AddStateBits(NS_RUBY_TEXT_CONTAINER_IS_SPAN);
104   } else {
105     RemoveStateBits(NS_RUBY_TEXT_CONTAINER_IS_SPAN);
106   }
107 }
108 
109 /* virtual */
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)110 void nsRubyTextContainerFrame::Reflow(nsPresContext* aPresContext,
111                                       ReflowOutput& aDesiredSize,
112                                       const ReflowInput& aReflowInput,
113                                       nsReflowStatus& aStatus) {
114   MarkInReflow();
115   DO_GLOBAL_REFLOW_COUNT("nsRubyTextContainerFrame");
116   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
117   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
118 
119   // Although a ruby text container may have continuations, returning
120   // complete reflow status is still safe, since its parent, ruby frame,
121   // ignores the status, and continuations of the ruby base container
122   // will take care of our continuations.
123   WritingMode rtcWM = GetWritingMode();
124 
125   nscoord minBCoord = nscoord_MAX;
126   nscoord maxBCoord = nscoord_MIN;
127   // The container size is not yet known, so we use a dummy (0, 0) size.
128   // The block-dir position will be corrected below after containerSize
129   // is finalized.
130   const nsSize dummyContainerSize;
131   for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
132     nsIFrame* child = e.get();
133     MOZ_ASSERT(child->IsRubyTextFrame());
134     LogicalRect rect = child->GetLogicalRect(rtcWM, dummyContainerSize);
135     LogicalMargin margin = child->GetLogicalUsedMargin(rtcWM);
136     nscoord blockStart = rect.BStart(rtcWM) - margin.BStart(rtcWM);
137     minBCoord = std::min(minBCoord, blockStart);
138     nscoord blockEnd = rect.BEnd(rtcWM) + margin.BEnd(rtcWM);
139     maxBCoord = std::max(maxBCoord, blockEnd);
140   }
141 
142   if (!mFrames.IsEmpty()) {
143     if (MOZ_UNLIKELY(minBCoord > maxBCoord)) {
144       // XXX When bug 765861 gets fixed, this warning should be upgraded.
145       NS_WARNING("bad block coord");
146       minBCoord = maxBCoord = 0;
147     }
148     LogicalSize size(rtcWM, mISize, maxBCoord - minBCoord);
149     nsSize containerSize = size.GetPhysicalSize(rtcWM);
150     for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
151       nsIFrame* child = e.get();
152       // We reflowed the child with a dummy container size, as the true size
153       // was not yet known at that time.
154       LogicalPoint pos = child->GetLogicalPosition(rtcWM, dummyContainerSize);
155       // Adjust block position to account for minBCoord,
156       // then reposition child based on the true container width.
157       pos.B(rtcWM) -= minBCoord;
158       // Relative positioning hasn't happened yet.
159       // So MovePositionBy should not be used here.
160       child->SetPosition(rtcWM, pos, containerSize);
161       nsContainerFrame::PlaceFrameView(child);
162     }
163     aDesiredSize.SetSize(rtcWM, size);
164   } else {
165     // If this ruby text container is empty, size it as if there were
166     // an empty inline child inside.
167     // Border and padding are suppressed on ruby text container, so we
168     // create a dummy zero-sized borderPadding for setting BSize.
169     aDesiredSize.ISize(rtcWM) = mISize;
170     LogicalMargin borderPadding(rtcWM);
171     nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
172                                            rtcWM, rtcWM);
173   }
174 }
175