diff options
Diffstat (limited to 'layout/generic/nsPageFrame.cpp')
-rw-r--r-- | layout/generic/nsPageFrame.cpp | 885 |
1 files changed, 885 insertions, 0 deletions
diff --git a/layout/generic/nsPageFrame.cpp b/layout/generic/nsPageFrame.cpp new file mode 100644 index 0000000000..b976a9c6a6 --- /dev/null +++ b/layout/generic/nsPageFrame.cpp @@ -0,0 +1,885 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "nsPageFrame.h" + +#include "mozilla/AppUnits.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/gfx/2D.h" +#include "gfxContext.h" +#include "nsDeviceContext.h" +#include "nsFontMetrics.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsGkAtoms.h" +#include "nsFieldSetFrame.h" +#include "nsPageContentFrame.h" +#include "nsDisplayList.h" +#include "nsPageSequenceFrame.h" // for nsSharedPageData +#include "nsTextFormatter.h" // for page number localization formatting +#include "nsBidiUtils.h" +#include "nsIPrintSettings.h" + +#include "mozilla/Logging.h" +extern mozilla::LazyLogModule gLayoutPrintingLog; +#define PR_PL(_p1) MOZ_LOG(gLayoutPrintingLog, mozilla::LogLevel::Debug, _p1) + +using namespace mozilla; +using namespace mozilla::gfx; + +nsPageFrame* NS_NewPageFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + return new (aPresShell) nsPageFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsPageFrame) + +NS_QUERYFRAME_HEAD(nsPageFrame) + NS_QUERYFRAME_ENTRY(nsPageFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +nsPageFrame::nsPageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : nsContainerFrame(aStyle, aPresContext, kClassID) {} + +nsPageFrame::~nsPageFrame() = default; + +nsReflowStatus nsPageFrame::ReflowPageContent( + nsPresContext* aPresContext, const ReflowInput& aPageReflowInput) { + // XXX Pay attention to the page's border and padding... + // + // Reflow our ::-moz-page-content frame, allowing it only to be as big as we + // are (minus margins). + if (mFrames.IsEmpty()) { + return {}; + } + + nsIFrame* frame = mFrames.FirstChild(); + nsSize maxSize = aPresContext->GetPageSize(); + float scale = aPresContext->GetPageScale(); + // When the reflow size is NS_UNCONSTRAINEDSIZE it means we are reflowing + // a single page to print selection. So this means we want to use + // NS_UNCONSTRAINEDSIZE without altering it. + // + // FIXME(emilio): Is this still true? + maxSize.width = NSToCoordCeil(maxSize.width / scale); + if (maxSize.height != NS_UNCONSTRAINEDSIZE) { + maxSize.height = NSToCoordCeil(maxSize.height / scale); + } + + // Get the number of Twips per pixel from the PresContext + const nscoord onePixel = AppUnitsPerCSSPixel(); + + // insurance against infinite reflow, when reflowing less than a pixel + // XXX Shouldn't we do something more friendly when invalid margins + // are set? + if (maxSize.width < onePixel || maxSize.height < onePixel) { + NS_WARNING("Reflow aborted; no space for content"); + return {}; + } + + ReflowInput kidReflowInput(aPresContext, aPageReflowInput, frame, + LogicalSize(frame->GetWritingMode(), maxSize)); + kidReflowInput.mFlags.mIsTopOfPage = true; + kidReflowInput.mFlags.mTableIsSplittable = true; + + mPageContentMargin = aPresContext->GetDefaultPageMargin(); + + // Use the margins given in the @page rule if told to do so. + // We clamp to the paper's unwriteable margins to avoid clipping, *except* + // that we will respect a margin of zero if specified, assuming this means + // the document is intended to fit the paper size exactly, and the client is + // taking full responsibility for what happens around the edges. + if (mPD->mPrintSettings->GetHonorPageRuleMargins()) { + const auto& margin = kidReflowInput.mStyleMargin->mMargin; + for (const auto side : mozilla::AllPhysicalSides()) { + if (!margin.Get(side).IsAuto()) { + nscoord computed = kidReflowInput.ComputedPhysicalMargin().Side(side); + // Respecting a zero margin is particularly important when the client + // is PDF.js where the PDF already contains the margins. + if (computed == 0) { + mPageContentMargin.Side(side) = 0; + } else { + nscoord unwriteable = nsPresContext::CSSTwipsToAppUnits( + mPD->mPrintSettings->GetUnwriteableMarginInTwips().Side(side)); + mPageContentMargin.Side(side) = std::max( + kidReflowInput.ComputedPhysicalMargin().Side(side), unwriteable); + } + } + } + } + + nscoord maxWidth = maxSize.width - mPageContentMargin.LeftRight() / scale; + nscoord maxHeight; + if (maxSize.height == NS_UNCONSTRAINEDSIZE) { + maxHeight = NS_UNCONSTRAINEDSIZE; + } else { + maxHeight = maxSize.height - mPageContentMargin.TopBottom() / scale; + } + + // Check the width and height, if they're too small we reset the margins + // back to the default. + if (maxWidth < onePixel || maxHeight < onePixel) { + mPageContentMargin = aPresContext->GetDefaultPageMargin(); + maxWidth = maxSize.width - mPageContentMargin.LeftRight() / scale; + if (maxHeight != NS_UNCONSTRAINEDSIZE) { + maxHeight = maxSize.height - mPageContentMargin.TopBottom() / scale; + } + } + + // And if they're still too small, we give up. + if (maxWidth < onePixel || maxHeight < onePixel) { + NS_WARNING("Reflow aborted; no space for content"); + return {}; + } + + kidReflowInput.SetComputedWidth(maxWidth); + kidReflowInput.SetComputedHeight(maxHeight); + + // calc location of frame + nscoord xc = mPageContentMargin.left; + nscoord yc = mPageContentMargin.top; + + // Get the child's desired size + ReflowOutput kidOutput(kidReflowInput); + nsReflowStatus kidStatus; + ReflowChild(frame, aPresContext, kidOutput, kidReflowInput, xc, yc, + ReflowChildFlags::Default, kidStatus); + + // Place and size the child + FinishReflowChild(frame, aPresContext, kidOutput, &kidReflowInput, xc, yc, + ReflowChildFlags::Default); + + NS_ASSERTION(!kidStatus.IsFullyComplete() || !frame->GetNextInFlow(), + "bad child flow list"); + return kidStatus; +} + +void nsPageFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aReflowOutput, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsPageFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + MOZ_ASSERT(mPD, "Need a pointer to nsSharedPageData before reflow starts"); + + NS_ASSERTION( + mFrames.FirstChild() && mFrames.FirstChild()->IsPageContentFrame(), + "pageFrame must have a pageContentFrame child"); + + // Our status is the same as our child's. + aStatus = ReflowPageContent(aPresContext, aReflowInput); + + PR_PL(("PageFrame::Reflow %p ", this)); + PR_PL(("[%d,%d][%d,%d]\n", aReflowOutput.Width(), aReflowOutput.Height(), + aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); + + // Return our desired size + WritingMode wm = aReflowInput.GetWritingMode(); + aReflowOutput.ISize(wm) = aReflowInput.AvailableISize(); + if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) { + aReflowOutput.BSize(wm) = aReflowInput.AvailableBSize(); + } + + aReflowOutput.SetOverflowAreasToDesiredBounds(); + FinishAndStoreOverflow(&aReflowOutput); + + PR_PL(("PageFrame::Reflow %p ", this)); + PR_PL(("[%d,%d]\n", aReflowInput.AvailableWidth(), + aReflowInput.AvailableHeight())); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aReflowOutput); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsPageFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"Page"_ns, aResult); +} +#endif + +void nsPageFrame::ProcessSpecialCodes(const nsString& aStr, nsString& aNewStr) { + aNewStr = aStr; + + // Search to see if the &D code is in the string + // then subst in the current date/time + constexpr auto kDate = u"&D"_ns; + if (aStr.Find(kDate) != kNotFound) { + aNewStr.ReplaceSubstring(kDate, mPD->mDateTimeStr); + } + + // NOTE: Must search for &PT before searching for &P + // + // Search to see if the "page number and page" total code are in the string + // and replace the page number and page total code with the actual + // values + constexpr auto kPageAndTotal = u"&PT"_ns; + if (aStr.Find(kPageAndTotal) != kNotFound) { + nsAutoString uStr; + nsTextFormatter::ssprintf(uStr, mPD->mPageNumAndTotalsFormat.get(), + mPageNum, mPD->mRawNumPages); + aNewStr.ReplaceSubstring(kPageAndTotal, uStr); + } + + // Search to see if the page number code is in the string + // and replace the page number code with the actual value + constexpr auto kPage = u"&P"_ns; + if (aStr.Find(kPage) != kNotFound) { + nsAutoString uStr; + nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), mPageNum); + aNewStr.ReplaceSubstring(kPage, uStr); + } + + constexpr auto kTitle = u"&T"_ns; + if (aStr.Find(kTitle) != kNotFound) { + aNewStr.ReplaceSubstring(kTitle, mPD->mDocTitle); + } + + constexpr auto kDocURL = u"&U"_ns; + if (aStr.Find(kDocURL) != kNotFound) { + aNewStr.ReplaceSubstring(kDocURL, mPD->mDocURL); + } + + constexpr auto kPageTotal = u"&L"_ns; + if (aStr.Find(kPageTotal) != kNotFound) { + nsAutoString uStr; + nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), + mPD->mRawNumPages); + aNewStr.ReplaceSubstring(kPageTotal, uStr); + } +} + +//------------------------------------------------------------------------------ +nscoord nsPageFrame::GetXPosition(gfxContext& aRenderingContext, + nsFontMetrics& aFontMetrics, + const nsRect& aRect, int32_t aJust, + const nsString& aStr) { + nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi( + aStr, this, aFontMetrics, aRenderingContext); + nscoord x = aRect.x; + switch (aJust) { + case nsIPrintSettings::kJustLeft: + x += mPD->mEdgePaperMargin.left; + break; + + case nsIPrintSettings::kJustCenter: + x += (aRect.width - width) / 2; + break; + + case nsIPrintSettings::kJustRight: + x += aRect.width - width - mPD->mEdgePaperMargin.right; + break; + } // switch + + return x; +} + +// Draw a header or footer +// @param aRenderingContext - rendering content to draw into +// @param aHeaderFooter - indicates whether it is a header or footer +// @param aStrLeft - string for the left header or footer; can be empty +// @param aStrCenter - string for the center header or footer; can be empty +// @param aStrRight - string for the right header or footer; can be empty +// @param aRect - the rect of the page +// @param aAscent - the ascent of the font +// @param aHeight - the height of the font +void nsPageFrame::DrawHeaderFooter( + gfxContext& aRenderingContext, nsFontMetrics& aFontMetrics, + nsHeaderFooterEnum aHeaderFooter, const nsString& aStrLeft, + const nsString& aStrCenter, const nsString& aStrRight, const nsRect& aRect, + nscoord aAscent, nscoord aHeight) { + int32_t numStrs = 0; + if (!aStrLeft.IsEmpty()) numStrs++; + if (!aStrCenter.IsEmpty()) numStrs++; + if (!aStrRight.IsEmpty()) numStrs++; + + if (numStrs == 0) return; + nscoord strSpace = aRect.width / numStrs; + + if (!aStrLeft.IsEmpty()) { + DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter, + nsIPrintSettings::kJustLeft, aStrLeft, aRect, aAscent, + aHeight, strSpace); + } + if (!aStrCenter.IsEmpty()) { + DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter, + nsIPrintSettings::kJustCenter, aStrCenter, aRect, aAscent, + aHeight, strSpace); + } + if (!aStrRight.IsEmpty()) { + DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter, + nsIPrintSettings::kJustRight, aStrRight, aRect, aAscent, + aHeight, strSpace); + } +} + +// Draw a header or footer string +// @param aRenderingContext - rendering context to draw into +// @param aHeaderFooter - indicates whether it is a header or footer +// @param aJust - indicates where the string is located within the header/footer +// @param aStr - the string to be drawn +// @param aRect - the rect of the page +// @param aHeight - the height of the font +// @param aAscent - the ascent of the font +// @param aWidth - available width for the string +void nsPageFrame::DrawHeaderFooter(gfxContext& aRenderingContext, + nsFontMetrics& aFontMetrics, + nsHeaderFooterEnum aHeaderFooter, + int32_t aJust, const nsString& aStr, + const nsRect& aRect, nscoord aAscent, + nscoord aHeight, nscoord aWidth) { + nscoord contentWidth = + aWidth - (mPD->mEdgePaperMargin.left + mPD->mEdgePaperMargin.right); + + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + if ((aHeaderFooter == eHeader && aHeight < mPageContentMargin.top) || + (aHeaderFooter == eFooter && aHeight < mPageContentMargin.bottom)) { + nsAutoString str; + ProcessSpecialCodes(aStr, str); + + int32_t indx; + int32_t textWidth = 0; + const char16_t* text = str.get(); + + int32_t len = (int32_t)str.Length(); + if (len == 0) { + return; // bail is empty string + } + // find how much text fits, the "position" is the size of the available area + if (nsLayoutUtils::BinarySearchForPosition( + drawTarget, aFontMetrics, text, 0, 0, 0, len, int32_t(contentWidth), + indx, textWidth)) { + if (indx < len - 1) { + // we can't fit in all the text + if (indx > 3) { + // But we can fit in at least 4 chars. Show all but 3 of them, then + // an ellipsis. + // XXXbz for non-plane0 text, this may be cutting things in the + // middle of a codepoint! Also, we have no guarantees that the three + // dots will fit in the space the three chars we removed took up with + // these font metrics! + str.Truncate(indx - 3); + str.AppendLiteral("..."); + } else { + // We can only fit 3 or fewer chars. Just show nothing + str.Truncate(); + } + } + } else { + return; // bail if couldn't find the correct length + } + + if (HasRTLChars(str)) { + PresContext()->SetBidiEnabled(); + } + + // calc the x and y positions of the text + nscoord x = + GetXPosition(aRenderingContext, aFontMetrics, aRect, aJust, str); + nscoord y; + if (aHeaderFooter == eHeader) { + y = aRect.y + mPD->mEdgePaperMargin.top; + } else { + y = aRect.YMost() - aHeight - mPD->mEdgePaperMargin.bottom; + } + + // set up new clip and draw the text + aRenderingContext.Save(); + aRenderingContext.Clip(NSRectToSnappedRect( + aRect, PresContext()->AppUnitsPerDevPixel(), *drawTarget)); + aRenderingContext.SetColor(sRGBColor::OpaqueBlack()); + nsLayoutUtils::DrawString(this, aFontMetrics, &aRenderingContext, str.get(), + str.Length(), nsPoint(x, y + aAscent), nullptr, + DrawStringFlags::ForceHorizontal); + aRenderingContext.Restore(); + } +} + +/** + * Remove all leaf display items that are not for descendants of + * aBuilder->GetReferenceFrame() from aList. + * @param aPage the page we're constructing the display list for + * @param aExtraPage the page we constructed aList for + * @param aList the list that is modified in-place + */ +static void PruneDisplayListForExtraPage(nsDisplayListBuilder* aBuilder, + nsPageFrame* aPage, + nsIFrame* aExtraPage, + nsDisplayList* aList) { + nsDisplayList newList; + + while (true) { + nsDisplayItem* i = aList->RemoveBottom(); + if (!i) break; + nsDisplayList* subList = i->GetSameCoordinateSystemChildren(); + if (subList) { + PruneDisplayListForExtraPage(aBuilder, aPage, aExtraPage, subList); + i->UpdateBounds(aBuilder); + } else { + nsIFrame* f = i->Frame(); + if (!nsLayoutUtils::IsProperAncestorFrameCrossDoc(aPage, f)) { + // We're throwing this away so call its destructor now. The memory + // is owned by aBuilder which destroys all items at once. + i->Destroy(aBuilder); + continue; + } + } + newList.AppendToTop(i); + } + aList->AppendToTop(&newList); +} + +static void BuildDisplayListForExtraPage(nsDisplayListBuilder* aBuilder, + nsPageFrame* aPage, + nsIFrame* aExtraPage, + nsDisplayList* aList) { + // The only content in aExtraPage we care about is out-of-flow content from + // aPage, whose placeholders have occurred in aExtraPage. If + // NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO is not set, then aExtraPage has + // no such content. + if (!aExtraPage->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + return; + } + nsDisplayList list; + aExtraPage->BuildDisplayListForStackingContext(aBuilder, &list); + PruneDisplayListForExtraPage(aBuilder, aPage, aExtraPage, &list); + aList->AppendToTop(&list); +} + +static gfx::Matrix4x4 ComputePageTransform(nsIFrame* aFrame, + float aAppUnitsPerPixel) { + float scale = aFrame->PresContext()->GetPageScale(); + return gfx::Matrix4x4::Scaling(scale, scale, 1); +} + +class nsDisplayHeaderFooter final : public nsPaintedDisplayItem { + public: + nsDisplayHeaderFooter(nsDisplayListBuilder* aBuilder, nsPageFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayHeaderFooter); + } + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayHeaderFooter) + + virtual void Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) override { +#ifdef DEBUG + nsPageFrame* pageFrame = do_QueryFrame(mFrame); + MOZ_ASSERT(pageFrame, "We should have an nsPageFrame"); +#endif + static_cast<nsPageFrame*>(mFrame)->PaintHeaderFooter( + *aCtx, ToReferenceFrame(), IsSubpixelAADisabled()); + } + NS_DISPLAY_DECL_NAME("HeaderFooter", TYPE_HEADER_FOOTER) + + virtual nsRect GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const override { + bool snap; + return GetBounds(aBuilder, &snap); + } +}; + +static void PaintMarginGuides(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const nsRect& aDirtyRect, nsPoint aPt) { + // Set up parameters needed to draw the guides: we draw them in blue, + // using 2px-long dashes with 2px separation and a line width of 0.5px. + // Drawing is antialiased, so on a non-hidpi screen where the line width is + // less than one device pixel, it doesn't disappear but renders fainter + // than a solid 1px-wide line would be. + // (In many cases, the entire preview is scaled down so that the guides + // will be nominally less than 1 dev px even on a hidpi screen, resulting + // in lighter antialiased rendering so they don't dominate the page.) + ColorPattern pattern(ToDeviceColor(sRGBColor(0.0f, 0.0f, 1.0f))); + Float dashes[] = {2.0f, 2.0f}; + StrokeOptions stroke(/* line width (in CSS px) */ 0.5f, + JoinStyle::MITER_OR_BEVEL, CapStyle::BUTT, + /* mitre limit (default, not used) */ 10.0f, + /* set dash pattern of 2px stroke, 2px gap */ + ArrayLength(dashes), dashes, + /* dash offset */ 0.0f); + DrawOptions options; + + MOZ_RELEASE_ASSERT(aFrame->IsPageFrame()); + const nsMargin& margin = + static_cast<nsPageFrame*>(aFrame)->GetUsedPageContentMargin(); + int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); + + // Get the frame's rect and inset by the margins to get the edges of the + // content area, where we want to draw the guides. + // We draw in two stages, first applying the top/bottom margins and drawing + // the horizontal guides across the full width of the page. + nsRect rect(aPt, aFrame->GetSize()); + rect.Deflate(nsMargin(margin.top, 0, margin.bottom, 0)); + Rect r = NSRectToRect(rect, appUnitsPerDevPx); + aDrawTarget->StrokeLine(r.TopLeft(), r.TopRight(), pattern, stroke, options); + aDrawTarget->StrokeLine(r.BottomLeft(), r.BottomRight(), pattern, stroke, + options); + + // Then reset rect, apply the left/right margins, and draw vertical guides + // extending the full height of the page. + rect = nsRect(aPt, aFrame->GetSize()); + rect.Deflate(nsMargin(0, margin.right, 0, margin.left)); + r = NSRectToRect(rect, appUnitsPerDevPx); + aDrawTarget->StrokeLine(r.TopLeft(), r.BottomLeft(), pattern, stroke, + options); + aDrawTarget->StrokeLine(r.TopRight(), r.BottomRight(), pattern, stroke, + options); +} + +//------------------------------------------------------------------------------ +using PageAndOffset = std::pair<nsPageContentFrame*, nscoord>; + +// Returns the previous continuation PageContentFrames that have overflow areas, +// and their offsets to the top of the given PageContentFrame |aPage|. Since the +// iteration is done backwards, the returned pages are arranged in descending +// order of page number. +static nsTArray<PageAndOffset> GetPreviousPagesWithOverflow( + nsPageContentFrame* aPage) { + nsTArray<PageAndOffset> pages(8); + + auto GetPreviousPageContentFrame = [](nsPageContentFrame* aPageCF) { + nsIFrame* prevCont = aPageCF->GetPrevContinuation(); + MOZ_ASSERT(!prevCont || prevCont->IsPageContentFrame(), + "Expected nsPageContentFrame or nullptr"); + + return static_cast<nsPageContentFrame*>(prevCont); + }; + + nsPageContentFrame* pageCF = aPage; + // The collective height of all prev-continuations we've traversed so far: + nscoord offsetToCurrentPageBStart = 0; + const auto wm = pageCF->GetWritingMode(); + while ((pageCF = GetPreviousPageContentFrame(pageCF))) { + offsetToCurrentPageBStart += pageCF->BSize(wm); + + if (pageCF->HasOverflowAreas()) { + pages.EmplaceBack(pageCF, offsetToCurrentPageBStart); + } + } + + return pages; +} + +static void BuildPreviousPageOverflow(nsDisplayListBuilder* aBuilder, + nsPageFrame* aPageFrame, + nsPageContentFrame* aCurrentPageCF, + const nsDisplayListSet& aLists) { + const auto previousPagesAndOffsets = + GetPreviousPagesWithOverflow(aCurrentPageCF); + + const auto wm = aCurrentPageCF->GetWritingMode(); + for (const PageAndOffset& pair : Reversed(previousPagesAndOffsets)) { + auto* prevPageCF = pair.first; + const nscoord offsetToCurrentPageBStart = pair.second; + // Only scrollable overflow create new pages, not ink overflow. + const LogicalRect scrollableOverflow( + wm, prevPageCF->ScrollableOverflowRectRelativeToSelf(), + prevPageCF->GetSize()); + const auto remainingOverflow = + scrollableOverflow.BEnd(wm) - offsetToCurrentPageBStart; + if (remainingOverflow <= 0) { + continue; + } + + // This rect represents the piece of prevPageCF's overflow that ends up on + // the current pageContentFrame (in prevPageCF's coordinate system). + // Note that we use InkOverflow here since this is for painting. + LogicalRect overflowRect(wm, prevPageCF->InkOverflowRectRelativeToSelf(), + prevPageCF->GetSize()); + overflowRect.BStart(wm) = offsetToCurrentPageBStart; + overflowRect.BSize(wm) = std::min(remainingOverflow, prevPageCF->BSize(wm)); + + { + // Convert the overflowRect to the coordinate system of aPageFrame, and + // set it as the visible rect for display list building. + const nsRect visibleRect = + overflowRect.GetPhysicalRect(wm, aPageFrame->GetSize()) + + prevPageCF->GetOffsetTo(aPageFrame); + nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( + aBuilder, aPageFrame, visibleRect, visibleRect); + + // This part is tricky. Because display items are positioned based on the + // frame tree, building a display list for the previous page yields + // display items that are outside of the current page bounds. + // To fix that, an additional reference frame offset is added, which + // shifts the display items down (block axis) as if the current and + // previous page were one long page in the same coordinate system. + const nsSize containerSize = aPageFrame->GetSize(); + LogicalPoint pageOffset(wm, aCurrentPageCF->GetOffsetTo(prevPageCF), + containerSize); + pageOffset.B(wm) -= offsetToCurrentPageBStart; + buildingForChild.SetAdditionalOffset( + pageOffset.GetPhysicalPoint(wm, containerSize)); + + aPageFrame->BuildDisplayListForChild(aBuilder, prevPageCF, aLists); + } + } +} + +void nsPageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + nsDisplayListCollection set(aBuilder); + + nsPresContext* pc = PresContext(); + + nsIFrame* child = mFrames.FirstChild(); + float scale = pc->GetPageScale(); + nsRect clipRect(nsPoint(0, 0), child->GetSize()); + // Note: this computation matches how we compute maxSize.height + // in nsPageFrame::Reflow + nscoord expectedPageContentHeight = NSToCoordCeil(GetSize().height / scale); + if (clipRect.height > expectedPageContentHeight) { + // We're doing print-selection, with one long page-content frame. + // Clip to the appropriate page-content slice for the current page. + NS_ASSERTION(mPageNum > 0, "page num should be positive"); + // Note: The pageContentFrame's y-position has been set such that a zero + // y-value matches the top edge of the current page. So, to clip to the + // current page's content (in coordinates *relative* to the page content + // frame), we just negate its y-position and add the top margin. + // + // FIXME(emilio): Rather than the default margin, this should probably use + // mPageContentMargin? + clipRect.y = NSToCoordCeil( + (-child->GetRect().y + pc->GetDefaultPageMargin().top) / scale); + clipRect.height = expectedPageContentHeight; + NS_ASSERTION(clipRect.y < child->GetSize().height, + "Should be clipping to region inside the page content bounds"); + } + clipRect += aBuilder->ToReferenceFrame(child); + + nsDisplayList content; + { + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + + // Overwrite current clip, since we're going to wrap in a transform + // and the current clip is no longer meaningful. + clipState.Clear(); + clipState.ClipContainingBlockDescendants(clipRect); + + MOZ_ASSERT(child->IsPageContentFrame(), "unexpected child frame type"); + auto* currentPageCF = static_cast<nsPageContentFrame*>(child); + + if (StaticPrefs::layout_display_list_improve_fragmentation() && + mPageNum <= 255) { + nsDisplayListBuilder::AutoPageNumberSetter p(aBuilder, mPageNum); + BuildPreviousPageOverflow(aBuilder, this, currentPageCF, set); + } + + // Set the visible rect to scrollable overflow rect of the child + // nsPageContentFrame in parent nsPageFrame coordinate space. + const nsRect childOverflowRect = + child->ScrollableOverflowRectRelativeToSelf(); + const nsRect visibleRect = childOverflowRect + child->GetOffsetTo(this); + + nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( + aBuilder, this, visibleRect, visibleRect); + BuildDisplayListForChild(aBuilder, child, set); + + set.SerializeWithCorrectZOrder(&content, child->GetContent()); + + // We may need to paint out-of-flow frames whose placeholders are + // on other pages. Add those pages to our display list. Note that + // out-of-flow frames can't be placed after their placeholders so + // we don't have to process earlier pages. The display lists for + // these extra pages are pruned so that only display items for the + // page we currently care about (which we would have reached by + // following placeholders to their out-of-flows) end up on the list. + // + // Stacking context frames that wrap content on their normal page, + // as well as OOF content for this page will have their container + // items duplicated. We tell the builder to include our page number + // in the unique key for any extra page items so that they can be + // differentiated from the ones created on the normal page. + NS_ASSERTION(mPageNum <= 255, "Too many pages to handle OOFs"); + if (mPageNum <= 255) { + nsDisplayListBuilder::AutoPageNumberSetter p(aBuilder, mPageNum); + // The static_cast here is technically unnecessary, but it helps + // devirtualize the GetNextContinuation() function call if pcf has a + // concrete type (with an inherited `final` GetNextContinuation() impl). + auto* pageCF = currentPageCF; + while ((pageCF = static_cast<nsPageContentFrame*>( + pageCF->GetNextContinuation()))) { + nsRect childVisible = childOverflowRect + child->GetOffsetTo(pageCF); + + nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( + aBuilder, pageCF, childVisible, childVisible); + BuildDisplayListForExtraPage(aBuilder, this, pageCF, &content); + } + } + + // Invoke AutoBuildingDisplayList to ensure that the correct visibleRect + // is used to compute the visible rect if AddCanvasBackgroundColorItem + // creates a display item. + nsDisplayListBuilder::AutoBuildingDisplayList building( + aBuilder, child, childOverflowRect, childOverflowRect); + + // Add the canvas background color to the bottom of the list. This + // happens after we've built the list so that AddCanvasBackgroundColorItem + // can monkey with the contents if necessary. + nsRect backgroundRect = + nsRect(aBuilder->ToReferenceFrame(child), child->GetSize()); + + pc->GetPresShell()->AddCanvasBackgroundColorItem( + aBuilder, &content, child, backgroundRect, NS_RGBA(0, 0, 0, 0)); + } + + content.AppendNewToTop<nsDisplayTransform>(aBuilder, child, &content, + content.GetBuildingRect(), + ::ComputePageTransform); + + if (pc->IsRootPaginatedDocument()) { + content.AppendNewToTop<nsDisplayHeaderFooter>(aBuilder, this); + + // For print-preview, show margin guides if requested in the settings. + if (pc->Type() == nsPresContext::eContext_PrintPreview && + mPD->mPrintSettings->GetShowMarginGuides()) { + content.AppendNewToTop<nsDisplayGeneric>( + aBuilder, this, PaintMarginGuides, "MarginGuides", + DisplayItemType::TYPE_MARGIN_GUIDES); + } + } + + aLists.Content()->AppendToTop(&content); +} + +//------------------------------------------------------------------------------ +void nsPageFrame::DeterminePageNum() { + // If we have no previous continuation, we're page 1. Otherwise, we're + // just one more than our previous continuation's page number. + auto* prevContinuation = static_cast<nsPageFrame*>(GetPrevContinuation()); + mPageNum = prevContinuation ? prevContinuation->GetPageNum() + 1 : 1; +} + +void nsPageFrame::PaintHeaderFooter(gfxContext& aRenderingContext, nsPoint aPt, + bool aDisableSubpixelAA) { + nsPresContext* pc = PresContext(); + + nsRect rect(aPt, mRect.Size()); + aRenderingContext.SetColor(sRGBColor::OpaqueBlack()); + + DrawTargetAutoDisableSubpixelAntialiasing disable( + aRenderingContext.GetDrawTarget(), aDisableSubpixelAA); + + // Get the FontMetrics to determine width.height of strings + nsFontMetrics::Params params; + params.userFontSet = pc->GetUserFontSet(); + params.textPerf = pc->GetTextPerfMetrics(); + params.fontStats = pc->GetFontMatchingStats(); + params.featureValueLookup = pc->GetFontFeatureValuesLookup(); + RefPtr<nsFontMetrics> fontMet = + pc->DeviceContext()->GetMetricsFor(mPD->mHeadFootFont, params); + + nscoord ascent = fontMet->MaxAscent(); + nscoord visibleHeight = fontMet->MaxHeight(); + + // print document headers and footers + nsString headerLeft, headerCenter, headerRight; + mPD->mPrintSettings->GetHeaderStrLeft(headerLeft); + mPD->mPrintSettings->GetHeaderStrCenter(headerCenter); + mPD->mPrintSettings->GetHeaderStrRight(headerRight); + DrawHeaderFooter(aRenderingContext, *fontMet, eHeader, headerLeft, + headerCenter, headerRight, rect, ascent, visibleHeight); + + nsString footerLeft, footerCenter, footerRight; + mPD->mPrintSettings->GetFooterStrLeft(footerLeft); + mPD->mPrintSettings->GetFooterStrCenter(footerCenter); + mPD->mPrintSettings->GetFooterStrRight(footerRight); + DrawHeaderFooter(aRenderingContext, *fontMet, eFooter, footerLeft, + footerCenter, footerRight, rect, ascent, visibleHeight); +} + +void nsPageFrame::SetSharedPageData(nsSharedPageData* aPD) { + mPD = aPD; + // Set the shared data into the page frame before reflow + nsPageContentFrame* pcf = + static_cast<nsPageContentFrame*>(mFrames.FirstChild()); + if (pcf) { + pcf->SetSharedPageData(mPD); + } +} + +void nsPageFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray<OwnedAnonBox>& aResult) { + MOZ_ASSERT(mFrames.FirstChild() && mFrames.FirstChild()->IsPageContentFrame(), + "pageFrame must have a pageContentFrame child"); + aResult.AppendElement(mFrames.FirstChild()); +} + +nsIFrame* NS_NewPageBreakFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + MOZ_ASSERT(aPresShell, "null PresShell"); + // check that we are only creating page break frames when printing + NS_ASSERTION(aPresShell->GetPresContext()->IsPaginated(), + "created a page break frame while not printing"); + + return new (aPresShell) + nsPageBreakFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsPageBreakFrame) + +nsPageBreakFrame::nsPageBreakFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsLeafFrame(aStyle, aPresContext, kClassID), mHaveReflowed(false) {} + +nsPageBreakFrame::~nsPageBreakFrame() = default; + +nscoord nsPageBreakFrame::GetIntrinsicISize() { + return nsPresContext::CSSPixelsToAppUnits(1); +} + +nscoord nsPageBreakFrame::GetIntrinsicBSize() { return 0; } + +void nsPageBreakFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aReflowOutput, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + DO_GLOBAL_REFLOW_COUNT("nsPageBreakFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + // Override reflow, since we don't want to deal with what our + // computed values are. + WritingMode wm = aReflowInput.GetWritingMode(); + nscoord bSize = aReflowInput.AvailableBSize(); + if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { + bSize = nscoord(0); + } else if (GetContent()->IsHTMLElement(nsGkAtoms::legend)) { + // If this is a page break frame for a _rendered legend_ then it should be + // ignored since these frames are inserted inside the fieldset's inner + // frame and thus "misplaced". nsFieldSetFrame::Reflow deals with these + // forced breaks explicitly instead. + nsContainerFrame* parent = GetParent(); + if (parent && + parent->Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) { + while ((parent = parent->GetParent())) { + if (nsFieldSetFrame* fieldset = do_QueryFrame(parent)) { + auto* legend = fieldset->GetLegend(); + if (legend && legend->GetContent() == GetContent()) { + bSize = nscoord(0); + } + break; + } + } + } + } + LogicalSize finalSize(wm, GetIntrinsicISize(), bSize); + // round the height down to the nearest pixel + // XXX(mats) why??? + finalSize.BSize(wm) -= + finalSize.BSize(wm) % nsPresContext::CSSPixelsToAppUnits(1); + aReflowOutput.SetSize(wm, finalSize); + + // Note: not using NS_FRAME_FIRST_REFLOW here, since it's not clear whether + // DidReflow will always get called before the next Reflow() call. + mHaveReflowed = true; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsPageBreakFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"PageBreak"_ns, aResult); +} +#endif |