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