1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : /* rendering object for CSS :first-letter pseudo-element */
39 :
40 : #include "nsCOMPtr.h"
41 : #include "nsFirstLetterFrame.h"
42 : #include "nsPresContext.h"
43 : #include "nsStyleContext.h"
44 : #include "nsIContent.h"
45 : #include "nsLineLayout.h"
46 : #include "nsGkAtoms.h"
47 : #include "nsAutoPtr.h"
48 : #include "nsStyleSet.h"
49 : #include "nsFrameManager.h"
50 : #include "nsPlaceholderFrame.h"
51 : #include "nsCSSFrameConstructor.h"
52 :
53 : using namespace::mozilla;
54 :
55 : nsIFrame*
56 0 : NS_NewFirstLetterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
57 : {
58 0 : return new (aPresShell) nsFirstLetterFrame(aContext);
59 : }
60 :
61 0 : NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
62 :
63 0 : NS_QUERYFRAME_HEAD(nsFirstLetterFrame)
64 0 : NS_QUERYFRAME_ENTRY(nsFirstLetterFrame)
65 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
66 :
67 : #ifdef NS_DEBUG
68 : NS_IMETHODIMP
69 0 : nsFirstLetterFrame::GetFrameName(nsAString& aResult) const
70 : {
71 0 : return MakeFrameName(NS_LITERAL_STRING("Letter"), aResult);
72 : }
73 : #endif
74 :
75 : nsIAtom*
76 0 : nsFirstLetterFrame::GetType() const
77 : {
78 0 : return nsGkAtoms::letterFrame;
79 : }
80 :
81 : PRIntn
82 0 : nsFirstLetterFrame::GetSkipSides() const
83 : {
84 0 : return 0;
85 : }
86 :
87 : NS_IMETHODIMP
88 0 : nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
89 : const nsRect& aDirtyRect,
90 : const nsDisplayListSet& aLists)
91 : {
92 0 : return BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
93 : }
94 :
95 : NS_IMETHODIMP
96 0 : nsFirstLetterFrame::Init(nsIContent* aContent,
97 : nsIFrame* aParent,
98 : nsIFrame* aPrevInFlow)
99 : {
100 0 : nsRefPtr<nsStyleContext> newSC;
101 0 : if (aPrevInFlow) {
102 : // Get proper style context for ourselves. We're creating the frame
103 : // that represents everything *except* the first letter, so just create
104 : // a style context like we would for a text node.
105 0 : nsStyleContext* parentStyleContext = mStyleContext->GetParent();
106 0 : if (parentStyleContext) {
107 : newSC = mStyleContext->GetRuleNode()->GetPresContext()->StyleSet()->
108 0 : ResolveStyleForNonElement(parentStyleContext);
109 0 : if (newSC)
110 0 : SetStyleContextWithoutNotification(newSC);
111 : }
112 : }
113 :
114 0 : return nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
115 : }
116 :
117 : NS_IMETHODIMP
118 0 : nsFirstLetterFrame::SetInitialChildList(ChildListID aListID,
119 : nsFrameList& aChildList)
120 : {
121 0 : nsFrameManager *frameManager = PresContext()->FrameManager();
122 :
123 0 : for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
124 0 : NS_ASSERTION(e.get()->GetParent() == this, "Unexpected parent");
125 0 : frameManager->ReparentStyleContext(e.get());
126 : }
127 :
128 0 : mFrames.SetFrames(aChildList);
129 0 : return NS_OK;
130 : }
131 :
132 : NS_IMETHODIMP
133 0 : nsFirstLetterFrame::GetChildFrameContainingOffset(PRInt32 inContentOffset,
134 : bool inHint,
135 : PRInt32* outFrameContentOffset,
136 : nsIFrame **outChildFrame)
137 : {
138 0 : nsIFrame *kid = mFrames.FirstChild();
139 0 : if (kid)
140 : {
141 0 : return kid->GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame);
142 : }
143 : else
144 0 : return nsFrame::GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame);
145 : }
146 :
147 : // Needed for non-floating first-letter frames and for the continuations
148 : // following the first-letter that we also use nsFirstLetterFrame for.
149 : /* virtual */ void
150 0 : nsFirstLetterFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext,
151 : nsIFrame::InlineMinWidthData *aData)
152 : {
153 0 : DoInlineIntrinsicWidth(aRenderingContext, aData, nsLayoutUtils::MIN_WIDTH);
154 0 : }
155 :
156 : // Needed for non-floating first-letter frames and for the continuations
157 : // following the first-letter that we also use nsFirstLetterFrame for.
158 : /* virtual */ void
159 0 : nsFirstLetterFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
160 : nsIFrame::InlinePrefWidthData *aData)
161 : {
162 0 : DoInlineIntrinsicWidth(aRenderingContext, aData, nsLayoutUtils::PREF_WIDTH);
163 0 : }
164 :
165 : // Needed for floating first-letter frames.
166 : /* virtual */ nscoord
167 0 : nsFirstLetterFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
168 : {
169 0 : return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext);
170 : }
171 :
172 : // Needed for floating first-letter frames.
173 : /* virtual */ nscoord
174 0 : nsFirstLetterFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
175 : {
176 0 : return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext);
177 : }
178 :
179 : /* virtual */ nsSize
180 0 : nsFirstLetterFrame::ComputeSize(nsRenderingContext *aRenderingContext,
181 : nsSize aCBSize, nscoord aAvailableWidth,
182 : nsSize aMargin, nsSize aBorder, nsSize aPadding,
183 : bool aShrinkWrap)
184 : {
185 0 : if (GetPrevInFlow()) {
186 : // We're wrapping the text *after* the first letter, so behave like an
187 : // inline frame.
188 0 : return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
189 : }
190 : return nsContainerFrame::ComputeSize(aRenderingContext,
191 0 : aCBSize, aAvailableWidth, aMargin, aBorder, aPadding, aShrinkWrap);
192 : }
193 :
194 : NS_IMETHODIMP
195 0 : nsFirstLetterFrame::Reflow(nsPresContext* aPresContext,
196 : nsHTMLReflowMetrics& aMetrics,
197 : const nsHTMLReflowState& aReflowState,
198 : nsReflowStatus& aReflowStatus)
199 : {
200 0 : DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
201 0 : DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aReflowStatus);
202 0 : nsresult rv = NS_OK;
203 :
204 : // Grab overflow list
205 0 : DrainOverflowFrames(aPresContext);
206 :
207 0 : nsIFrame* kid = mFrames.FirstChild();
208 :
209 : // Setup reflow state for our child
210 0 : nsSize availSize(aReflowState.availableWidth, aReflowState.availableHeight);
211 0 : const nsMargin& bp = aReflowState.mComputedBorderPadding;
212 0 : nscoord lr = bp.left + bp.right;
213 0 : nscoord tb = bp.top + bp.bottom;
214 0 : NS_ASSERTION(availSize.width != NS_UNCONSTRAINEDSIZE,
215 : "should no longer use unconstrained widths");
216 0 : availSize.width -= lr;
217 0 : if (NS_UNCONSTRAINEDSIZE != availSize.height) {
218 0 : availSize.height -= tb;
219 : }
220 :
221 : // Reflow the child
222 0 : if (!aReflowState.mLineLayout) {
223 : // When there is no lineLayout provided, we provide our own. The
224 : // only time that the first-letter-frame is not reflowing in a
225 : // line context is when its floating.
226 0 : nsHTMLReflowState rs(aPresContext, aReflowState, kid, availSize);
227 0 : nsLineLayout ll(aPresContext, nsnull, &aReflowState, nsnull);
228 :
229 : // For unicode-bidi: plaintext, we need to get the direction of the line
230 : // from the resolved paragraph level of the child, not the block frame,
231 : // because the block frame could be split by hard line breaks into
232 : // multiple paragraphs with different base direction
233 : PRUint8 direction;
234 0 : nsIFrame* containerFrame = ll.GetLineContainerFrame();
235 0 : if (containerFrame->GetStyleTextReset()->mUnicodeBidi &
236 : NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
237 0 : FramePropertyTable *propTable = aPresContext->PropertyTable();
238 0 : direction = NS_PTR_TO_INT32(propTable->Get(kid, BaseLevelProperty())) & 1;
239 : } else {
240 0 : direction = containerFrame->GetStyleVisibility()->mDirection;
241 : }
242 : ll.BeginLineReflow(bp.left, bp.top, availSize.width, NS_UNCONSTRAINEDSIZE,
243 0 : false, true, direction);
244 0 : rs.mLineLayout = ≪
245 0 : ll.SetInFirstLetter(true);
246 0 : ll.SetFirstLetterStyleOK(true);
247 :
248 0 : kid->WillReflow(aPresContext);
249 0 : kid->Reflow(aPresContext, aMetrics, rs, aReflowStatus);
250 :
251 0 : ll.EndLineReflow();
252 0 : ll.SetInFirstLetter(false);
253 :
254 : // In the floating first-letter case, we need to set this ourselves;
255 : // nsLineLayout::BeginSpan will set it in the other case
256 0 : mBaseline = aMetrics.ascent;
257 : }
258 : else {
259 : // Pretend we are a span and reflow the child frame
260 0 : nsLineLayout* ll = aReflowState.mLineLayout;
261 : bool pushedFrame;
262 :
263 : ll->SetInFirstLetter(
264 0 : mStyleContext->GetPseudo() == nsCSSPseudoElements::firstLetter);
265 0 : ll->BeginSpan(this, &aReflowState, bp.left, availSize.width, &mBaseline);
266 0 : ll->ReflowFrame(kid, aReflowStatus, &aMetrics, pushedFrame);
267 0 : ll->EndSpan(this);
268 0 : ll->SetInFirstLetter(false);
269 : }
270 :
271 : // Place and size the child and update the output metrics
272 0 : kid->SetRect(nsRect(bp.left, bp.top, aMetrics.width, aMetrics.height));
273 0 : kid->FinishAndStoreOverflow(&aMetrics);
274 0 : kid->DidReflow(aPresContext, nsnull, NS_FRAME_REFLOW_FINISHED);
275 :
276 0 : aMetrics.width += lr;
277 0 : aMetrics.height += tb;
278 0 : aMetrics.ascent += bp.top;
279 :
280 : // Ensure that the overflow rect contains the child textframe's overflow rect.
281 : // Note that if this is floating, the overline/underline drawable area is in
282 : // the overflow rect of the child textframe.
283 0 : aMetrics.UnionOverflowAreasWithDesiredBounds();
284 0 : ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
285 :
286 0 : if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) {
287 : // Create a continuation or remove existing continuations based on
288 : // the reflow completion status.
289 0 : if (NS_FRAME_IS_COMPLETE(aReflowStatus)) {
290 0 : if (aReflowState.mLineLayout) {
291 0 : aReflowState.mLineLayout->SetFirstLetterStyleOK(false);
292 : }
293 0 : nsIFrame* kidNextInFlow = kid->GetNextInFlow();
294 0 : if (kidNextInFlow) {
295 : // Remove all of the childs next-in-flows
296 0 : static_cast<nsContainerFrame*>(kidNextInFlow->GetParent())
297 0 : ->DeleteNextInFlowChild(aPresContext, kidNextInFlow, true);
298 : }
299 : }
300 : else {
301 : // Create a continuation for the child frame if it doesn't already
302 : // have one.
303 0 : if (!IsFloating()) {
304 : nsIFrame* nextInFlow;
305 0 : rv = CreateNextInFlow(aPresContext, kid, nextInFlow);
306 0 : if (NS_FAILED(rv)) {
307 0 : return rv;
308 : }
309 :
310 : // And then push it to our overflow list
311 0 : const nsFrameList& overflow = mFrames.RemoveFramesAfter(kid);
312 0 : if (overflow.NotEmpty()) {
313 0 : SetOverflowFrames(aPresContext, overflow);
314 : }
315 0 : } else if (!kid->GetNextInFlow()) {
316 : // For floating first letter frames (if a continuation wasn't already
317 : // created for us) we need to put the continuation with the rest of the
318 : // text that the first letter frame was made out of.
319 : nsIFrame* continuation;
320 : rv = CreateContinuationForFloatingParent(aPresContext, kid,
321 0 : &continuation, true);
322 : }
323 : }
324 : }
325 :
326 0 : FinishAndStoreOverflow(&aMetrics);
327 :
328 0 : NS_FRAME_SET_TRUNCATION(aReflowStatus, aReflowState, aMetrics);
329 0 : return rv;
330 : }
331 :
332 : /* virtual */ bool
333 0 : nsFirstLetterFrame::CanContinueTextRun() const
334 : {
335 : // We can continue a text run through a first-letter frame.
336 0 : return true;
337 : }
338 :
339 : nsresult
340 0 : nsFirstLetterFrame::CreateContinuationForFloatingParent(nsPresContext* aPresContext,
341 : nsIFrame* aChild,
342 : nsIFrame** aContinuation,
343 : bool aIsFluid)
344 : {
345 0 : NS_ASSERTION(IsFloating(),
346 : "can only call this on floating first letter frames");
347 0 : NS_PRECONDITION(aContinuation, "bad args");
348 :
349 0 : *aContinuation = nsnull;
350 0 : nsresult rv = NS_OK;
351 :
352 0 : nsIPresShell* presShell = aPresContext->PresShell();
353 : nsPlaceholderFrame* placeholderFrame =
354 0 : presShell->FrameManager()->GetPlaceholderFrameFor(this);
355 0 : nsIFrame* parent = placeholderFrame->GetParent();
356 :
357 : nsIFrame* continuation;
358 : rv = presShell->FrameConstructor()->
359 0 : CreateContinuingFrame(aPresContext, aChild, parent, &continuation, aIsFluid);
360 0 : if (NS_FAILED(rv) || !continuation) {
361 0 : return rv;
362 : }
363 :
364 : // The continuation will have gotten the first letter style from it's
365 : // prev continuation, so we need to repair the style context so it
366 : // doesn't have the first letter styling.
367 0 : nsStyleContext* parentSC = this->GetStyleContext()->GetParent();
368 0 : if (parentSC) {
369 0 : nsRefPtr<nsStyleContext> newSC;
370 0 : newSC = presShell->StyleSet()->ResolveStyleForNonElement(parentSC);
371 0 : if (newSC) {
372 0 : continuation->SetStyleContext(newSC);
373 : }
374 : }
375 :
376 : //XXX Bidi may not be involved but we have to use the list name
377 : // kNoReflowPrincipalList because this is just like creating a continuation
378 : // except we have to insert it in a different place and we don't want a
379 : // reflow command to try to be issued.
380 0 : nsFrameList temp(continuation, continuation);
381 0 : rv = parent->InsertFrames(kNoReflowPrincipalList, placeholderFrame, temp);
382 :
383 0 : *aContinuation = continuation;
384 0 : return rv;
385 : }
386 :
387 : void
388 0 : nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext)
389 : {
390 0 : nsAutoPtr<nsFrameList> overflowFrames;
391 :
392 : // Check for an overflow list with our prev-in-flow
393 0 : nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow();
394 0 : if (nsnull != prevInFlow) {
395 0 : overflowFrames = prevInFlow->StealOverflowFrames();
396 0 : if (overflowFrames) {
397 0 : NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list");
398 :
399 : // When pushing and pulling frames we need to check for whether any
400 : // views need to be reparented.
401 0 : nsContainerFrame::ReparentFrameViewList(aPresContext, *overflowFrames,
402 0 : prevInFlow, this);
403 0 : mFrames.InsertFrames(this, nsnull, *overflowFrames);
404 : }
405 : }
406 :
407 : // It's also possible that we have an overflow list for ourselves
408 0 : overflowFrames = StealOverflowFrames();
409 0 : if (overflowFrames) {
410 0 : NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
411 0 : mFrames.AppendFrames(nsnull, *overflowFrames);
412 : }
413 :
414 : // Now repair our first frames style context (since we only reflow
415 : // one frame there is no point in doing any other ones until they
416 : // are reflowed)
417 0 : nsIFrame* kid = mFrames.FirstChild();
418 0 : if (kid) {
419 0 : nsRefPtr<nsStyleContext> sc;
420 0 : nsIContent* kidContent = kid->GetContent();
421 0 : if (kidContent) {
422 0 : NS_ASSERTION(kidContent->IsNodeOfType(nsINode::eTEXT),
423 : "should contain only text nodes");
424 0 : sc = aPresContext->StyleSet()->ResolveStyleForNonElement(mStyleContext);
425 0 : if (sc) {
426 0 : kid->SetStyleContext(sc);
427 : }
428 : }
429 : }
430 0 : }
431 :
432 : nscoord
433 0 : nsFirstLetterFrame::GetBaseline() const
434 : {
435 0 : return mBaseline;
436 : }
|