Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : /* rendering object for CSS :first-letter pseudo-element */
7 :
8 : #include "nsFirstLetterFrame.h"
9 : #include "nsPresContext.h"
10 : #include "nsStyleContext.h"
11 : #include "nsIContent.h"
12 : #include "nsLineLayout.h"
13 : #include "nsGkAtoms.h"
14 : #include "mozilla/StyleSetHandle.h"
15 : #include "mozilla/StyleSetHandleInlines.h"
16 : #include "nsFrameManager.h"
17 : #include "mozilla/RestyleManager.h"
18 : #include "mozilla/RestyleManagerInlines.h"
19 : #include "nsPlaceholderFrame.h"
20 : #include "nsCSSFrameConstructor.h"
21 :
22 : using namespace mozilla;
23 : using namespace mozilla::layout;
24 :
25 : nsFirstLetterFrame*
26 0 : NS_NewFirstLetterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
27 : {
28 0 : return new (aPresShell) nsFirstLetterFrame(aContext);
29 : }
30 :
31 0 : NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
32 :
33 0 : NS_QUERYFRAME_HEAD(nsFirstLetterFrame)
34 0 : NS_QUERYFRAME_ENTRY(nsFirstLetterFrame)
35 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
36 :
37 : #ifdef DEBUG_FRAME_DUMP
38 : nsresult
39 0 : nsFirstLetterFrame::GetFrameName(nsAString& aResult) const
40 : {
41 0 : return MakeFrameName(NS_LITERAL_STRING("Letter"), aResult);
42 : }
43 : #endif
44 :
45 : void
46 0 : nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
47 : const nsRect& aDirtyRect,
48 : const nsDisplayListSet& aLists)
49 : {
50 0 : BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
51 0 : }
52 :
53 : void
54 0 : nsFirstLetterFrame::Init(nsIContent* aContent,
55 : nsContainerFrame* aParent,
56 : nsIFrame* aPrevInFlow)
57 : {
58 0 : RefPtr<nsStyleContext> newSC;
59 0 : if (aPrevInFlow) {
60 : // Get proper style context for ourselves. We're creating the frame
61 : // that represents everything *except* the first letter, so just create
62 : // a style context like we would for a text node.
63 0 : nsStyleContext* parentStyleContext = mStyleContext->GetParentAllowServo();
64 0 : if (parentStyleContext) {
65 0 : newSC = PresContext()->StyleSet()->
66 0 : ResolveStyleForFirstLetterContinuation(parentStyleContext);
67 0 : SetStyleContextWithoutNotification(newSC);
68 : }
69 : }
70 :
71 0 : nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
72 0 : }
73 :
74 : void
75 0 : nsFirstLetterFrame::SetInitialChildList(ChildListID aListID,
76 : nsFrameList& aChildList)
77 : {
78 0 : MOZ_ASSERT(aListID == kPrincipalList, "Principal child list is the only "
79 : "list that nsFirstLetterFrame should set via this function");
80 0 : RestyleManager* restyleManager = PresContext()->RestyleManager();
81 :
82 0 : for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
83 0 : NS_ASSERTION(e.get()->GetParent() == this, "Unexpected parent");
84 0 : restyleManager->ReparentStyleContext(e.get());
85 0 : nsLayoutUtils::MarkDescendantsDirty(e.get());
86 : }
87 :
88 0 : mFrames.SetFrames(aChildList);
89 0 : }
90 :
91 : nsresult
92 0 : nsFirstLetterFrame::GetChildFrameContainingOffset(int32_t inContentOffset,
93 : bool inHint,
94 : int32_t* outFrameContentOffset,
95 : nsIFrame **outChildFrame)
96 : {
97 0 : nsIFrame *kid = mFrames.FirstChild();
98 0 : if (kid) {
99 0 : return kid->GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame);
100 : } else {
101 0 : return nsFrame::GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame);
102 : }
103 : }
104 :
105 : // Needed for non-floating first-letter frames and for the continuations
106 : // following the first-letter that we also use nsFirstLetterFrame for.
107 : /* virtual */ void
108 0 : nsFirstLetterFrame::AddInlineMinISize(gfxContext *aRenderingContext,
109 : nsIFrame::InlineMinISizeData *aData)
110 : {
111 0 : DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::MIN_ISIZE);
112 0 : }
113 :
114 : // Needed for non-floating first-letter frames and for the continuations
115 : // following the first-letter that we also use nsFirstLetterFrame for.
116 : /* virtual */ void
117 0 : nsFirstLetterFrame::AddInlinePrefISize(gfxContext *aRenderingContext,
118 : nsIFrame::InlinePrefISizeData *aData)
119 : {
120 0 : DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE);
121 0 : aData->mLineIsEmpty = false;
122 0 : }
123 :
124 : // Needed for floating first-letter frames.
125 : /* virtual */ nscoord
126 0 : nsFirstLetterFrame::GetMinISize(gfxContext *aRenderingContext)
127 : {
128 0 : return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
129 : }
130 :
131 : // Needed for floating first-letter frames.
132 : /* virtual */ nscoord
133 0 : nsFirstLetterFrame::GetPrefISize(gfxContext *aRenderingContext)
134 : {
135 0 : return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
136 : }
137 :
138 : /* virtual */
139 : LogicalSize
140 0 : nsFirstLetterFrame::ComputeSize(gfxContext *aRenderingContext,
141 : WritingMode aWM,
142 : const LogicalSize& aCBSize,
143 : nscoord aAvailableISize,
144 : const LogicalSize& aMargin,
145 : const LogicalSize& aBorder,
146 : const LogicalSize& aPadding,
147 : ComputeSizeFlags aFlags)
148 : {
149 0 : if (GetPrevInFlow()) {
150 : // We're wrapping the text *after* the first letter, so behave like an
151 : // inline frame.
152 0 : return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
153 : }
154 : return nsContainerFrame::ComputeSize(aRenderingContext, aWM,
155 0 : aCBSize, aAvailableISize, aMargin, aBorder, aPadding, aFlags);
156 : }
157 :
158 : void
159 0 : nsFirstLetterFrame::Reflow(nsPresContext* aPresContext,
160 : ReflowOutput& aMetrics,
161 : const ReflowInput& aReflowInput,
162 : nsReflowStatus& aReflowStatus)
163 : {
164 0 : MarkInReflow();
165 0 : DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
166 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aReflowStatus);
167 :
168 : // Grab overflow list
169 0 : DrainOverflowFrames(aPresContext);
170 :
171 0 : nsIFrame* kid = mFrames.FirstChild();
172 :
173 : // Setup reflow state for our child
174 0 : WritingMode wm = aReflowInput.GetWritingMode();
175 0 : LogicalSize availSize = aReflowInput.AvailableSize();
176 0 : const LogicalMargin& bp = aReflowInput.ComputedLogicalBorderPadding();
177 0 : NS_ASSERTION(availSize.ISize(wm) != NS_UNCONSTRAINEDSIZE,
178 : "should no longer use unconstrained inline size");
179 0 : availSize.ISize(wm) -= bp.IStartEnd(wm);
180 0 : if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
181 0 : availSize.BSize(wm) -= bp.BStartEnd(wm);
182 : }
183 :
184 0 : WritingMode lineWM = aMetrics.GetWritingMode();
185 0 : ReflowOutput kidMetrics(lineWM);
186 :
187 : // Reflow the child
188 0 : if (!aReflowInput.mLineLayout) {
189 : // When there is no lineLayout provided, we provide our own. The
190 : // only time that the first-letter-frame is not reflowing in a
191 : // line context is when its floating.
192 0 : WritingMode kidWritingMode = WritingModeForLine(wm, kid);
193 0 : LogicalSize kidAvailSize = availSize.ConvertTo(kidWritingMode, wm);
194 0 : ReflowInput rs(aPresContext, aReflowInput, kid, kidAvailSize);
195 0 : nsLineLayout ll(aPresContext, nullptr, &aReflowInput, nullptr, nullptr);
196 :
197 0 : ll.BeginLineReflow(bp.IStart(wm), bp.BStart(wm),
198 0 : availSize.ISize(wm), NS_UNCONSTRAINEDSIZE,
199 : false, true, kidWritingMode,
200 0 : nsSize(aReflowInput.AvailableWidth(),
201 0 : aReflowInput.AvailableHeight()));
202 0 : rs.mLineLayout = ≪
203 0 : ll.SetInFirstLetter(true);
204 0 : ll.SetFirstLetterStyleOK(true);
205 :
206 0 : kid->Reflow(aPresContext, kidMetrics, rs, aReflowStatus);
207 :
208 0 : ll.EndLineReflow();
209 0 : ll.SetInFirstLetter(false);
210 :
211 : // In the floating first-letter case, we need to set this ourselves;
212 : // nsLineLayout::BeginSpan will set it in the other case
213 0 : mBaseline = kidMetrics.BlockStartAscent();
214 :
215 : // Place and size the child and update the output metrics
216 0 : LogicalSize convertedSize = kidMetrics.Size(lineWM).ConvertTo(wm, lineWM);
217 0 : kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm),
218 0 : convertedSize.ISize(wm), convertedSize.BSize(wm)));
219 0 : kid->FinishAndStoreOverflow(&kidMetrics, rs.mStyleDisplay);
220 0 : kid->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED);
221 :
222 0 : convertedSize.ISize(wm) += bp.IStartEnd(wm);
223 0 : convertedSize.BSize(wm) += bp.BStartEnd(wm);
224 0 : aMetrics.SetSize(wm, convertedSize);
225 0 : aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() +
226 0 : bp.BStart(wm));
227 :
228 : // Ensure that the overflow rect contains the child textframe's
229 : // overflow rect.
230 : // Note that if this is floating, the overline/underline drawable
231 : // area is in the overflow rect of the child textframe.
232 0 : aMetrics.UnionOverflowAreasWithDesiredBounds();
233 0 : ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
234 :
235 0 : FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
236 : } else {
237 : // Pretend we are a span and reflow the child frame
238 0 : nsLineLayout* ll = aReflowInput.mLineLayout;
239 : bool pushedFrame;
240 :
241 0 : ll->SetInFirstLetter(
242 0 : mStyleContext->GetPseudo() == nsCSSPseudoElements::firstLetter);
243 0 : ll->BeginSpan(this, &aReflowInput, bp.IStart(wm),
244 0 : availSize.ISize(wm), &mBaseline);
245 0 : ll->ReflowFrame(kid, aReflowStatus, &kidMetrics, pushedFrame);
246 0 : NS_ASSERTION(lineWM.IsVertical() == wm.IsVertical(),
247 : "we're assuming we can mix sizes between lineWM and wm "
248 : "since we shouldn't have orthogonal writing modes within "
249 : "a line.");
250 0 : aMetrics.ISize(lineWM) = ll->EndSpan(this) + bp.IStartEnd(wm);
251 0 : ll->SetInFirstLetter(false);
252 :
253 0 : if (mStyleContext->StyleTextReset()->mInitialLetterSize != 0.0f) {
254 0 : aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() +
255 0 : bp.BStart(wm));
256 0 : aMetrics.BSize(lineWM) = kidMetrics.BSize(lineWM) + bp.BStartEnd(wm);
257 : } else {
258 0 : nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, bp, lineWM, wm);
259 : }
260 : }
261 :
262 0 : if (!aReflowStatus.IsInlineBreakBefore()) {
263 : // Create a continuation or remove existing continuations based on
264 : // the reflow completion status.
265 0 : if (aReflowStatus.IsComplete()) {
266 0 : if (aReflowInput.mLineLayout) {
267 0 : aReflowInput.mLineLayout->SetFirstLetterStyleOK(false);
268 : }
269 0 : nsIFrame* kidNextInFlow = kid->GetNextInFlow();
270 0 : if (kidNextInFlow) {
271 : // Remove all of the childs next-in-flows
272 0 : kidNextInFlow->GetParent()->DeleteNextInFlowChild(kidNextInFlow, true);
273 : }
274 : } else {
275 : // Create a continuation for the child frame if it doesn't already
276 : // have one.
277 0 : if (!IsFloating()) {
278 0 : CreateNextInFlow(kid);
279 : // And then push it to our overflow list
280 0 : const nsFrameList& overflow = mFrames.RemoveFramesAfter(kid);
281 0 : if (overflow.NotEmpty()) {
282 0 : SetOverflowFrames(overflow);
283 : }
284 0 : } else if (!kid->GetNextInFlow()) {
285 : // For floating first letter frames (if a continuation wasn't already
286 : // created for us) we need to put the continuation with the rest of the
287 : // text that the first letter frame was made out of.
288 : nsIFrame* continuation;
289 : CreateContinuationForFloatingParent(aPresContext, kid,
290 0 : &continuation, true);
291 : }
292 : }
293 : }
294 :
295 0 : NS_FRAME_SET_TRUNCATION(aReflowStatus, aReflowInput, aMetrics);
296 0 : }
297 :
298 : /* virtual */ bool
299 0 : nsFirstLetterFrame::CanContinueTextRun() const
300 : {
301 : // We can continue a text run through a first-letter frame.
302 0 : return true;
303 : }
304 :
305 : nsresult
306 0 : nsFirstLetterFrame::CreateContinuationForFloatingParent(nsPresContext* aPresContext,
307 : nsIFrame* aChild,
308 : nsIFrame** aContinuation,
309 : bool aIsFluid)
310 : {
311 0 : NS_ASSERTION(IsFloating(),
312 : "can only call this on floating first letter frames");
313 0 : NS_PRECONDITION(aContinuation, "bad args");
314 :
315 0 : *aContinuation = nullptr;
316 :
317 0 : nsIPresShell* presShell = aPresContext->PresShell();
318 0 : nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrame();
319 0 : nsContainerFrame* parent = placeholderFrame->GetParent();
320 :
321 : nsIFrame* continuation = presShell->FrameConstructor()->
322 0 : CreateContinuingFrame(aPresContext, aChild, parent, aIsFluid);
323 :
324 : // The continuation will have gotten the first letter style from its
325 : // prev continuation, so we need to repair the style context so it
326 : // doesn't have the first letter styling.
327 : //
328 : // Note that getting parent frame's style context is different from getting
329 : // this frame's style context's parent in the presence of ::first-line,
330 : // which we do want the continuation to inherit from.
331 0 : nsStyleContext* parentSC = parent->StyleContext();
332 0 : if (parentSC) {
333 0 : RefPtr<nsStyleContext> newSC;
334 0 : newSC = presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC);
335 0 : continuation->SetStyleContext(newSC);
336 0 : nsLayoutUtils::MarkDescendantsDirty(continuation);
337 : }
338 :
339 : //XXX Bidi may not be involved but we have to use the list name
340 : // kNoReflowPrincipalList because this is just like creating a continuation
341 : // except we have to insert it in a different place and we don't want a
342 : // reflow command to try to be issued.
343 0 : nsFrameList temp(continuation, continuation);
344 0 : parent->InsertFrames(kNoReflowPrincipalList, placeholderFrame, temp);
345 :
346 0 : *aContinuation = continuation;
347 0 : return NS_OK;
348 : }
349 :
350 : void
351 0 : nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext)
352 : {
353 : // Check for an overflow list with our prev-in-flow
354 0 : nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow();
355 0 : if (prevInFlow) {
356 : AutoFrameListPtr overflowFrames(aPresContext,
357 0 : prevInFlow->StealOverflowFrames());
358 0 : if (overflowFrames) {
359 0 : NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list");
360 :
361 : // When pushing and pulling frames we need to check for whether any
362 : // views need to be reparented.
363 0 : nsContainerFrame::ReparentFrameViewList(*overflowFrames, prevInFlow,
364 0 : this);
365 0 : mFrames.InsertFrames(this, nullptr, *overflowFrames);
366 : }
367 : }
368 :
369 : // It's also possible that we have an overflow list for ourselves
370 0 : AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames());
371 0 : if (overflowFrames) {
372 0 : NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
373 0 : mFrames.AppendFrames(nullptr, *overflowFrames);
374 : }
375 :
376 : // Now repair our first frames style context (since we only reflow
377 : // one frame there is no point in doing any other ones until they
378 : // are reflowed)
379 0 : nsIFrame* kid = mFrames.FirstChild();
380 0 : if (kid) {
381 0 : RefPtr<nsStyleContext> sc;
382 0 : nsIContent* kidContent = kid->GetContent();
383 0 : if (kidContent) {
384 0 : NS_ASSERTION(kidContent->IsNodeOfType(nsINode::eTEXT),
385 : "should contain only text nodes");
386 0 : nsStyleContext* parentSC = prevInFlow ? mStyleContext->GetParentAllowServo() :
387 0 : mStyleContext;
388 0 : sc = aPresContext->StyleSet()->ResolveStyleForText(kidContent, parentSC);
389 0 : kid->SetStyleContext(sc);
390 0 : nsLayoutUtils::MarkDescendantsDirty(kid);
391 : }
392 : }
393 0 : }
394 :
395 : nscoord
396 0 : nsFirstLetterFrame::GetLogicalBaseline(WritingMode aWritingMode) const
397 : {
398 0 : return mBaseline;
399 : }
400 :
401 : nsIFrame::LogicalSides
402 0 : nsFirstLetterFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
403 : {
404 0 : if (GetPrevContinuation()) {
405 : // We shouldn't get calls to GetSkipSides for later continuations since
406 : // they have separate style contexts with initial values for all the
407 : // properties that could trigger a call to GetSkipSides. Then again,
408 : // it's not really an error to call GetSkipSides on any frame, so
409 : // that's why we handle it properly.
410 0 : return LogicalSides(eLogicalSideBitsAll);
411 : }
412 0 : return LogicalSides(); // first continuation displays all sides
413 : }
|