nsPageFrame.cpp (44065B)
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 "PrintedSheetFrame.h" 10 #include "gfxContext.h" 11 #include "mozilla/AppUnits.h" 12 #include "mozilla/Logging.h" 13 #include "mozilla/PresShell.h" 14 #include "mozilla/StaticPrefs_layout.h" 15 #include "mozilla/StaticPrefs_print.h" 16 #include "mozilla/gfx/2D.h" 17 #include "mozilla/intl/Segmenter.h" 18 #include "nsBidiUtils.h" 19 #include "nsDeviceContext.h" 20 #include "nsDisplayList.h" 21 #include "nsFieldSetFrame.h" 22 #include "nsFontMetrics.h" 23 #include "nsGkAtoms.h" 24 #include "nsIFrame.h" 25 #include "nsIPrintSettings.h" 26 #include "nsLayoutUtils.h" 27 #include "nsPageContentFrame.h" 28 #include "nsPageSequenceFrame.h" // for nsSharedPageData 29 #include "nsPresContext.h" 30 #include "nsTextFormatter.h" // for page number localization formatting 31 extern mozilla::LazyLogModule gLayoutPrintingLog; 32 #define PR_PL(_p1) MOZ_LOG(gLayoutPrintingLog, mozilla::LogLevel::Debug, _p1) 33 34 using namespace mozilla; 35 using namespace mozilla::gfx; 36 37 nsPageFrame* NS_NewPageFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 38 return new (aPresShell) nsPageFrame(aStyle, aPresShell->GetPresContext()); 39 } 40 41 NS_IMPL_FRAMEARENA_HELPERS(nsPageFrame) 42 43 NS_QUERYFRAME_HEAD(nsPageFrame) 44 NS_QUERYFRAME_ENTRY(nsPageFrame) 45 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 46 47 nsPageFrame::nsPageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) 48 : nsContainerFrame(aStyle, aPresContext, kClassID) {} 49 50 nsPageFrame::~nsPageFrame() = default; 51 52 nsReflowStatus nsPageFrame::ReflowPageContent( 53 nsPresContext* aPresContext, const ReflowInput& aPageReflowInput) { 54 nsPageContentFrame* const frame = PageContentFrame(); 55 // If this is the first page, it won't have its page name and computed style 56 // set yet. Before reflow, make sure that page name and computed style have 57 // been applied. 58 frame->EnsurePageName(); 59 // XXX Pay attention to the page's border and padding... 60 // 61 // Reflow our ::-moz-page-content frame, allowing it only to be as big as we 62 // are (minus margins). 63 const nsSize pageSize = ComputePageSize(); 64 // Scaling applied to the page in the single page-per-sheet case (used for 65 // down-scaling when the page is too large to fit on the sheet we are printing 66 // on). In the single page-per-sheet case, we need this here to preemptively 67 // increase the margins by the same amount that the scaling will reduce them 68 // in order to make sure that their physical size is unchanged (particularly 69 // important for the unwriteable margins). 70 const auto* ppsInfo = GetSharedPageData()->PagesPerSheetInfo(); 71 const float pageSizeScale = 72 ppsInfo->mNumPages == 1 ? ComputeSinglePPSPageSizeScale(pageSize) : 1.0f; 73 // Scaling applied to content, as given by the print UI. 74 // This is an additional scale factor that is applied to the content in the 75 // nsPageContentFrame. 76 const float extraContentScale = aPresContext->GetPageScale(); 77 // Size for the page content. This will be scaled by extraContentScale, and 78 // is used to calculate the computed size of the nsPageContentFrame content 79 // by subtracting margins. 80 nsSize availableSpace = pageSize; 81 82 // When the reflow size is NS_UNCONSTRAINEDSIZE it means we are reflowing 83 // a single page to print selection. So this means we want to use 84 // NS_UNCONSTRAINEDSIZE without altering it. 85 // 86 // FIXME(emilio): Is this still true? 87 availableSpace.width = 88 NSToCoordCeil(availableSpace.width / extraContentScale); 89 if (availableSpace.height != NS_UNCONSTRAINEDSIZE) { 90 availableSpace.height = 91 NSToCoordCeil(availableSpace.height / extraContentScale); 92 } 93 94 // Get the number of Twips per pixel from the PresContext 95 const nscoord onePixel = AppUnitsPerCSSPixel(); 96 97 // insurance against infinite reflow, when reflowing less than a pixel 98 // XXX Shouldn't we do something more friendly when invalid margins 99 // are set? 100 if (availableSpace.width < onePixel || availableSpace.height < onePixel) { 101 NS_WARNING("Reflow aborted; no space for content"); 102 return {}; 103 } 104 105 ReflowInput kidReflowInput( 106 aPresContext, aPageReflowInput, frame, 107 LogicalSize(frame->GetWritingMode(), availableSpace)); 108 kidReflowInput.mFlags.mIsTopOfPage = true; 109 kidReflowInput.mFlags.mTableIsSplittable = true; 110 111 nsMargin defaultMargins = aPresContext->GetDefaultPageMargin(); 112 // The default margins are in the coordinate space of the physical paper. 113 // Scale them by the pageSizeScale to convert them to the content coordinate 114 // space. 115 for (const auto side : mozilla::AllPhysicalSides()) { 116 defaultMargins.Side(side) = 117 NSToCoordRound((float)defaultMargins.Side(side) / pageSizeScale); 118 } 119 mPageContentMargin = defaultMargins; 120 121 // Use the margins given in the @page rule if told to do so. 122 // We clamp to the paper's unwriteable margins to avoid clipping, *except* 123 // that we will respect a margin of zero if specified, assuming this means 124 // the document is intended to fit the paper size exactly, and the client is 125 // taking full responsibility for what happens around the edges. 126 const auto anchorResolutionParams = 127 AnchorPosResolutionParams::From(&kidReflowInput); 128 if (mPD->mPrintSettings->GetHonorPageRuleMargins()) { 129 for (const auto side : mozilla::AllPhysicalSides()) { 130 if (!kidReflowInput.mStyleMargin->GetMargin(side, anchorResolutionParams) 131 ->IsAuto()) { 132 // Computed margins are already in the coordinate space of the content, 133 // do not scale. 134 const nscoord computed = 135 kidReflowInput.ComputedPhysicalMargin().Side(side); 136 // Respecting a zero margin is particularly important when the client 137 // is PDF.js where the PDF already contains the margins. 138 // User could also be asking to ignore unwriteable margins (Though 139 // currently, it is impossible through the print UI to set both 140 // `HonorPageRuleMargins` and `IgnoreUnwriteableMargins`). 141 if (computed == 0 || 142 mPD->mPrintSettings->GetIgnoreUnwriteableMargins()) { 143 mPageContentMargin.Side(side) = computed; 144 } else { 145 // Unwriteable margins are in the coordinate space of the physical 146 // paper. Scale them by the pageSizeScale to convert them to the 147 // content coordinate space. 148 const int32_t unwriteableTwips = 149 mPD->mPrintSettings->GetUnwriteableMarginInTwips().Side(side); 150 const nscoord unwriteable = nsPresContext::CSSTwipsToAppUnits( 151 (float)unwriteableTwips / pageSizeScale); 152 mPageContentMargin.Side(side) = std::max( 153 kidReflowInput.ComputedPhysicalMargin().Side(side), unwriteable); 154 } 155 } 156 } 157 } 158 159 // TODO: This seems odd that we need to scale the margins by the extra 160 // scale factor, but this is needed for correct margins. 161 // Why are the margins already scaled? Shouldn't they be stored so that this 162 // scaling factor would be redundant? 163 nscoord computedWidth = 164 availableSpace.width - mPageContentMargin.LeftRight() / extraContentScale; 165 nscoord computedHeight; 166 if (availableSpace.height == NS_UNCONSTRAINEDSIZE) { 167 computedHeight = NS_UNCONSTRAINEDSIZE; 168 } else { 169 computedHeight = availableSpace.height - 170 mPageContentMargin.TopBottom() / extraContentScale; 171 } 172 173 // Check the width and height, if they're too small we reset the margins 174 // back to the default. 175 if (computedWidth < onePixel || computedHeight < onePixel) { 176 mPageContentMargin = defaultMargins; 177 computedWidth = availableSpace.width - 178 mPageContentMargin.LeftRight() / extraContentScale; 179 if (computedHeight != NS_UNCONSTRAINEDSIZE) { 180 computedHeight = availableSpace.height - 181 mPageContentMargin.TopBottom() / extraContentScale; 182 } 183 // And if they're still too small, we give up. 184 if (computedWidth < onePixel || computedHeight < onePixel) { 185 NS_WARNING("Reflow aborted; no space for content"); 186 return {}; 187 } 188 } 189 190 kidReflowInput.SetComputedWidth(computedWidth); 191 kidReflowInput.SetComputedHeight(computedHeight); 192 193 // calc location of frame 194 const nscoord xc = mPageContentMargin.left; 195 const nscoord yc = mPageContentMargin.top; 196 197 // Get the child's desired size 198 ReflowOutput kidOutput(kidReflowInput); 199 nsReflowStatus kidStatus; 200 ReflowChild(frame, aPresContext, kidOutput, kidReflowInput, xc, yc, 201 ReflowChildFlags::Default, kidStatus); 202 203 // Place and size the child 204 FinishReflowChild(frame, aPresContext, kidOutput, &kidReflowInput, xc, yc, 205 ReflowChildFlags::Default); 206 207 NS_ASSERTION(!kidStatus.IsFullyComplete() || !frame->GetNextInFlow(), 208 "bad child flow list"); 209 return kidStatus; 210 } 211 212 void nsPageFrame::Reflow(nsPresContext* aPresContext, 213 ReflowOutput& aReflowOutput, 214 const ReflowInput& aReflowInput, 215 nsReflowStatus& aStatus) { 216 MarkInReflow(); 217 DO_GLOBAL_REFLOW_COUNT("nsPageFrame"); 218 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 219 MOZ_ASSERT(mPD, "Need a pointer to nsSharedPageData before reflow starts"); 220 221 // Our status is the same as our child's. 222 aStatus = ReflowPageContent(aPresContext, aReflowInput); 223 224 PR_PL(("PageFrame::Reflow %p ", this)); 225 PR_PL(("[%d,%d][%d,%d]\n", aReflowOutput.Width(), aReflowOutput.Height(), 226 aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); 227 228 // Return our desired size 229 WritingMode wm = aReflowInput.GetWritingMode(); 230 aReflowOutput.ISize(wm) = aReflowInput.AvailableISize(); 231 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) { 232 aReflowOutput.BSize(wm) = aReflowInput.AvailableBSize(); 233 } 234 235 aReflowOutput.SetOverflowAreasToDesiredBounds(); 236 FinishAndStoreOverflow(&aReflowOutput); 237 238 PR_PL(("PageFrame::Reflow %p ", this)); 239 PR_PL(("[%d,%d]\n", aReflowInput.AvailableWidth(), 240 aReflowInput.AvailableHeight())); 241 } 242 243 #ifdef DEBUG_FRAME_DUMP 244 nsresult nsPageFrame::GetFrameName(nsAString& aResult) const { 245 return MakeFrameName(u"Page"_ns, aResult); 246 } 247 #endif 248 249 void nsPageFrame::ProcessSpecialCodes(const nsString& aStr, nsString& aNewStr) { 250 aNewStr = aStr; 251 252 // Helper to wrap a string in Unicode FSI/PDI controls, so that its bidi 253 // rendering will be isolated from the context, and will respect the default 254 // directionality of the string's content. 255 // We apply this to each of the strings being substituted in for "special" 256 // codes in the header/footer. 257 constexpr char16_t kFirstStrongIsolate = char16_t(0x2068); 258 constexpr char16_t kPopDirectionalIsolate = char16_t(0x2069); 259 auto bidiIsolateWrap = [](nsString& aString) { 260 aString.Insert(kFirstStrongIsolate, 0); 261 aString.Append(kPopDirectionalIsolate); 262 }; 263 264 // Search to see if the &D code is in the string 265 // then subst in the current date/time 266 constexpr auto kDate = u"&D"_ns; 267 if (aStr.Find(kDate) != kNotFound) { 268 nsAutoString uStr(mPD->mDateTimeStr); 269 bidiIsolateWrap(uStr); 270 aNewStr.ReplaceSubstring(kDate, uStr); 271 } 272 273 // NOTE: Must search for &PT before searching for &P 274 // 275 // Search to see if the "page number and page" total code are in the string 276 // and replace the page number and page total code with the actual 277 // values 278 constexpr auto kPageAndTotal = u"&PT"_ns; 279 if (aStr.Find(kPageAndTotal) != kNotFound) { 280 nsAutoString uStr; 281 nsTextFormatter::ssprintf(uStr, mPD->mPageNumAndTotalsFormat.get(), 282 mPageNum, mPD->mRawNumPages); 283 bidiIsolateWrap(uStr); 284 aNewStr.ReplaceSubstring(kPageAndTotal, uStr); 285 } 286 287 // Search to see if the page number code is in the string 288 // and replace the page number code with the actual value 289 constexpr auto kPage = u"&P"_ns; 290 if (aStr.Find(kPage) != kNotFound) { 291 nsAutoString uStr; 292 nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), mPageNum); 293 bidiIsolateWrap(uStr); 294 aNewStr.ReplaceSubstring(kPage, uStr); 295 } 296 297 constexpr auto kTitle = u"&T"_ns; 298 if (aStr.Find(kTitle) != kNotFound) { 299 nsAutoString uStr(mPD->mDocTitle); 300 bidiIsolateWrap(uStr); 301 aNewStr.ReplaceSubstring(kTitle, uStr); 302 } 303 304 constexpr auto kDocURL = u"&U"_ns; 305 if (aStr.Find(kDocURL) != kNotFound) { 306 nsAutoString uStr(mPD->mDocURL); 307 bidiIsolateWrap(uStr); 308 aNewStr.ReplaceSubstring(kDocURL, uStr); 309 } 310 311 constexpr auto kPageTotal = u"&L"_ns; 312 if (aStr.Find(kPageTotal) != kNotFound) { 313 nsAutoString uStr; 314 nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), 315 mPD->mRawNumPages); 316 bidiIsolateWrap(uStr); 317 aNewStr.ReplaceSubstring(kPageTotal, uStr); 318 } 319 } 320 321 //------------------------------------------------------------------------------ 322 nscoord nsPageFrame::GetXPosition(gfxContext& aRenderingContext, 323 nsFontMetrics& aFontMetrics, 324 const nsRect& aRect, int32_t aJust, 325 const nsString& aStr) { 326 nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi( 327 aStr, this, aFontMetrics, aRenderingContext); 328 nscoord x = aRect.x; 329 switch (aJust) { 330 case nsIPrintSettings::kJustLeft: 331 x += mPD->mEdgePaperMargin.left; 332 break; 333 334 case nsIPrintSettings::kJustCenter: 335 x += (aRect.width - width) / 2; 336 break; 337 338 case nsIPrintSettings::kJustRight: 339 x += aRect.width - width - mPD->mEdgePaperMargin.right; 340 break; 341 } // switch 342 343 return x; 344 } 345 346 // Draw a header or footer 347 // @param aRenderingContext - rendering content to draw into 348 // @param aHeaderFooter - indicates whether it is a header or footer 349 // @param aStrLeft - string for the left header or footer; can be empty 350 // @param aStrCenter - string for the center header or footer; can be empty 351 // @param aStrRight - string for the right header or footer; can be empty 352 // @param aRect - the rect of the page 353 // @param aAscent - the ascent of the font 354 // @param aHeight - the height of the font 355 void nsPageFrame::DrawHeaderFooter( 356 gfxContext& aRenderingContext, nsFontMetrics& aFontMetrics, 357 nsHeaderFooterEnum aHeaderFooter, const nsString& aStrLeft, 358 const nsString& aStrCenter, const nsString& aStrRight, const nsRect& aRect, 359 nscoord aAscent, nscoord aHeight) { 360 int32_t numStrs = 0; 361 if (!aStrLeft.IsEmpty()) { 362 numStrs++; 363 } 364 if (!aStrCenter.IsEmpty()) { 365 numStrs++; 366 } 367 if (!aStrRight.IsEmpty()) { 368 numStrs++; 369 } 370 371 if (numStrs == 0) { 372 return; 373 } 374 const nscoord contentWidth = 375 aRect.width - (mPD->mEdgePaperMargin.left + mPD->mEdgePaperMargin.right); 376 const nscoord strSpace = contentWidth / numStrs; 377 378 if (!aStrLeft.IsEmpty()) { 379 DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter, 380 nsIPrintSettings::kJustLeft, aStrLeft, aRect, aAscent, 381 aHeight, strSpace); 382 } 383 if (!aStrCenter.IsEmpty()) { 384 DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter, 385 nsIPrintSettings::kJustCenter, aStrCenter, aRect, aAscent, 386 aHeight, strSpace); 387 } 388 if (!aStrRight.IsEmpty()) { 389 DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter, 390 nsIPrintSettings::kJustRight, aStrRight, aRect, aAscent, 391 aHeight, strSpace); 392 } 393 } 394 395 // Draw a header or footer string 396 // @param aRenderingContext - rendering context to draw into 397 // @param aHeaderFooter - indicates whether it is a header or footer 398 // @param aJust - indicates where the string is located within the header/footer 399 // @param aStr - the string to be drawn 400 // @param aRect - the rect of the page 401 // @param aHeight - the height of the font 402 // @param aAscent - the ascent of the font 403 // @param aWidth - available width for the string 404 void nsPageFrame::DrawHeaderFooter(gfxContext& aRenderingContext, 405 nsFontMetrics& aFontMetrics, 406 nsHeaderFooterEnum aHeaderFooter, 407 int32_t aJust, const nsString& aStr, 408 const nsRect& aRect, nscoord aAscent, 409 nscoord aHeight, nscoord aWidth) { 410 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); 411 412 if ((aHeaderFooter == eHeader && aHeight < mPageContentMargin.top) || 413 (aHeaderFooter == eFooter && aHeight < mPageContentMargin.bottom)) { 414 nsAutoString str; 415 ProcessSpecialCodes(aStr, str); 416 417 int32_t len = (int32_t)str.Length(); 418 if (len == 0) { 419 return; // bail is empty string 420 } 421 422 int32_t index; 423 int32_t textWidth = 0; 424 const char16_t* text = str.get(); 425 // find how much text fits, the "position" is the size of the available area 426 if (nsLayoutUtils::BinarySearchForPosition(drawTarget, aFontMetrics, text, 427 0, 0, 0, len, int32_t(aWidth), 428 index, textWidth)) { 429 if (index < len - 1) { 430 // we can't fit in all the text, try to remove 3 glyphs and append 431 // three "." charactrers. 432 433 // TODO: This might not actually remove three glyphs in cases where 434 // ZWJ sequences, regional indicators, etc are used. 435 // We also have guarantee that removing three glyphs will make enough 436 // space for the ellipse, if they are zero-width or even just narrower 437 // than the "." character. 438 // See https://bugzilla.mozilla.org/1765008 439 mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16 revIter(str); 440 441 // Start iteration at the point where the text does properly fit. 442 revIter.Seek(index); 443 444 // Step backwards 3 times, checking if we have any string left by the 445 // end. 446 revIter.Next(); 447 revIter.Next(); 448 if (const Maybe<uint32_t> maybeIndex = revIter.Next()) { 449 // TODO: We should consider checking for the ellipse character, or 450 // possibly for another continuation indicator based on 451 // localization. 452 // See https://bugzilla.mozilla.org/1765007 453 str.Truncate(*maybeIndex); 454 str.AppendLiteral("..."); 455 } else { 456 // We can only fit 3 or fewer chars. Just show nothing 457 str.Truncate(); 458 } 459 } 460 } else { 461 return; // bail if couldn't find the correct length 462 } 463 464 if (HasRTLChars(str)) { 465 PresContext()->SetBidiEnabled(); 466 } 467 468 // calc the x and y positions of the text 469 nscoord x = 470 GetXPosition(aRenderingContext, aFontMetrics, aRect, aJust, str); 471 nscoord y; 472 if (aHeaderFooter == eHeader) { 473 y = aRect.y + mPD->mEdgePaperMargin.top; 474 } else { 475 y = aRect.YMost() - aHeight - mPD->mEdgePaperMargin.bottom; 476 } 477 478 // set up new clip and draw the text 479 aRenderingContext.Save(); 480 aRenderingContext.Clip(NSRectToSnappedRect( 481 aRect, PresContext()->AppUnitsPerDevPixel(), *drawTarget)); 482 aRenderingContext.SetColor(sRGBColor::OpaqueBlack()); 483 nsLayoutUtils::DrawString(this, aFontMetrics, &aRenderingContext, str.get(), 484 str.Length(), nsPoint(x, y + aAscent), nullptr, 485 DrawStringFlags::ForceHorizontal); 486 aRenderingContext.Restore(); 487 } 488 } 489 490 class nsDisplayHeaderFooter final : public nsPaintedDisplayItem { 491 public: 492 nsDisplayHeaderFooter(nsDisplayListBuilder* aBuilder, nsPageFrame* aFrame) 493 : nsPaintedDisplayItem(aBuilder, aFrame) { 494 MOZ_COUNT_CTOR(nsDisplayHeaderFooter); 495 } 496 497 MOZ_COUNTED_DTOR_FINAL(nsDisplayHeaderFooter) 498 499 virtual void Paint(nsDisplayListBuilder* aBuilder, 500 gfxContext* aCtx) override { 501 #ifdef DEBUG 502 nsPageFrame* pageFrame = do_QueryFrame(mFrame); 503 MOZ_ASSERT(pageFrame, "We should have an nsPageFrame"); 504 #endif 505 static_cast<nsPageFrame*>(mFrame)->PaintHeaderFooter( 506 *aCtx, ToReferenceFrame(), false); 507 } 508 NS_DISPLAY_DECL_NAME("HeaderFooter", TYPE_HEADER_FOOTER) 509 510 virtual nsRect GetComponentAlphaBounds( 511 nsDisplayListBuilder* aBuilder) const override { 512 bool snap; 513 return GetBounds(aBuilder, &snap); 514 } 515 }; 516 517 static void PaintMarginGuides(nsIFrame* aFrame, DrawTarget* aDrawTarget, 518 const nsRect& aDirtyRect, nsPoint aPt) { 519 // Set up parameters needed to draw the guides: we draw them in blue, 520 // using 2px-long dashes with 2px separation and a line width of 0.5px. 521 // Drawing is antialiased, so on a non-hidpi screen where the line width is 522 // less than one device pixel, it doesn't disappear but renders fainter 523 // than a solid 1px-wide line would be. 524 // (In many cases, the entire preview is scaled down so that the guides 525 // will be nominally less than 1 dev px even on a hidpi screen, resulting 526 // in lighter antialiased rendering so they don't dominate the page.) 527 ColorPattern pattern(ToDeviceColor(sRGBColor(0.0f, 0.0f, 1.0f))); 528 Float dashes[] = {2.0f, 2.0f}; 529 StrokeOptions stroke(/* line width (in CSS px) */ 0.5f, 530 JoinStyle::MITER_OR_BEVEL, CapStyle::BUTT, 531 /* mitre limit (default, not used) */ 10.0f, 532 /* set dash pattern of 2px stroke, 2px gap */ 533 std::size(dashes), dashes, 534 /* dash offset */ 0.0f); 535 DrawOptions options; 536 537 MOZ_RELEASE_ASSERT(aFrame->IsPageFrame()); 538 const nsMargin& margin = 539 static_cast<nsPageFrame*>(aFrame)->GetUsedPageContentMargin(); 540 int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); 541 542 // Get the frame's rect and inset by the margins to get the edges of the 543 // content area, where we want to draw the guides. 544 // We draw in two stages, first applying the top/bottom margins and drawing 545 // the horizontal guides across the full width of the page. 546 nsRect rect(aPt, aFrame->GetSize()); 547 rect.Deflate(nsMargin(margin.top, 0, margin.bottom, 0)); 548 Rect r = NSRectToRect(rect, appUnitsPerDevPx); 549 aDrawTarget->StrokeLine(r.TopLeft(), r.TopRight(), pattern, stroke, options); 550 aDrawTarget->StrokeLine(r.BottomLeft(), r.BottomRight(), pattern, stroke, 551 options); 552 553 // Then reset rect, apply the left/right margins, and draw vertical guides 554 // extending the full height of the page. 555 rect = nsRect(aPt, aFrame->GetSize()); 556 rect.Deflate(nsMargin(0, margin.right, 0, margin.left)); 557 r = NSRectToRect(rect, appUnitsPerDevPx); 558 aDrawTarget->StrokeLine(r.TopLeft(), r.BottomLeft(), pattern, stroke, 559 options); 560 aDrawTarget->StrokeLine(r.TopRight(), r.BottomRight(), pattern, stroke, 561 options); 562 } 563 564 static std::tuple<uint32_t, uint32_t> GetRowAndColFromIdx(uint32_t aIdxOnSheet, 565 uint32_t aNumCols) { 566 // Compute the row index by *dividing* the item's ordinal position by how 567 // many items fit in each row (i.e. the number of columns), and flooring. 568 // Compute the column index by getting the remainder of that division: 569 // Notably, mNumRows is irrelevant to this computation; that's because 570 // we're adding new items column-by-column rather than row-by-row. 571 return {aIdxOnSheet / aNumCols, aIdxOnSheet % aNumCols}; 572 } 573 574 // The minimum ratio for which we will center the page on the sheet when using 575 // auto-detect logic. 576 // Note that this ratio is of the content's size to the sheet size scaled to be 577 // in content space, and so the actual ratio will always be from 0.0 to 1.0, 578 // with this marking the smallest ratio we consider a near-miss. 579 // The ratio of A4 on Letter is 0.915034. A threshold of 0.9 will ensure that 580 // A4 on Letter works, as well as other near-misses. 581 // 582 // The ratio is computed as so: 583 // scale = min(1, sheetHeight / pageHeight, sheetWidth / pageWidth) 584 // 585 // Where pageSize is pageWidth or pageHeight, and sheetSize is sheetWidth or 586 // sheetHeight, respectively: 587 // scaledPageSize = pageSize * scale 588 // ratio = scaledPageSize / sheetSize 589 // 590 // A4 (210mm x 297mm) on US Letter (215.9mm x 279.4mm) is derived as so: 591 // scale = min(1, 215.9 / 210, 279.4 / 297) = 0.9407407407.. 592 // 593 // Using the widths: 594 // scaledPageSize = (210 * 0.940741) = 197.556 595 // ratio = 197.556 / 215.9 = 0.915034 596 // 597 // See nsPageFrame::ComputeSinglePPSPageSizeScale for scale calculation, and 598 // OffsetToCenterPage for ratio calculation. 599 constexpr float kCenterPageRatioThreshold = 0.9f; 600 601 // Numeric values for the pref "print.center_page_on_sheet" 602 enum { 603 kPrintCenterPageOnSheetNever = 0, 604 kPrintCenterPageOnSheetAlways = 1, 605 kPrintCenterPageOnSheetAuto = 2 606 }; 607 608 // Returns an offset to center the page on the sheet, with a given scale. 609 // When no centering can/should happen, this will avoid extra calculations and 610 // return 0.0f. 611 // This takes into account the value of the pref "print.center_page_on_sheet". 612 static float OffsetToCenterPage(nscoord aContentSize, nscoord aSheetSize, 613 float aScale, float aAppUnitsPerPixel) { 614 MOZ_ASSERT(aScale <= 1.0f && aScale > 0.0f, 615 "Scale must be in the range (0,1]"); 616 const unsigned centerPagePref = StaticPrefs::print_center_page_on_sheet(); 617 if (centerPagePref == kPrintCenterPageOnSheetNever) { 618 return 0.0f; 619 } 620 621 // Determine the ratio of scaled page to the sheet size. 622 const float sheetSize = 623 NSAppUnitsToFloatPixels(aSheetSize, aAppUnitsPerPixel); 624 const float scaledContentSize = 625 NSAppUnitsToFloatPixels(aContentSize, aAppUnitsPerPixel) * aScale; 626 const float ratio = scaledContentSize / sheetSize; 627 628 // If the ratio is within the threshold, or the pref indicates we should 629 // always center the page, return half the difference to form the offset. 630 if (centerPagePref == kPrintCenterPageOnSheetAlways || 631 ratio >= kCenterPageRatioThreshold) { 632 return (sheetSize - scaledContentSize) * 0.5f; 633 } 634 return 0.0f; 635 } 636 637 // Helper for BuildDisplayList: 638 static gfx::Matrix4x4 ComputePagesPerSheetAndPageSizeTransform( 639 const nsIFrame* aFrame, float aAppUnitsPerPixel) { 640 MOZ_ASSERT(aFrame->IsPageFrame()); 641 auto* pageFrame = static_cast<const nsPageFrame*>(aFrame); 642 const nsSize contentPageSize = pageFrame->ComputePageSize(); 643 MOZ_ASSERT(contentPageSize.width > 0 && contentPageSize.height > 0); 644 nsSharedPageData* pd = pageFrame->GetSharedPageData(); 645 const auto* ppsInfo = pd->PagesPerSheetInfo(); 646 647 const nsContainerFrame* const parentFrame = pageFrame->GetParent(); 648 MOZ_ASSERT(parentFrame->IsPrintedSheetFrame(), 649 "Parent of nsPageFrame should be PrintedSheetFrame"); 650 const auto* sheetFrame = static_cast<const PrintedSheetFrame*>(parentFrame); 651 652 const double rotation = 653 pageFrame->GetPageOrientationRotation(pageFrame->GetSharedPageData()); 654 655 gfx::Matrix4x4 transform; 656 657 if (ppsInfo->mNumPages == 1) { 658 const nsSize sheetSize = sheetFrame->GetSizeForChildren(); 659 if (rotation != 0.0) { 660 const bool sheetIsPortrait = sheetSize.width < sheetSize.height; 661 const bool rotatingClockwise = rotation > 0.0; 662 663 // rotation point: 664 int32_t x, y; 665 if (rotatingClockwise != sheetIsPortrait) { 666 // rotating portrait clockwise, or landscape counterclockwise 667 x = y = std::min(sheetSize.width, sheetSize.height) / 2; 668 } else { 669 // rotating portrait counterclockwise, or landscape clockwise 670 x = y = std::max(sheetSize.width, sheetSize.height) / 2; 671 } 672 673 transform = gfx::Matrix4x4::Translation( 674 NSAppUnitsToFloatPixels(x, aAppUnitsPerPixel), 675 NSAppUnitsToFloatPixels(y, aAppUnitsPerPixel), 0); 676 transform.RotateZ(rotation); 677 transform.PreTranslate(NSAppUnitsToFloatPixels(-x, aAppUnitsPerPixel), 678 NSAppUnitsToFloatPixels(-y, aAppUnitsPerPixel), 0); 679 } 680 681 // If the difference in horizontal size, after scaling, is relatively small 682 // then center the page on the sheet. 683 const float scale = 684 pageFrame->ComputeSinglePPSPageSizeScale(contentPageSize); 685 const float centeringOffset = OffsetToCenterPage( 686 contentPageSize.width, sheetSize.width, scale, aAppUnitsPerPixel); 687 688 // Only bother with the translation if it is at least one pixel. 689 // It's possible for a mismatch in the paper size reported by the print 690 // server and the paper size from Gecko to lead to small offsets, or 691 // even (in combination with floating point error) a very small negative 692 // offset. Do not apply an offset in those cases. 693 if (centeringOffset >= 1.0f) { 694 transform.PreTranslate(centeringOffset, 0, 0); 695 } 696 transform.PreScale(scale, scale, 1); 697 return transform; 698 } 699 700 // The multiple pages-per-sheet case. 701 702 // Begin with the translation of the page to its pages-per-sheet grid "cell" 703 // (the grid origin accounts for the sheet's unwriteable margins): 704 const nsPoint gridOrigin = sheetFrame->GetGridOrigin(); 705 const nscoord cellWidth = sheetFrame->GetGridCellWidth(); 706 const nscoord cellHeight = sheetFrame->GetGridCellHeight(); 707 uint32_t rowIdx, colIdx; 708 std::tie(rowIdx, colIdx) = GetRowAndColFromIdx(pageFrame->IndexOnSheet(), 709 sheetFrame->GetGridNumCols()); 710 transform = gfx::Matrix4x4::Translation( 711 NSAppUnitsToFloatPixels(gridOrigin.x + nscoord(colIdx) * cellWidth, 712 aAppUnitsPerPixel), 713 NSAppUnitsToFloatPixels(gridOrigin.y + nscoord(rowIdx) * cellHeight, 714 aAppUnitsPerPixel), 715 0.0f); 716 717 // Scale the page to fit, centered, in the grid cell: 718 float scaleX = float(cellWidth) / float(contentPageSize.width); 719 float scaleY = float(cellHeight) / float(contentPageSize.height); 720 MOZ_ASSERT(scaleX > 0.0f && scaleX <= 1.0f && scaleY > 0.0f && 721 scaleY <= 1.0f); 722 float scale; 723 float dx = 0.0f, dy = 0.0f; 724 if (scaleX < scaleY) { 725 scale = scaleX; 726 // We need to scale down more for the width than the height, so we'll have 727 // some spare space in the page's vertical direction. We offset the page 728 // to share that space equally above and below the page to center it. 729 nscoord extraSpace = 730 cellHeight - NSToCoordRound(float(contentPageSize.height) * scale); 731 dy = NSAppUnitsToFloatPixels(extraSpace / 2, aAppUnitsPerPixel); 732 } else { 733 scale = scaleY; 734 nscoord extraSpace = 735 cellWidth - NSToCoordRound(float(contentPageSize.width) * scale); 736 dx = NSAppUnitsToFloatPixels(extraSpace / 2, aAppUnitsPerPixel); 737 } 738 transform.PreTranslate(dx, dy, 0.0f); 739 transform.PreScale(scale, scale, 1.0f); 740 741 // Apply 'page-orientation' rotation, if applicable: 742 if (rotation != 0.0) { 743 // We've already translated and scaled the page to fit the cell, ignoring 744 // rotation. Here we rotate the page around its center and, if necessary, 745 // also scale it to fit it to its cell for its orientation change. 746 747 float fitScale = 1.0f; 748 if (MOZ_LIKELY(cellWidth != cellHeight && 749 contentPageSize.width != contentPageSize.height)) { 750 // If neither the cell nor the page are square, the scale must change. 751 float cellRatio = float(cellWidth) / float(cellHeight); 752 float pageRatio = 753 float(contentPageSize.width) / float(contentPageSize.height); 754 const bool orientationWillMatchAfterRotation = 755 floor(cellRatio) != floor(pageRatio); 756 if (cellRatio > 1.0f) { 757 cellRatio = 1.0f / cellRatio; // normalize 758 } 759 if (pageRatio > 1.0f) { 760 pageRatio = 1.0f / pageRatio; // normalize 761 } 762 fitScale = std::max(cellRatio, pageRatio); 763 if (orientationWillMatchAfterRotation) { 764 // Scale up, not down 765 fitScale = 1.0f / fitScale; 766 } 767 } 768 769 transform.PreTranslate( 770 NSAppUnitsToFloatPixels(contentPageSize.width / 2, aAppUnitsPerPixel), 771 NSAppUnitsToFloatPixels(contentPageSize.height / 2, aAppUnitsPerPixel), 772 0); 773 if (MOZ_LIKELY(fitScale != 1.0f)) { 774 transform.PreScale(fitScale, fitScale, 1.0f); 775 } 776 transform.RotateZ(rotation); 777 transform.PreTranslate( 778 NSAppUnitsToFloatPixels(-contentPageSize.width / 2, aAppUnitsPerPixel), 779 NSAppUnitsToFloatPixels(-contentPageSize.height / 2, aAppUnitsPerPixel), 780 0); 781 } 782 783 return transform; 784 } 785 786 nsIFrame::ComputeTransformFunction nsPageFrame::GetTransformGetter() const { 787 return ComputePagesPerSheetAndPageSizeTransform; 788 } 789 790 nsPageContentFrame* nsPageFrame::PageContentFrame() const { 791 nsIFrame* const frame = mFrames.FirstChild(); 792 MOZ_ASSERT(frame, "pageFrame must have one child"); 793 MOZ_ASSERT(frame->IsPageContentFrame(), 794 "pageFrame must have pageContentFrame as the first child"); 795 return static_cast<nsPageContentFrame*>(frame); 796 } 797 798 nsSize nsPageFrame::ComputePageSize() const { 799 // Compute the expected page-size. 800 const StylePageSize& pageSize = PageContentFrame()->StylePage()->mSize; 801 nsSize size = PresContext()->GetPageSize(); 802 if (pageSize.IsSize()) { 803 // Use the specified size, 804 // ignoring sizes that include a zero width or height. 805 // These are also ignored in ServoStyleSet::GetPageSizeForPageName() 806 // when getting the paper size. 807 nscoord cssPageWidth = pageSize.AsSize().width.ToAppUnits(); 808 nscoord cssPageHeight = pageSize.AsSize().height.ToAppUnits(); 809 if (cssPageWidth > 0 && cssPageHeight > 0) { 810 return nsSize{cssPageWidth, cssPageHeight}; 811 } 812 // Invalid size; just return the default 813 return size; 814 } 815 816 if (pageSize.IsOrientation()) { 817 // Ensure the correct orientation is applied. 818 if (pageSize.AsOrientation() == StylePageSizeOrientation::Portrait) { 819 if (size.width > size.height) { 820 std::swap(size.width, size.height); 821 } 822 } else { 823 MOZ_ASSERT(pageSize.AsOrientation() == 824 StylePageSizeOrientation::Landscape); 825 if (size.width < size.height) { 826 std::swap(size.width, size.height); 827 } 828 } 829 } else { 830 MOZ_ASSERT(pageSize.IsAuto(), "Impossible page-size value?"); 831 } 832 return size; 833 } 834 835 float nsPageFrame::ComputeSinglePPSPageSizeScale( 836 const nsSize aContentPageSize) const { 837 MOZ_ASSERT(GetSharedPageData()->PagesPerSheetInfo()->mNumPages == 1, 838 "Only intended for the pps==1 case"); 839 MOZ_ASSERT(aContentPageSize == ComputePageSize(), 840 "Incorrect content page size"); 841 842 if (PageContentFrame()->StylePage()->mSize.IsAuto()) { 843 return 1.0f; 844 } 845 846 const nsContainerFrame* const parent = GetParent(); 847 MOZ_ASSERT(parent && parent->IsPrintedSheetFrame(), 848 "Parent of nsPageFrame should be PrintedSheetFrame"); 849 const auto* sheet = static_cast<const PrintedSheetFrame*>(parent); 850 851 // Compute scaling due to a possible mismatch in the paper size we are 852 // printing to (from the pres context) and the specified page size when the 853 // content uses "@page {size: ...}" to specify a page size for the content. 854 float scale = 1.0f; 855 856 const nsSize sheetSize = sheet->GetSizeForChildren(); 857 nscoord contentPageHeight = aContentPageSize.height; 858 // Scale down if the target is too wide. 859 if (aContentPageSize.width > sheetSize.width) { 860 scale *= float(sheetSize.width) / float(aContentPageSize.width); 861 contentPageHeight = NSToCoordRound(contentPageHeight * scale); 862 } 863 // Scale down if the target is too tall. 864 if (contentPageHeight > sheetSize.height) { 865 scale *= float(sheetSize.height) / float(contentPageHeight); 866 } 867 MOZ_ASSERT( 868 scale <= 1.0f, 869 "Page-size mismatches should only have caused us to scale down, not up."); 870 return scale; 871 } 872 873 double nsPageFrame::GetPageOrientationRotation(nsSharedPageData* aPD) const { 874 if (aPD->PagesPerSheetInfo()->mNumPages == 1 && !PresContext()->IsScreen() && 875 aPD->mPrintSettings->GetOutputFormat() != 876 nsIPrintSettings::kOutputFormatPDF) { 877 // In the single page-per-sheet case we rotate the page by essentially 878 // rotating the entire sheet. But we can't do that when the output device 879 // doesn't support mixed sheet orientations. 880 return 0.0; 881 } 882 883 const StylePageOrientation& orientation = 884 PageContentFrame()->StylePage()->mPageOrientation; 885 886 if (orientation == StylePageOrientation::RotateLeft) { 887 return -M_PI / 2.0; 888 } 889 if (orientation == StylePageOrientation::RotateRight) { 890 return M_PI / 2.0; 891 } 892 return 0.0; 893 } 894 895 void nsPageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 896 const nsDisplayListSet& aLists) { 897 nsDisplayList content(aBuilder); 898 nsDisplayListSet set(&content, &content, &content, &content, &content, 899 &content); 900 { 901 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 902 clipState.Clear(); 903 904 nsPresContext* const pc = PresContext(); 905 { 906 // We need to extend the building rect to include the specified page size 907 // (scaled by the print scaling factor), in case it is larger than the 908 // physical page size. In that case the nsPageFrame will be the size of 909 // the physical page, but the child nsPageContentFrame will be the larger 910 // specified page size. The more correct way to do this would be to fully 911 // reverse the result of ComputePagesPerSheetAndPageSizeTransform to 912 // handle this scaling, but this should have the same result and is 913 // easier. 914 const float scale = pc->GetPageScale(); 915 const nsSize pageSize = ComputePageSize(); 916 const nsRect scaledPageRect{0, 0, NSToCoordCeil(pageSize.width / scale), 917 NSToCoordCeil(pageSize.height / scale)}; 918 nsDisplayListBuilder::AutoBuildingDisplayList buildingForPageContentFrame( 919 aBuilder, this, scaledPageRect, scaledPageRect); 920 921 nsContainerFrame::BuildDisplayList(aBuilder, set); 922 } 923 924 if (pc->IsRootPaginatedDocument()) { 925 content.AppendNewToTop<nsDisplayHeaderFooter>(aBuilder, this); 926 927 // For print-preview, show margin guides if requested in the settings. 928 if (pc->Type() == nsPresContext::eContext_PrintPreview && 929 mPD->mPrintSettings->GetShowMarginGuides()) { 930 content.AppendNewToTop<nsDisplayGeneric>( 931 aBuilder, this, PaintMarginGuides, "MarginGuides", 932 DisplayItemType::TYPE_MARGIN_GUIDES); 933 } 934 } 935 } 936 937 // We'll be drawing the page with a (usually-trivial) 938 // N-pages-per-sheet transform applied, so our passed-in visible rect 939 // isn't meaningful while we're drawing our children, because the 940 // transform could scale down content whose coordinates are off-screen 941 // such that it ends up on-screen. So: we temporarily update the visible 942 // rect to be the child nsPageFrame's whole frame-rect (represented in 943 // this PrintedSheetFrame's coordinate space. 944 content.AppendNewToTop<nsDisplayTransform>( 945 aBuilder, this, &content, content.GetBuildingRect(), 946 nsDisplayTransform::WithTransformGetter); 947 948 set.MoveTo(aLists); 949 } 950 951 //------------------------------------------------------------------------------ 952 void nsPageFrame::DeterminePageNum() { 953 // If we have no previous continuation, we're page 1. Otherwise, we're 954 // just one more than our previous continuation's page number. 955 auto* prevContinuation = static_cast<nsPageFrame*>(GetPrevContinuation()); 956 mPageNum = prevContinuation ? prevContinuation->GetPageNum() + 1 : 1; 957 } 958 959 void nsPageFrame::PaintHeaderFooter(gfxContext& aRenderingContext, nsPoint aPt, 960 bool aDisableSubpixelAA) { 961 nsPresContext* pc = PresContext(); 962 963 nsRect rect(aPt, ComputePageSize()); 964 aRenderingContext.SetColor(sRGBColor::OpaqueBlack()); 965 966 DrawTargetAutoDisableSubpixelAntialiasing disable( 967 aRenderingContext.GetDrawTarget(), aDisableSubpixelAA); 968 969 // Get the FontMetrics to determine width.height of strings 970 nsFontMetrics::Params params; 971 params.userFontSet = pc->GetUserFontSet(); 972 params.textPerf = pc->GetTextPerfMetrics(); 973 params.featureValueLookup = pc->GetFontFeatureValuesLookup(); 974 RefPtr<nsFontMetrics> fontMet = pc->GetMetricsFor(mPD->mHeadFootFont, params); 975 976 nscoord ascent = fontMet->MaxAscent(); 977 nscoord visibleHeight = fontMet->MaxHeight(); 978 979 // print document headers and footers 980 nsString headerLeft, headerCenter, headerRight; 981 mPD->mPrintSettings->GetHeaderStrLeft(headerLeft); 982 mPD->mPrintSettings->GetHeaderStrCenter(headerCenter); 983 mPD->mPrintSettings->GetHeaderStrRight(headerRight); 984 DrawHeaderFooter(aRenderingContext, *fontMet, eHeader, headerLeft, 985 headerCenter, headerRight, rect, ascent, visibleHeight); 986 987 nsString footerLeft, footerCenter, footerRight; 988 mPD->mPrintSettings->GetFooterStrLeft(footerLeft); 989 mPD->mPrintSettings->GetFooterStrCenter(footerCenter); 990 mPD->mPrintSettings->GetFooterStrRight(footerRight); 991 DrawHeaderFooter(aRenderingContext, *fontMet, eFooter, footerLeft, 992 footerCenter, footerRight, rect, ascent, visibleHeight); 993 } 994 995 void nsPageFrame::SetSharedPageData(nsSharedPageData* aPD) { 996 mPD = aPD; 997 // Set the shared data into the page frame before reflow 998 PageContentFrame()->SetSharedPageData(mPD); 999 } 1000 1001 nsIFrame* NS_NewPageBreakFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 1002 MOZ_ASSERT(aPresShell, "null PresShell"); 1003 // check that we are only creating page break frames when printing 1004 NS_ASSERTION(aPresShell->GetPresContext()->IsPaginated(), 1005 "created a page break frame while not printing"); 1006 1007 return new (aPresShell) 1008 nsPageBreakFrame(aStyle, aPresShell->GetPresContext()); 1009 } 1010 1011 NS_IMPL_FRAMEARENA_HELPERS(nsPageBreakFrame) 1012 1013 nsPageBreakFrame::nsPageBreakFrame(ComputedStyle* aStyle, 1014 nsPresContext* aPresContext) 1015 : nsLeafFrame(aStyle, aPresContext, kClassID) {} 1016 1017 nsPageBreakFrame::~nsPageBreakFrame() = default; 1018 1019 IntrinsicSize nsPageBreakFrame::GetIntrinsicSize() { 1020 IntrinsicSize intrinsicSize; 1021 intrinsicSize.ISize(GetWritingMode()) 1022 .emplace(nsPresContext::CSSPixelsToAppUnits(1)); 1023 return intrinsicSize; 1024 } 1025 1026 void nsPageBreakFrame::Reflow(nsPresContext* aPresContext, 1027 ReflowOutput& aReflowOutput, 1028 const ReflowInput& aReflowInput, 1029 nsReflowStatus& aStatus) { 1030 DO_GLOBAL_REFLOW_COUNT("nsPageBreakFrame"); 1031 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 1032 1033 // Override reflow, since we don't want to deal with what our 1034 // computed values are. 1035 const WritingMode wm = aReflowInput.GetWritingMode(); 1036 nscoord bSize = aReflowInput.AvailableBSize(); 1037 if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { 1038 bSize = nscoord(0); 1039 } else if (GetContent()->IsHTMLElement(nsGkAtoms::legend)) { 1040 // If this is a page break frame for a _rendered legend_ then it should be 1041 // ignored since these frames are inserted inside the fieldset's inner 1042 // frame and thus "misplaced". nsFieldSetFrame::Reflow deals with these 1043 // forced breaks explicitly instead. 1044 const nsContainerFrame* parent = GetParent(); 1045 if (parent && 1046 parent->Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) { 1047 while ((parent = parent->GetParent())) { 1048 if (const nsFieldSetFrame* const fieldset = do_QueryFrame(parent)) { 1049 const auto* const legend = fieldset->GetLegend(); 1050 if (legend && legend->GetContent() == GetContent()) { 1051 bSize = nscoord(0); 1052 } 1053 break; 1054 } 1055 } 1056 } 1057 } 1058 LogicalSize finalSize(wm, *GetIntrinsicSize().ISize(wm), bSize); 1059 // round the height down to the nearest pixel 1060 // XXX(mats) why??? 1061 finalSize.BSize(wm) -= 1062 finalSize.BSize(wm) % nsPresContext::CSSPixelsToAppUnits(1); 1063 aReflowOutput.SetSize(wm, finalSize); 1064 } 1065 1066 #ifdef DEBUG_FRAME_DUMP 1067 nsresult nsPageBreakFrame::GetFrameName(nsAString& aResult) const { 1068 return MakeFrameName(u"PageBreak"_ns, aResult); 1069 } 1070 #endif