diff options
Diffstat (limited to 'layout/generic/nsFirstLetterFrame.cpp')
-rw-r--r-- | layout/generic/nsFirstLetterFrame.cpp | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/layout/generic/nsFirstLetterFrame.cpp b/layout/generic/nsFirstLetterFrame.cpp new file mode 100644 index 000000000..980e1e9be --- /dev/null +++ b/layout/generic/nsFirstLetterFrame.cpp @@ -0,0 +1,415 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* rendering object for CSS :first-letter pseudo-element */ + +#include "nsFirstLetterFrame.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsIContent.h" +#include "nsLineLayout.h" +#include "nsGkAtoms.h" +#include "mozilla/StyleSetHandle.h" +#include "mozilla/StyleSetHandleInlines.h" +#include "nsFrameManager.h" +#include "mozilla/RestyleManagerHandle.h" +#include "mozilla/RestyleManagerHandleInlines.h" +#include "nsPlaceholderFrame.h" +#include "nsCSSFrameConstructor.h" + +using namespace mozilla; +using namespace mozilla::layout; + +nsFirstLetterFrame* +NS_NewFirstLetterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsFirstLetterFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame) + +NS_QUERYFRAME_HEAD(nsFirstLetterFrame) + NS_QUERYFRAME_ENTRY(nsFirstLetterFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsFirstLetterFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("Letter"), aResult); +} +#endif + +nsIAtom* +nsFirstLetterFrame::GetType() const +{ + return nsGkAtoms::letterFrame; +} + +void +nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + BuildDisplayListForInline(aBuilder, aDirtyRect, aLists); +} + +void +nsFirstLetterFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + RefPtr<nsStyleContext> newSC; + if (aPrevInFlow) { + // Get proper style context for ourselves. We're creating the frame + // that represents everything *except* the first letter, so just create + // a style context like we would for a text node. + nsStyleContext* parentStyleContext = mStyleContext->GetParent(); + if (parentStyleContext) { + newSC = PresContext()->StyleSet()-> + ResolveStyleForOtherNonElement(parentStyleContext); + SetStyleContextWithoutNotification(newSC); + } + } + + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); +} + +void +nsFirstLetterFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + MOZ_ASSERT(aListID == kPrincipalList, "Principal child list is the only " + "list that nsFirstLetterFrame should set via this function"); + RestyleManagerHandle restyleManager = PresContext()->RestyleManager(); + + for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) { + NS_ASSERTION(e.get()->GetParent() == this, "Unexpected parent"); + restyleManager->ReparentStyleContext(e.get()); + nsLayoutUtils::MarkDescendantsDirty(e.get()); + } + + mFrames.SetFrames(aChildList); +} + +nsresult +nsFirstLetterFrame::GetChildFrameContainingOffset(int32_t inContentOffset, + bool inHint, + int32_t* outFrameContentOffset, + nsIFrame **outChildFrame) +{ + nsIFrame *kid = mFrames.FirstChild(); + if (kid) { + return kid->GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame); + } else { + return nsFrame::GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame); + } +} + +// Needed for non-floating first-letter frames and for the continuations +// following the first-letter that we also use nsFirstLetterFrame for. +/* virtual */ void +nsFirstLetterFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext, + nsIFrame::InlineMinISizeData *aData) +{ + DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::MIN_ISIZE); +} + +// Needed for non-floating first-letter frames and for the continuations +// following the first-letter that we also use nsFirstLetterFrame for. +/* virtual */ void +nsFirstLetterFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext, + nsIFrame::InlinePrefISizeData *aData) +{ + DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE); +} + +// Needed for floating first-letter frames. +/* virtual */ nscoord +nsFirstLetterFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext); +} + +// Needed for floating first-letter frames. +/* virtual */ nscoord +nsFirstLetterFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext); +} + +/* virtual */ +LogicalSize +nsFirstLetterFrame::ComputeSize(nsRenderingContext *aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + if (GetPrevInFlow()) { + // We're wrapping the text *after* the first letter, so behave like an + // inline frame. + return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + return nsContainerFrame::ComputeSize(aRenderingContext, aWM, + aCBSize, aAvailableISize, aMargin, aBorder, aPadding, aFlags); +} + +void +nsFirstLetterFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aMetrics, + const ReflowInput& aReflowInput, + nsReflowStatus& aReflowStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aReflowStatus); + + // Grab overflow list + DrainOverflowFrames(aPresContext); + + nsIFrame* kid = mFrames.FirstChild(); + + // Setup reflow state for our child + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalSize availSize = aReflowInput.AvailableSize(); + const LogicalMargin& bp = aReflowInput.ComputedLogicalBorderPadding(); + NS_ASSERTION(availSize.ISize(wm) != NS_UNCONSTRAINEDSIZE, + "should no longer use unconstrained inline size"); + availSize.ISize(wm) -= bp.IStartEnd(wm); + if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) { + availSize.BSize(wm) -= bp.BStartEnd(wm); + } + + WritingMode lineWM = aMetrics.GetWritingMode(); + ReflowOutput kidMetrics(lineWM); + + // Reflow the child + if (!aReflowInput.mLineLayout) { + // When there is no lineLayout provided, we provide our own. The + // only time that the first-letter-frame is not reflowing in a + // line context is when its floating. + WritingMode kidWritingMode = GetWritingMode(kid); + LogicalSize kidAvailSize = availSize.ConvertTo(kidWritingMode, wm); + ReflowInput rs(aPresContext, aReflowInput, kid, kidAvailSize); + nsLineLayout ll(aPresContext, nullptr, &aReflowInput, nullptr, nullptr); + + ll.BeginLineReflow(bp.IStart(wm), bp.BStart(wm), + availSize.ISize(wm), NS_UNCONSTRAINEDSIZE, + false, true, kidWritingMode, + nsSize(aReflowInput.AvailableWidth(), + aReflowInput.AvailableHeight())); + rs.mLineLayout = ≪ + ll.SetInFirstLetter(true); + ll.SetFirstLetterStyleOK(true); + + kid->Reflow(aPresContext, kidMetrics, rs, aReflowStatus); + + ll.EndLineReflow(); + ll.SetInFirstLetter(false); + + // In the floating first-letter case, we need to set this ourselves; + // nsLineLayout::BeginSpan will set it in the other case + mBaseline = kidMetrics.BlockStartAscent(); + + // Place and size the child and update the output metrics + LogicalSize convertedSize = kidMetrics.Size(lineWM).ConvertTo(wm, lineWM); + kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm), + convertedSize.ISize(wm), convertedSize.BSize(wm))); + kid->FinishAndStoreOverflow(&kidMetrics); + kid->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); + + convertedSize.ISize(wm) += bp.IStartEnd(wm); + convertedSize.BSize(wm) += bp.BStartEnd(wm); + aMetrics.SetSize(wm, convertedSize); + aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + + bp.BStart(wm)); + + // Ensure that the overflow rect contains the child textframe's + // overflow rect. + // Note that if this is floating, the overline/underline drawable + // area is in the overflow rect of the child textframe. + aMetrics.UnionOverflowAreasWithDesiredBounds(); + ConsiderChildOverflow(aMetrics.mOverflowAreas, kid); + + FinishAndStoreOverflow(&aMetrics); + } else { + // Pretend we are a span and reflow the child frame + nsLineLayout* ll = aReflowInput.mLineLayout; + bool pushedFrame; + + ll->SetInFirstLetter( + mStyleContext->GetPseudo() == nsCSSPseudoElements::firstLetter); + ll->BeginSpan(this, &aReflowInput, bp.IStart(wm), + availSize.ISize(wm), &mBaseline); + ll->ReflowFrame(kid, aReflowStatus, &kidMetrics, pushedFrame); + NS_ASSERTION(lineWM.IsVertical() == wm.IsVertical(), + "we're assuming we can mix sizes between lineWM and wm " + "since we shouldn't have orthogonal writing modes within " + "a line."); + aMetrics.ISize(lineWM) = ll->EndSpan(this) + bp.IStartEnd(wm); + ll->SetInFirstLetter(false); + + if (mStyleContext->StyleTextReset()->mInitialLetterSize != 0.0f) { + aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + + bp.BStart(wm)); + aMetrics.BSize(lineWM) = kidMetrics.BSize(lineWM) + bp.BStartEnd(wm); + } else { + nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, bp, lineWM, wm); + } + } + + if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) { + // Create a continuation or remove existing continuations based on + // the reflow completion status. + if (NS_FRAME_IS_COMPLETE(aReflowStatus)) { + if (aReflowInput.mLineLayout) { + aReflowInput.mLineLayout->SetFirstLetterStyleOK(false); + } + nsIFrame* kidNextInFlow = kid->GetNextInFlow(); + if (kidNextInFlow) { + // Remove all of the childs next-in-flows + kidNextInFlow->GetParent()->DeleteNextInFlowChild(kidNextInFlow, true); + } + } else { + // Create a continuation for the child frame if it doesn't already + // have one. + if (!IsFloating()) { + CreateNextInFlow(kid); + // And then push it to our overflow list + const nsFrameList& overflow = mFrames.RemoveFramesAfter(kid); + if (overflow.NotEmpty()) { + SetOverflowFrames(overflow); + } + } else if (!kid->GetNextInFlow()) { + // For floating first letter frames (if a continuation wasn't already + // created for us) we need to put the continuation with the rest of the + // text that the first letter frame was made out of. + nsIFrame* continuation; + CreateContinuationForFloatingParent(aPresContext, kid, + &continuation, true); + } + } + } + + NS_FRAME_SET_TRUNCATION(aReflowStatus, aReflowInput, aMetrics); +} + +/* virtual */ bool +nsFirstLetterFrame::CanContinueTextRun() const +{ + // We can continue a text run through a first-letter frame. + return true; +} + +nsresult +nsFirstLetterFrame::CreateContinuationForFloatingParent(nsPresContext* aPresContext, + nsIFrame* aChild, + nsIFrame** aContinuation, + bool aIsFluid) +{ + NS_ASSERTION(IsFloating(), + "can only call this on floating first letter frames"); + NS_PRECONDITION(aContinuation, "bad args"); + + *aContinuation = nullptr; + + nsIPresShell* presShell = aPresContext->PresShell(); + nsPlaceholderFrame* placeholderFrame = + presShell->FrameManager()->GetPlaceholderFrameFor(this); + nsContainerFrame* parent = placeholderFrame->GetParent(); + + nsIFrame* continuation = presShell->FrameConstructor()-> + CreateContinuingFrame(aPresContext, aChild, parent, aIsFluid); + + // The continuation will have gotten the first letter style from its + // prev continuation, so we need to repair the style context so it + // doesn't have the first letter styling. + nsStyleContext* parentSC = this->StyleContext()->GetParent(); + if (parentSC) { + RefPtr<nsStyleContext> newSC; + newSC = presShell->StyleSet()->ResolveStyleForOtherNonElement(parentSC); + continuation->SetStyleContext(newSC); + nsLayoutUtils::MarkDescendantsDirty(continuation); + } + + //XXX Bidi may not be involved but we have to use the list name + // kNoReflowPrincipalList because this is just like creating a continuation + // except we have to insert it in a different place and we don't want a + // reflow command to try to be issued. + nsFrameList temp(continuation, continuation); + parent->InsertFrames(kNoReflowPrincipalList, placeholderFrame, temp); + + *aContinuation = continuation; + return NS_OK; +} + +void +nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) +{ + // Check for an overflow list with our prev-in-flow + nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow(); + if (prevInFlow) { + AutoFrameListPtr overflowFrames(aPresContext, + prevInFlow->StealOverflowFrames()); + if (overflowFrames) { + NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list"); + + // When pushing and pulling frames we need to check for whether any + // views need to be reparented. + nsContainerFrame::ReparentFrameViewList(*overflowFrames, prevInFlow, + this); + mFrames.InsertFrames(this, nullptr, *overflowFrames); + } + } + + // It's also possible that we have an overflow list for ourselves + AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames()); + if (overflowFrames) { + NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames"); + mFrames.AppendFrames(nullptr, *overflowFrames); + } + + // Now repair our first frames style context (since we only reflow + // one frame there is no point in doing any other ones until they + // are reflowed) + nsIFrame* kid = mFrames.FirstChild(); + if (kid) { + RefPtr<nsStyleContext> sc; + nsIContent* kidContent = kid->GetContent(); + if (kidContent) { + NS_ASSERTION(kidContent->IsNodeOfType(nsINode::eTEXT), + "should contain only text nodes"); + nsStyleContext* parentSC = prevInFlow ? mStyleContext->GetParent() : + mStyleContext; + sc = aPresContext->StyleSet()->ResolveStyleForText(kidContent, parentSC); + kid->SetStyleContext(sc); + nsLayoutUtils::MarkDescendantsDirty(kid); + } + } +} + +nscoord +nsFirstLetterFrame::GetLogicalBaseline(WritingMode aWritingMode) const +{ + return mBaseline; +} + +nsIFrame::LogicalSides +nsFirstLetterFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const +{ + if (GetPrevContinuation()) { + // We shouldn't get calls to GetSkipSides for later continuations since + // they have separate style contexts with initial values for all the + // properties that could trigger a call to GetSkipSides. Then again, + // it's not really an error to call GetSkipSides on any frame, so + // that's why we handle it properly. + return LogicalSides(eLogicalSideBitsAll); + } + return LogicalSides(); // first continuation displays all sides +} |