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 #include "nsPageFrame.h"
8 
9 #include "mozilla/AppUnits.h"
10 #include "mozilla/PresShell.h"
11 #include "mozilla/StaticPrefs_layout.h"
12 #include "mozilla/gfx/2D.h"
13 #include "gfxContext.h"
14 #include "nsDeviceContext.h"
15 #include "nsFontMetrics.h"
16 #include "nsIFrame.h"
17 #include "nsLayoutUtils.h"
18 #include "nsPresContext.h"
19 #include "nsGkAtoms.h"
20 #include "nsFieldSetFrame.h"
21 #include "nsPageContentFrame.h"
22 #include "nsDisplayList.h"
23 #include "nsPageSequenceFrame.h"  // for nsSharedPageData
24 #include "nsTextFormatter.h"      // for page number localization formatting
25 #include "nsBidiUtils.h"
26 #include "nsIPrintSettings.h"
27 
28 #include "mozilla/Logging.h"
29 extern mozilla::LazyLogModule gLayoutPrintingLog;
30 #define PR_PL(_p1) MOZ_LOG(gLayoutPrintingLog, mozilla::LogLevel::Debug, _p1)
31 
32 using namespace mozilla;
33 using namespace mozilla::gfx;
34 
NS_NewPageFrame(PresShell * aPresShell,ComputedStyle * aStyle)35 nsPageFrame* NS_NewPageFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
36   return new (aPresShell) nsPageFrame(aStyle, aPresShell->GetPresContext());
37 }
38 
39 NS_IMPL_FRAMEARENA_HELPERS(nsPageFrame)
40 
NS_QUERYFRAME_HEAD(nsPageFrame)41 NS_QUERYFRAME_HEAD(nsPageFrame)
42   NS_QUERYFRAME_ENTRY(nsPageFrame)
43 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
44 
45 nsPageFrame::nsPageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
46     : nsContainerFrame(aStyle, aPresContext, kClassID) {}
47 
48 nsPageFrame::~nsPageFrame() = default;
49 
ReflowPageContent(nsPresContext * aPresContext,const ReflowInput & aPageReflowInput)50 nsReflowStatus nsPageFrame::ReflowPageContent(
51     nsPresContext* aPresContext, const ReflowInput& aPageReflowInput) {
52   nsIFrame* const frame = PageContentFrame();
53   // XXX Pay attention to the page's border and padding...
54   //
55   // Reflow our ::-moz-page-content frame, allowing it only to be as big as we
56   // are (minus margins).
57   nsSize maxSize = ComputePageSize();
58 
59   float scale = aPresContext->GetPageScale();
60   // When the reflow size is NS_UNCONSTRAINEDSIZE it means we are reflowing
61   // a single page to print selection. So this means we want to use
62   // NS_UNCONSTRAINEDSIZE without altering it.
63   //
64   // FIXME(emilio): Is this still true?
65   maxSize.width = NSToCoordCeil(maxSize.width / scale);
66   if (maxSize.height != NS_UNCONSTRAINEDSIZE) {
67     maxSize.height = NSToCoordCeil(maxSize.height / scale);
68   }
69 
70   // Get the number of Twips per pixel from the PresContext
71   const nscoord onePixel = AppUnitsPerCSSPixel();
72 
73   // insurance against infinite reflow, when reflowing less than a pixel
74   // XXX Shouldn't we do something more friendly when invalid margins
75   //     are set?
76   if (maxSize.width < onePixel || maxSize.height < onePixel) {
77     NS_WARNING("Reflow aborted; no space for content");
78     return {};
79   }
80 
81   ReflowInput kidReflowInput(aPresContext, aPageReflowInput, frame,
82                              LogicalSize(frame->GetWritingMode(), maxSize));
83   kidReflowInput.mFlags.mIsTopOfPage = true;
84   kidReflowInput.mFlags.mTableIsSplittable = true;
85 
86   mPageContentMargin = aPresContext->GetDefaultPageMargin();
87 
88   // Use the margins given in the @page rule if told to do so.
89   // We clamp to the paper's unwriteable margins to avoid clipping, *except*
90   // that we will respect a margin of zero if specified, assuming this means
91   // the document is intended to fit the paper size exactly, and the client is
92   // taking full responsibility for what happens around the edges.
93   if (mPD->mPrintSettings->GetHonorPageRuleMargins()) {
94     const auto& margin = kidReflowInput.mStyleMargin->mMargin;
95     for (const auto side : mozilla::AllPhysicalSides()) {
96       if (!margin.Get(side).IsAuto()) {
97         nscoord computed = kidReflowInput.ComputedPhysicalMargin().Side(side);
98         // Respecting a zero margin is particularly important when the client
99         // is PDF.js where the PDF already contains the margins.
100         if (computed == 0) {
101           mPageContentMargin.Side(side) = 0;
102         } else {
103           nscoord unwriteable = nsPresContext::CSSTwipsToAppUnits(
104               mPD->mPrintSettings->GetUnwriteableMarginInTwips().Side(side));
105           mPageContentMargin.Side(side) = std::max(
106               kidReflowInput.ComputedPhysicalMargin().Side(side), unwriteable);
107         }
108       }
109     }
110   }
111 
112   nscoord maxWidth = maxSize.width - mPageContentMargin.LeftRight() / scale;
113   nscoord maxHeight;
114   if (maxSize.height == NS_UNCONSTRAINEDSIZE) {
115     maxHeight = NS_UNCONSTRAINEDSIZE;
116   } else {
117     maxHeight = maxSize.height - mPageContentMargin.TopBottom() / scale;
118   }
119 
120   // Check the width and height, if they're too small we reset the margins
121   // back to the default.
122   if (maxWidth < onePixel || maxHeight < onePixel) {
123     mPageContentMargin = aPresContext->GetDefaultPageMargin();
124     maxWidth = maxSize.width - mPageContentMargin.LeftRight() / scale;
125     if (maxHeight != NS_UNCONSTRAINEDSIZE) {
126       maxHeight = maxSize.height - mPageContentMargin.TopBottom() / scale;
127     }
128   }
129 
130   // And if they're still too small, we give up.
131   if (maxWidth < onePixel || maxHeight < onePixel) {
132     NS_WARNING("Reflow aborted; no space for content");
133     return {};
134   }
135 
136   kidReflowInput.SetComputedWidth(maxWidth);
137   kidReflowInput.SetComputedHeight(maxHeight);
138 
139   // calc location of frame
140   nscoord xc = mPageContentMargin.left;
141   nscoord yc = mPageContentMargin.top;
142 
143   // Get the child's desired size
144   ReflowOutput kidOutput(kidReflowInput);
145   nsReflowStatus kidStatus;
146   ReflowChild(frame, aPresContext, kidOutput, kidReflowInput, xc, yc,
147               ReflowChildFlags::Default, kidStatus);
148 
149   // Place and size the child
150   FinishReflowChild(frame, aPresContext, kidOutput, &kidReflowInput, xc, yc,
151                     ReflowChildFlags::Default);
152 
153   NS_ASSERTION(!kidStatus.IsFullyComplete() || !frame->GetNextInFlow(),
154                "bad child flow list");
155   return kidStatus;
156 }
157 
Reflow(nsPresContext * aPresContext,ReflowOutput & aReflowOutput,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)158 void nsPageFrame::Reflow(nsPresContext* aPresContext,
159                          ReflowOutput& aReflowOutput,
160                          const ReflowInput& aReflowInput,
161                          nsReflowStatus& aStatus) {
162   MarkInReflow();
163   DO_GLOBAL_REFLOW_COUNT("nsPageFrame");
164   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
165   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
166   MOZ_ASSERT(mPD, "Need a pointer to nsSharedPageData before reflow starts");
167 
168   // Our status is the same as our child's.
169   aStatus = ReflowPageContent(aPresContext, aReflowInput);
170 
171   PR_PL(("PageFrame::Reflow %p ", this));
172   PR_PL(("[%d,%d][%d,%d]\n", aReflowOutput.Width(), aReflowOutput.Height(),
173          aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
174 
175   // Return our desired size
176   WritingMode wm = aReflowInput.GetWritingMode();
177   aReflowOutput.ISize(wm) = aReflowInput.AvailableISize();
178   if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
179     aReflowOutput.BSize(wm) = aReflowInput.AvailableBSize();
180   }
181 
182   aReflowOutput.SetOverflowAreasToDesiredBounds();
183   FinishAndStoreOverflow(&aReflowOutput);
184 
185   PR_PL(("PageFrame::Reflow %p ", this));
186   PR_PL(("[%d,%d]\n", aReflowInput.AvailableWidth(),
187          aReflowInput.AvailableHeight()));
188 
189   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aReflowOutput);
190 }
191 
192 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const193 nsresult nsPageFrame::GetFrameName(nsAString& aResult) const {
194   return MakeFrameName(u"Page"_ns, aResult);
195 }
196 #endif
197 
ProcessSpecialCodes(const nsString & aStr,nsString & aNewStr)198 void nsPageFrame::ProcessSpecialCodes(const nsString& aStr, nsString& aNewStr) {
199   aNewStr = aStr;
200 
201   // Search to see if the &D code is in the string
202   // then subst in the current date/time
203   constexpr auto kDate = u"&D"_ns;
204   if (aStr.Find(kDate) != kNotFound) {
205     aNewStr.ReplaceSubstring(kDate, mPD->mDateTimeStr);
206   }
207 
208   // NOTE: Must search for &PT before searching for &P
209   //
210   // Search to see if the "page number and page" total code are in the string
211   // and replace the page number and page total code with the actual
212   // values
213   constexpr auto kPageAndTotal = u"&PT"_ns;
214   if (aStr.Find(kPageAndTotal) != kNotFound) {
215     nsAutoString uStr;
216     nsTextFormatter::ssprintf(uStr, mPD->mPageNumAndTotalsFormat.get(),
217                               mPageNum, mPD->mRawNumPages);
218     aNewStr.ReplaceSubstring(kPageAndTotal, uStr);
219   }
220 
221   // Search to see if the page number code is in the string
222   // and replace the page number code with the actual value
223   constexpr auto kPage = u"&P"_ns;
224   if (aStr.Find(kPage) != kNotFound) {
225     nsAutoString uStr;
226     nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), mPageNum);
227     aNewStr.ReplaceSubstring(kPage, uStr);
228   }
229 
230   constexpr auto kTitle = u"&T"_ns;
231   if (aStr.Find(kTitle) != kNotFound) {
232     aNewStr.ReplaceSubstring(kTitle, mPD->mDocTitle);
233   }
234 
235   constexpr auto kDocURL = u"&U"_ns;
236   if (aStr.Find(kDocURL) != kNotFound) {
237     aNewStr.ReplaceSubstring(kDocURL, mPD->mDocURL);
238   }
239 
240   constexpr auto kPageTotal = u"&L"_ns;
241   if (aStr.Find(kPageTotal) != kNotFound) {
242     nsAutoString uStr;
243     nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(),
244                               mPD->mRawNumPages);
245     aNewStr.ReplaceSubstring(kPageTotal, uStr);
246   }
247 }
248 
249 //------------------------------------------------------------------------------
GetXPosition(gfxContext & aRenderingContext,nsFontMetrics & aFontMetrics,const nsRect & aRect,int32_t aJust,const nsString & aStr)250 nscoord nsPageFrame::GetXPosition(gfxContext& aRenderingContext,
251                                   nsFontMetrics& aFontMetrics,
252                                   const nsRect& aRect, int32_t aJust,
253                                   const nsString& aStr) {
254   nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
255       aStr, this, aFontMetrics, aRenderingContext);
256   nscoord x = aRect.x;
257   switch (aJust) {
258     case nsIPrintSettings::kJustLeft:
259       x += mPD->mEdgePaperMargin.left;
260       break;
261 
262     case nsIPrintSettings::kJustCenter:
263       x += (aRect.width - width) / 2;
264       break;
265 
266     case nsIPrintSettings::kJustRight:
267       x += aRect.width - width - mPD->mEdgePaperMargin.right;
268       break;
269   }  // switch
270 
271   return x;
272 }
273 
274 // Draw a header or footer
275 // @param aRenderingContext - rendering content to draw into
276 // @param aHeaderFooter - indicates whether it is a header or footer
277 // @param aStrLeft - string for the left header or footer; can be empty
278 // @param aStrCenter - string for the center header or footer; can be empty
279 // @param aStrRight - string for the right header or footer; can be empty
280 // @param aRect - the rect of the page
281 // @param aAscent - the ascent of the font
282 // @param aHeight - the height of the font
DrawHeaderFooter(gfxContext & aRenderingContext,nsFontMetrics & aFontMetrics,nsHeaderFooterEnum aHeaderFooter,const nsString & aStrLeft,const nsString & aStrCenter,const nsString & aStrRight,const nsRect & aRect,nscoord aAscent,nscoord aHeight)283 void nsPageFrame::DrawHeaderFooter(
284     gfxContext& aRenderingContext, nsFontMetrics& aFontMetrics,
285     nsHeaderFooterEnum aHeaderFooter, const nsString& aStrLeft,
286     const nsString& aStrCenter, const nsString& aStrRight, const nsRect& aRect,
287     nscoord aAscent, nscoord aHeight) {
288   int32_t numStrs = 0;
289   if (!aStrLeft.IsEmpty()) numStrs++;
290   if (!aStrCenter.IsEmpty()) numStrs++;
291   if (!aStrRight.IsEmpty()) numStrs++;
292 
293   if (numStrs == 0) return;
294   const nscoord contentWidth =
295       aRect.width - (mPD->mEdgePaperMargin.left + mPD->mEdgePaperMargin.right);
296   const nscoord strSpace = contentWidth / numStrs;
297 
298   if (!aStrLeft.IsEmpty()) {
299     DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter,
300                      nsIPrintSettings::kJustLeft, aStrLeft, aRect, aAscent,
301                      aHeight, strSpace);
302   }
303   if (!aStrCenter.IsEmpty()) {
304     DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter,
305                      nsIPrintSettings::kJustCenter, aStrCenter, aRect, aAscent,
306                      aHeight, strSpace);
307   }
308   if (!aStrRight.IsEmpty()) {
309     DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter,
310                      nsIPrintSettings::kJustRight, aStrRight, aRect, aAscent,
311                      aHeight, strSpace);
312   }
313 }
314 
315 // Draw a header or footer string
316 // @param aRenderingContext - rendering context to draw into
317 // @param aHeaderFooter - indicates whether it is a header or footer
318 // @param aJust - indicates where the string is located within the header/footer
319 // @param aStr - the string to be drawn
320 // @param aRect - the rect of the page
321 // @param aHeight - the height of the font
322 // @param aAscent - the ascent of the font
323 // @param aWidth - available width for the string
DrawHeaderFooter(gfxContext & aRenderingContext,nsFontMetrics & aFontMetrics,nsHeaderFooterEnum aHeaderFooter,int32_t aJust,const nsString & aStr,const nsRect & aRect,nscoord aAscent,nscoord aHeight,nscoord aWidth)324 void nsPageFrame::DrawHeaderFooter(gfxContext& aRenderingContext,
325                                    nsFontMetrics& aFontMetrics,
326                                    nsHeaderFooterEnum aHeaderFooter,
327                                    int32_t aJust, const nsString& aStr,
328                                    const nsRect& aRect, nscoord aAscent,
329                                    nscoord aHeight, nscoord aWidth) {
330   DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
331 
332   if ((aHeaderFooter == eHeader && aHeight < mPageContentMargin.top) ||
333       (aHeaderFooter == eFooter && aHeight < mPageContentMargin.bottom)) {
334     nsAutoString str;
335     ProcessSpecialCodes(aStr, str);
336 
337     int32_t indx;
338     int32_t textWidth = 0;
339     const char16_t* text = str.get();
340 
341     int32_t len = (int32_t)str.Length();
342     if (len == 0) {
343       return;  // bail is empty string
344     }
345     // find how much text fits, the "position" is the size of the available area
346     if (nsLayoutUtils::BinarySearchForPosition(drawTarget, aFontMetrics, text,
347                                                0, 0, 0, len, int32_t(aWidth),
348                                                indx, textWidth)) {
349       if (indx < len - 1) {
350         // we can't fit in all the text
351         if (indx > 3) {
352           // But we can fit in at least 4 chars.  Show all but 3 of them, then
353           // an ellipsis.
354           // XXXbz for non-plane0 text, this may be cutting things in the
355           // middle of a codepoint!  Also, we have no guarantees that the three
356           // dots will fit in the space the three chars we removed took up with
357           // these font metrics!
358           str.Truncate(indx - 3);
359           str.AppendLiteral("...");
360         } else {
361           // We can only fit 3 or fewer chars.  Just show nothing
362           str.Truncate();
363         }
364       }
365     } else {
366       return;  // bail if couldn't find the correct length
367     }
368 
369     if (HasRTLChars(str)) {
370       PresContext()->SetBidiEnabled();
371     }
372 
373     // calc the x and y positions of the text
374     nscoord x =
375         GetXPosition(aRenderingContext, aFontMetrics, aRect, aJust, str);
376     nscoord y;
377     if (aHeaderFooter == eHeader) {
378       y = aRect.y + mPD->mEdgePaperMargin.top;
379     } else {
380       y = aRect.YMost() - aHeight - mPD->mEdgePaperMargin.bottom;
381     }
382 
383     // set up new clip and draw the text
384     aRenderingContext.Save();
385     aRenderingContext.Clip(NSRectToSnappedRect(
386         aRect, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
387     aRenderingContext.SetColor(sRGBColor::OpaqueBlack());
388     nsLayoutUtils::DrawString(this, aFontMetrics, &aRenderingContext, str.get(),
389                               str.Length(), nsPoint(x, y + aAscent), nullptr,
390                               DrawStringFlags::ForceHorizontal);
391     aRenderingContext.Restore();
392   }
393 }
394 
395 class nsDisplayHeaderFooter final : public nsPaintedDisplayItem {
396  public:
nsDisplayHeaderFooter(nsDisplayListBuilder * aBuilder,nsPageFrame * aFrame)397   nsDisplayHeaderFooter(nsDisplayListBuilder* aBuilder, nsPageFrame* aFrame)
398       : nsPaintedDisplayItem(aBuilder, aFrame) {
399     MOZ_COUNT_CTOR(nsDisplayHeaderFooter);
400   }
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayHeaderFooter)401   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayHeaderFooter)
402 
403   virtual void Paint(nsDisplayListBuilder* aBuilder,
404                      gfxContext* aCtx) override {
405 #ifdef DEBUG
406     nsPageFrame* pageFrame = do_QueryFrame(mFrame);
407     MOZ_ASSERT(pageFrame, "We should have an nsPageFrame");
408 #endif
409     static_cast<nsPageFrame*>(mFrame)->PaintHeaderFooter(
410         *aCtx, ToReferenceFrame(), IsSubpixelAADisabled());
411   }
412   NS_DISPLAY_DECL_NAME("HeaderFooter", TYPE_HEADER_FOOTER)
413 
GetComponentAlphaBounds(nsDisplayListBuilder * aBuilder) const414   virtual nsRect GetComponentAlphaBounds(
415       nsDisplayListBuilder* aBuilder) const override {
416     bool snap;
417     return GetBounds(aBuilder, &snap);
418   }
419 };
420 
PaintMarginGuides(nsIFrame * aFrame,DrawTarget * aDrawTarget,const nsRect & aDirtyRect,nsPoint aPt)421 static void PaintMarginGuides(nsIFrame* aFrame, DrawTarget* aDrawTarget,
422                               const nsRect& aDirtyRect, nsPoint aPt) {
423   // Set up parameters needed to draw the guides: we draw them in blue,
424   // using 2px-long dashes with 2px separation and a line width of 0.5px.
425   // Drawing is antialiased, so on a non-hidpi screen where the line width is
426   // less than one device pixel, it doesn't disappear but renders fainter
427   // than a solid 1px-wide line would be.
428   // (In many cases, the entire preview is scaled down so that the guides
429   // will be nominally less than 1 dev px even on a hidpi screen, resulting
430   // in lighter antialiased rendering so they don't dominate the page.)
431   ColorPattern pattern(ToDeviceColor(sRGBColor(0.0f, 0.0f, 1.0f)));
432   Float dashes[] = {2.0f, 2.0f};
433   StrokeOptions stroke(/* line width (in CSS px) */ 0.5f,
434                        JoinStyle::MITER_OR_BEVEL, CapStyle::BUTT,
435                        /* mitre limit (default, not used) */ 10.0f,
436                        /* set dash pattern of 2px stroke, 2px gap */
437                        ArrayLength(dashes), dashes,
438                        /* dash offset */ 0.0f);
439   DrawOptions options;
440 
441   MOZ_RELEASE_ASSERT(aFrame->IsPageFrame());
442   const nsMargin& margin =
443       static_cast<nsPageFrame*>(aFrame)->GetUsedPageContentMargin();
444   int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
445 
446   // Get the frame's rect and inset by the margins to get the edges of the
447   // content area, where we want to draw the guides.
448   // We draw in two stages, first applying the top/bottom margins and drawing
449   // the horizontal guides across the full width of the page.
450   nsRect rect(aPt, aFrame->GetSize());
451   rect.Deflate(nsMargin(margin.top, 0, margin.bottom, 0));
452   Rect r = NSRectToRect(rect, appUnitsPerDevPx);
453   aDrawTarget->StrokeLine(r.TopLeft(), r.TopRight(), pattern, stroke, options);
454   aDrawTarget->StrokeLine(r.BottomLeft(), r.BottomRight(), pattern, stroke,
455                           options);
456 
457   // Then reset rect, apply the left/right margins, and draw vertical guides
458   // extending the full height of the page.
459   rect = nsRect(aPt, aFrame->GetSize());
460   rect.Deflate(nsMargin(0, margin.right, 0, margin.left));
461   r = NSRectToRect(rect, appUnitsPerDevPx);
462   aDrawTarget->StrokeLine(r.TopLeft(), r.BottomLeft(), pattern, stroke,
463                           options);
464   aDrawTarget->StrokeLine(r.TopRight(), r.BottomRight(), pattern, stroke,
465                           options);
466 }
467 
GetRowAndColFromIdx(uint32_t aIdxOnSheet,uint32_t aNumCols)468 static std::tuple<uint32_t, uint32_t> GetRowAndColFromIdx(uint32_t aIdxOnSheet,
469                                                           uint32_t aNumCols) {
470   // Compute the row index by *dividing* the item's ordinal position by how
471   // many items fit in each row (i.e. the number of columns), and flooring.
472   // Compute the column index by getting the remainder of that division:
473   // Notably, mNumRows is irrelevant to this computation; that's because
474   // we're adding new items column-by-column rather than row-by-row.
475   return {aIdxOnSheet / aNumCols, aIdxOnSheet % aNumCols};
476 }
477 
478 // Helper for BuildDisplayList:
ComputePagesPerSheetAndPageSizeTransform(const nsIFrame * aFrame,float aAppUnitsPerPixel)479 static gfx::Matrix4x4 ComputePagesPerSheetAndPageSizeTransform(
480     const nsIFrame* aFrame, float aAppUnitsPerPixel) {
481   MOZ_ASSERT(aFrame->IsPageFrame());
482   auto* pageFrame = static_cast<const nsPageFrame*>(aFrame);
483 
484   // Variables that we use in our transform (initialized with reasonable
485   // defaults that work for the regular one-page-per-sheet scenario):
486   float scale = 1.0f;
487   nsPoint gridOrigin;
488   uint32_t rowIdx = 0;
489   uint32_t colIdx = 0;
490 
491   // Compute scaling due to a possible mismatch in the paper size we are
492   // printing to (from the pres context) and the specified page size when the
493   // content uses "@page {size: ...}" to specify a page size for the content.
494   const nsSize actualPaperSize = pageFrame->PresContext()->GetPageSize();
495   const nsSize contentPageSize = pageFrame->ComputePageSize();
496   {
497     nscoord contentPageHeight = contentPageSize.height;
498     // Scale down if the target is too wide.
499     if (contentPageSize.width > actualPaperSize.width) {
500       scale *= float(actualPaperSize.width) / float(contentPageSize.width);
501       contentPageHeight = NSToCoordRound(contentPageHeight * scale);
502     }
503     // Scale down if the target is too tall.
504     if (contentPageHeight > actualPaperSize.height) {
505       scale *= float(actualPaperSize.height) / float(contentPageHeight);
506     }
507   }
508   MOZ_ASSERT(
509       scale <= 1.0f,
510       "Page-size mismatches should only have caused us to scale down, not up.");
511 
512   if (nsSharedPageData* pd = pageFrame->GetSharedPageData()) {
513     const auto* ppsInfo = pd->PagesPerSheetInfo();
514     if (ppsInfo->mNumPages > 1) {
515       scale *= pd->mPagesPerSheetScale;
516       gridOrigin = pd->mPagesPerSheetGridOrigin;
517       std::tie(rowIdx, colIdx) = GetRowAndColFromIdx(pageFrame->IndexOnSheet(),
518                                                      pd->mPagesPerSheetNumCols);
519     }
520   }
521 
522   // Scale down the page based on the above-computed scale:
523   auto transform = gfx::Matrix4x4::Scaling(scale, scale, 1);
524 
525   // Draw the page at an offset, to get it in its pages-per-sheet "cell":
526   transform.PreTranslate(
527       NSAppUnitsToFloatPixels(colIdx * contentPageSize.width,
528                               aAppUnitsPerPixel),
529       NSAppUnitsToFloatPixels(rowIdx * contentPageSize.height,
530                               aAppUnitsPerPixel),
531       0);
532 
533   // Also add the grid origin as an offset (so that we're not drawing into the
534   // sheet's unwritable area). Note that this is a PostTranslate operation
535   // (vs. PreTranslate above), since gridOrigin is an offset on the sheet
536   // itself, whereas the offset above was in the scaled coordinate space of the
537   // pages.
538   return transform.PostTranslate(
539       NSAppUnitsToFloatPixels(gridOrigin.x, aAppUnitsPerPixel),
540       NSAppUnitsToFloatPixels(gridOrigin.y, aAppUnitsPerPixel), 0);
541 }
542 
GetTransformGetter() const543 nsIFrame::ComputeTransformFunction nsPageFrame::GetTransformGetter() const {
544   return ComputePagesPerSheetAndPageSizeTransform;
545 }
546 
PageContentFrame() const547 nsPageContentFrame* nsPageFrame::PageContentFrame() const {
548   nsIFrame* const frame = mFrames.FirstChild();
549   MOZ_ASSERT(frame, "pageFrame must have one child");
550   MOZ_ASSERT(frame->IsPageContentFrame(),
551              "pageFrame must have pageContentFrame as the first child");
552   return static_cast<nsPageContentFrame*>(frame);
553 }
554 
ComputePageSize() const555 nsSize nsPageFrame::ComputePageSize() const {
556   const nsPageContentFrame* const pcf = PageContentFrame();
557   nsSize size = PresContext()->GetPageSize();
558 
559   // Compute the expected page-size.
560   const nsStylePage* const stylePage = pcf->StylePage();
561   const StylePageSize& pageSize = stylePage->mSize;
562 
563   if (pageSize.IsSize()) {
564     // Use the specified size
565     return nsSize{pageSize.AsSize().width.ToAppUnits(),
566                   pageSize.AsSize().height.ToAppUnits()};
567   }
568   if (pageSize.IsOrientation()) {
569     // Ensure the correct orientation is applied.
570     if (pageSize.AsOrientation() == StyleOrientation::Portrait) {
571       if (size.width > size.height) {
572         std::swap(size.width, size.height);
573       }
574     } else {
575       MOZ_ASSERT(pageSize.AsOrientation() == StyleOrientation::Landscape);
576       if (size.width < size.height) {
577         std::swap(size.width, size.height);
578       }
579     }
580   } else {
581     MOZ_ASSERT(pageSize.IsAuto(), "Impossible page-size value?");
582   }
583   return size;
584 }
585 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)586 void nsPageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
587                                    const nsDisplayListSet& aLists) {
588   nsDisplayList content;
589   nsDisplayListSet set(&content, &content, &content, &content, &content,
590                        &content);
591   {
592     DisplayListClipState::AutoSaveRestore clipState(aBuilder);
593     clipState.Clear();
594 
595     // We need to extend the building rect to include the specified page size
596     // (scaled by the print scaling factor), in case it is larger than the
597     // physical page size. In that case the nsPageFrame will be the size of the
598     // physical page, but the child nsPageContentFrame will be the larger
599     // specified page size. The more correct way to do this would be to fully
600     // reverse the result of ComputePagesPerSheetAndPageSizeTransform to handle
601     // this scaling, but this should have the same result and is easier.
602     nsPresContext* const pc = PresContext();
603     const float scale = pc->GetPageScale();
604     const nsSize pageSize = ComputePageSize();
605     const nsRect scaledPageRect{0, 0, NSToCoordCeil(pageSize.width / scale),
606                                 NSToCoordCeil(pageSize.height / scale)};
607     nsDisplayListBuilder::AutoBuildingDisplayList buildingForPageContentFrame(
608         aBuilder, this, scaledPageRect, scaledPageRect);
609 
610     nsContainerFrame::BuildDisplayList(aBuilder, set);
611 
612     if (pc->IsRootPaginatedDocument()) {
613       content.AppendNewToTop<nsDisplayHeaderFooter>(aBuilder, this);
614 
615       // For print-preview, show margin guides if requested in the settings.
616       if (pc->Type() == nsPresContext::eContext_PrintPreview &&
617           mPD->mPrintSettings->GetShowMarginGuides()) {
618         content.AppendNewToTop<nsDisplayGeneric>(
619             aBuilder, this, PaintMarginGuides, "MarginGuides",
620             DisplayItemType::TYPE_MARGIN_GUIDES);
621       }
622     }
623   }
624 
625   // We'll be drawing the page with a (usually-trivial)
626   // N-pages-per-sheet transform applied, so our passed-in visible rect
627   // isn't meaningful while we're drawing our children, because the
628   // transform could scale down content whose coordinates are off-screen
629   // such that it ends up on-screen. So: we temporarily update the visible
630   // rect to be the child nsPageFrame's whole frame-rect (represented in
631   // this PrintedSheetFrame's coordinate space.
632   content.AppendNewToTop<nsDisplayTransform>(
633       aBuilder, this, &content, content.GetBuildingRect(),
634       nsDisplayTransform::WithTransformGetter);
635 
636   set.MoveTo(aLists);
637 }
638 
639 //------------------------------------------------------------------------------
DeterminePageNum()640 void nsPageFrame::DeterminePageNum() {
641   // If we have no previous continuation, we're page 1. Otherwise, we're
642   // just one more than our previous continuation's page number.
643   auto* prevContinuation = static_cast<nsPageFrame*>(GetPrevContinuation());
644   mPageNum = prevContinuation ? prevContinuation->GetPageNum() + 1 : 1;
645 }
646 
PaintHeaderFooter(gfxContext & aRenderingContext,nsPoint aPt,bool aDisableSubpixelAA)647 void nsPageFrame::PaintHeaderFooter(gfxContext& aRenderingContext, nsPoint aPt,
648                                     bool aDisableSubpixelAA) {
649   nsPresContext* pc = PresContext();
650 
651   nsRect rect(aPt, ComputePageSize());
652   aRenderingContext.SetColor(sRGBColor::OpaqueBlack());
653 
654   DrawTargetAutoDisableSubpixelAntialiasing disable(
655       aRenderingContext.GetDrawTarget(), aDisableSubpixelAA);
656 
657   // Get the FontMetrics to determine width.height of strings
658   nsFontMetrics::Params params;
659   params.userFontSet = pc->GetUserFontSet();
660   params.textPerf = pc->GetTextPerfMetrics();
661   params.fontStats = pc->GetFontMatchingStats();
662   params.featureValueLookup = pc->GetFontFeatureValuesLookup();
663   RefPtr<nsFontMetrics> fontMet =
664       pc->DeviceContext()->GetMetricsFor(mPD->mHeadFootFont, params);
665 
666   nscoord ascent = fontMet->MaxAscent();
667   nscoord visibleHeight = fontMet->MaxHeight();
668 
669   // print document headers and footers
670   nsString headerLeft, headerCenter, headerRight;
671   mPD->mPrintSettings->GetHeaderStrLeft(headerLeft);
672   mPD->mPrintSettings->GetHeaderStrCenter(headerCenter);
673   mPD->mPrintSettings->GetHeaderStrRight(headerRight);
674   DrawHeaderFooter(aRenderingContext, *fontMet, eHeader, headerLeft,
675                    headerCenter, headerRight, rect, ascent, visibleHeight);
676 
677   nsString footerLeft, footerCenter, footerRight;
678   mPD->mPrintSettings->GetFooterStrLeft(footerLeft);
679   mPD->mPrintSettings->GetFooterStrCenter(footerCenter);
680   mPD->mPrintSettings->GetFooterStrRight(footerRight);
681   DrawHeaderFooter(aRenderingContext, *fontMet, eFooter, footerLeft,
682                    footerCenter, footerRight, rect, ascent, visibleHeight);
683 }
684 
SetSharedPageData(nsSharedPageData * aPD)685 void nsPageFrame::SetSharedPageData(nsSharedPageData* aPD) {
686   mPD = aPD;
687   // Set the shared data into the page frame before reflow
688   PageContentFrame()->SetSharedPageData(mPD);
689 }
690 
NS_NewPageBreakFrame(PresShell * aPresShell,ComputedStyle * aStyle)691 nsIFrame* NS_NewPageBreakFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
692   MOZ_ASSERT(aPresShell, "null PresShell");
693   // check that we are only creating page break frames when printing
694   NS_ASSERTION(aPresShell->GetPresContext()->IsPaginated(),
695                "created a page break frame while not printing");
696 
697   return new (aPresShell)
698       nsPageBreakFrame(aStyle, aPresShell->GetPresContext());
699 }
700 
NS_IMPL_FRAMEARENA_HELPERS(nsPageBreakFrame)701 NS_IMPL_FRAMEARENA_HELPERS(nsPageBreakFrame)
702 
703 nsPageBreakFrame::nsPageBreakFrame(ComputedStyle* aStyle,
704                                    nsPresContext* aPresContext)
705     : nsLeafFrame(aStyle, aPresContext, kClassID), mHaveReflowed(false) {}
706 
707 nsPageBreakFrame::~nsPageBreakFrame() = default;
708 
GetIntrinsicISize()709 nscoord nsPageBreakFrame::GetIntrinsicISize() {
710   return nsPresContext::CSSPixelsToAppUnits(1);
711 }
712 
GetIntrinsicBSize()713 nscoord nsPageBreakFrame::GetIntrinsicBSize() { return 0; }
714 
Reflow(nsPresContext * aPresContext,ReflowOutput & aReflowOutput,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)715 void nsPageBreakFrame::Reflow(nsPresContext* aPresContext,
716                               ReflowOutput& aReflowOutput,
717                               const ReflowInput& aReflowInput,
718                               nsReflowStatus& aStatus) {
719   DO_GLOBAL_REFLOW_COUNT("nsPageBreakFrame");
720   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
721   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
722 
723   // Override reflow, since we don't want to deal with what our
724   // computed values are.
725   WritingMode wm = aReflowInput.GetWritingMode();
726   nscoord bSize = aReflowInput.AvailableBSize();
727   if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
728     bSize = nscoord(0);
729   } else if (GetContent()->IsHTMLElement(nsGkAtoms::legend)) {
730     // If this is a page break frame for a _rendered legend_ then it should be
731     // ignored since these frames are inserted inside the fieldset's inner
732     // frame and thus "misplaced".  nsFieldSetFrame::Reflow deals with these
733     // forced breaks explicitly instead.
734     nsContainerFrame* parent = GetParent();
735     if (parent &&
736         parent->Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) {
737       while ((parent = parent->GetParent())) {
738         if (nsFieldSetFrame* fieldset = do_QueryFrame(parent)) {
739           auto* legend = fieldset->GetLegend();
740           if (legend && legend->GetContent() == GetContent()) {
741             bSize = nscoord(0);
742           }
743           break;
744         }
745       }
746     }
747   }
748   LogicalSize finalSize(wm, GetIntrinsicISize(), bSize);
749   // round the height down to the nearest pixel
750   // XXX(mats) why???
751   finalSize.BSize(wm) -=
752       finalSize.BSize(wm) % nsPresContext::CSSPixelsToAppUnits(1);
753   aReflowOutput.SetSize(wm, finalSize);
754 
755   // Note: not using NS_FRAME_FIRST_REFLOW here, since it's not clear whether
756   // DidReflow will always get called before the next Reflow() call.
757   mHaveReflowed = true;
758 }
759 
760 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const761 nsresult nsPageBreakFrame::GetFrameName(nsAString& aResult) const {
762   return MakeFrameName(u"PageBreak"_ns, aResult);
763 }
764 #endif
765