tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PrintedSheetFrame.cpp (17783B)


      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 https://mozilla.org/MPL/2.0/. */
      6 
      7 /* Rendering object for a printed or print-previewed sheet of paper */
      8 
      9 #include "mozilla/PrintedSheetFrame.h"
     10 
     11 #include "mozilla/PresShell.h"
     12 #include "mozilla/StaticPrefs_print.h"
     13 #include "nsCSSFrameConstructor.h"
     14 #include "nsPageContentFrame.h"
     15 #include "nsPageFrame.h"
     16 #include "nsPageSequenceFrame.h"
     17 
     18 using namespace mozilla;
     19 
     20 PrintedSheetFrame* NS_NewPrintedSheetFrame(PresShell* aPresShell,
     21                                           ComputedStyle* aStyle) {
     22  return new (aPresShell)
     23      PrintedSheetFrame(aStyle, aPresShell->GetPresContext());
     24 }
     25 
     26 namespace mozilla {
     27 
     28 NS_QUERYFRAME_HEAD(PrintedSheetFrame)
     29  NS_QUERYFRAME_ENTRY(PrintedSheetFrame)
     30 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
     31 
     32 NS_IMPL_FRAMEARENA_HELPERS(PrintedSheetFrame)
     33 
     34 void PrintedSheetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
     35                                         const nsDisplayListSet& aLists) {
     36  if (PresContext()->IsScreen()) {
     37    // Draw the background/shadow/etc. of a blank sheet of paper, for
     38    // print-preview.
     39    DisplayBorderBackgroundOutline(aBuilder, aLists);
     40  }
     41 
     42  for (auto* frame : mFrames) {
     43    if (!frame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE)) {
     44      BuildDisplayListForChild(aBuilder, frame, aLists);
     45    }
     46  }
     47 }
     48 
     49 // If the given page is included in the user's page range, this function
     50 // returns false. Otherwise, it tags the page with the
     51 // NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state bit and returns true.
     52 static bool TagIfSkippedByCustomRange(nsPageFrame* aPageFrame, int32_t aPageNum,
     53                                      nsSharedPageData* aPD) {
     54  if (!nsIPrintSettings::IsPageSkipped(aPageNum, aPD->mPageRanges)) {
     55    MOZ_ASSERT(!aPageFrame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE),
     56               "page frames NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state should "
     57               "only be set if we actually want to skip the page");
     58    return false;
     59  }
     60 
     61  aPageFrame->AddStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE);
     62  return true;
     63 }
     64 
     65 void PrintedSheetFrame::ClaimPageFrameFromPrevInFlow() {
     66  MoveOverflowToChildList();
     67  if (!GetPrevContinuation()) {
     68    // The first page content frame of each document will not yet have its page
     69    // style set yet. This is because normally page style is set either from
     70    // the previous page content frame, or using the new page name when named
     71    // pages cause a page break in block reflow. Ensure that, for the first
     72    // page, it is set here so that all nsPageContentFrames have their page
     73    // style set before reflow.
     74    auto* firstChild = PrincipalChildList().FirstChild();
     75    MOZ_ASSERT(firstChild && firstChild->IsPageFrame(),
     76               "PrintedSheetFrame only has nsPageFrame children");
     77    auto* pageFrame = static_cast<nsPageFrame*>(firstChild);
     78    pageFrame->PageContentFrame()->EnsurePageName();
     79  }
     80 }
     81 
     82 void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,
     83                               ReflowOutput& aReflowOutput,
     84                               const ReflowInput& aReflowInput,
     85                               nsReflowStatus& aStatus) {
     86  MarkInReflow();
     87  DO_GLOBAL_REFLOW_COUNT("PrintedSheetFrame");
     88  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
     89 
     90  // If we have a prev-in-flow, take its overflowing content:
     91  MoveOverflowToChildList();
     92 
     93  const WritingMode wm = aReflowInput.GetWritingMode();
     94 
     95  // See the comments for GetSizeForChildren.
     96  // Note that nsPageFrame::ComputeSinglePPSPageSizeScale depends on this value
     97  // and is currently called while reflowing a single nsPageFrame child (i.e.
     98  // before we've finished reflowing ourself). Ideally our children wouldn't be
     99  // accessing our dimensions until after we've finished reflowing ourself -
    100  // see bug 1835782.
    101  mSizeForChildren =
    102      nsSize(aReflowInput.AvailableISize(), aReflowInput.AvailableBSize());
    103  if (mPD->PagesPerSheetInfo()->mNumPages == 1) {
    104    auto* firstChild = PrincipalChildList().FirstChild();
    105    MOZ_ASSERT(firstChild && firstChild->IsPageFrame(),
    106               "PrintedSheetFrame only has nsPageFrame children");
    107    if (static_cast<nsPageFrame*>(firstChild)
    108            ->GetPageOrientationRotation(mPD) != 0.0) {
    109      std::swap(mSizeForChildren.width, mSizeForChildren.height);
    110    }
    111  }
    112 
    113  // Count the number of pages that are displayed on this sheet (i.e. how many
    114  // child frames we end up laying out, excluding any pages that are skipped
    115  // due to not being in the user's page-range selection).
    116  uint32_t numPagesOnThisSheet = 0;
    117 
    118  // Target for numPagesOnThisSheet.
    119  const uint32_t desiredPagesPerSheet = mPD->PagesPerSheetInfo()->mNumPages;
    120 
    121  if (desiredPagesPerSheet > 1) {
    122    ComputePagesPerSheetGridMetrics(mSizeForChildren);
    123  }
    124 
    125  // NOTE: I'm intentionally *not* using a range-based 'for' loop here, since
    126  // we potentially mutate the frame list (appending to the end) during the
    127  // list, which is not generally safe with range-based 'for' loops.
    128  for (auto* childFrame = mFrames.FirstChild(); childFrame;
    129       childFrame = childFrame->GetNextSibling()) {
    130    MOZ_ASSERT(childFrame->IsPageFrame(),
    131               "we're only expecting page frames as children");
    132    auto* pageFrame = static_cast<nsPageFrame*>(childFrame);
    133 
    134    // Be sure our child has a pointer to the nsSharedPageData and knows its
    135    // page number:
    136    pageFrame->SetSharedPageData(mPD);
    137    pageFrame->DeterminePageNum();
    138 
    139    if (!TagIfSkippedByCustomRange(pageFrame, pageFrame->GetPageNum(), mPD)) {
    140      // The page is going to be displayed on this sheet. Tell it its index
    141      // among the displayed pages, so we can use that to compute its "cell"
    142      // when painting.
    143      pageFrame->SetIndexOnSheet(numPagesOnThisSheet);
    144      numPagesOnThisSheet++;
    145    }
    146 
    147    // This is the app-unit size of the page (in physical & logical units).
    148    // Note: The page sizes come from CSS or else from the user selected size;
    149    // pages are never reflowed to fit their sheet - if/when necessary they are
    150    // scaled to fit their sheet. Hence why we get the page's own dimensions to
    151    // use as its "available space"/"container size" here.
    152    const nsSize physPageSize = pageFrame->ComputePageSize();
    153    const LogicalSize pageSize(wm, physPageSize);
    154 
    155    ReflowInput pageReflowInput(aPresContext, aReflowInput, pageFrame,
    156                                pageSize);
    157 
    158    // For layout purposes, we position *all* our nsPageFrame children at our
    159    // origin. Then, if we have multiple pages-per-sheet, we'll shrink & shift
    160    // each one into the right position as a paint-time effect, in
    161    // BuildDisplayList.
    162    LogicalPoint pagePos(wm);
    163 
    164    // Outparams for reflow:
    165    ReflowOutput pageReflowOutput(pageReflowInput);
    166    nsReflowStatus status;
    167 
    168    ReflowChild(pageFrame, aPresContext, pageReflowOutput, pageReflowInput, wm,
    169                pagePos, physPageSize, ReflowChildFlags::Default, status);
    170 
    171    FinishReflowChild(pageFrame, aPresContext, pageReflowOutput,
    172                      &pageReflowInput, wm, pagePos, physPageSize,
    173                      ReflowChildFlags::Default);
    174 
    175    // Since we don't support incremental reflow in printed documents (see the
    176    // early-return in nsPageSequenceFrame::Reflow), we can assume that this
    177    // was the first time that pageFrame has been reflowed, and so there's no
    178    // way that it could already have a next-in-flow. If it *did* have a
    179    // next-in-flow, we would need to handle it in the 'status' logic below.
    180    NS_ASSERTION(!pageFrame->GetNextInFlow(), "bad child flow list");
    181 
    182    // Did this page complete the document, or do we need to generate
    183    // another page frame?
    184    if (status.IsFullyComplete()) {
    185      // The page we just reflowed is the final page! Record its page number
    186      // as the number of pages:
    187      mPD->mRawNumPages = pageFrame->GetPageNum();
    188    } else {
    189      // Create a continuation for our page frame. We add the continuation to
    190      // our child list, and then potentially push it to our overflow list, if
    191      // it really belongs on the next sheet.
    192      nsIFrame* continuingPage =
    193          PresShell()->FrameConstructor()->CreateContinuingFrame(pageFrame,
    194                                                                 this);
    195      mFrames.InsertFrame(nullptr, pageFrame, continuingPage);
    196      const bool isContinuingPageSkipped =
    197          TagIfSkippedByCustomRange(static_cast<nsPageFrame*>(continuingPage),
    198                                    pageFrame->GetPageNum() + 1, mPD);
    199 
    200      // If we've already reached the target number of pages for this sheet,
    201      // and this continuation page that we just created is meant to be
    202      // displayed (i.e. it's in the chosen page range), then we need to push it
    203      // to our overflow list so that it'll go onto a subsequent sheet.
    204      // Otherwise we leave it on this sheet. This ensures we *only* generate
    205      // another sheet IFF there's a displayable page that will end up on it.
    206      if (numPagesOnThisSheet >= desiredPagesPerSheet &&
    207          !isContinuingPageSkipped) {
    208        PushChildrenToOverflow(continuingPage, pageFrame);
    209        aStatus.SetIncomplete();
    210      }
    211    }
    212  }
    213 
    214  // This should hold for the first sheet, because our UI should prevent the
    215  // user from creating a 0-length page range; and it should hold for
    216  // subsequent sheets because we should only create an additional sheet when
    217  // we discover a displayable (i.e. non-skipped) page that we need to push
    218  // to that new sheet.
    219 
    220  // XXXdholbert In certain edge cases (e.g. after a page-orientation-flip that
    221  // reduces the page count), it's possible for us to be given a page range
    222  // that is *entirely out-of-bounds* (with "from" & "to" both being larger
    223  // than our actual page-number count).  This scenario produces a single
    224  // PrintedSheetFrame with zero displayable pages on it, which is a weird
    225  // state to be in. This is hopefully a scenario that the frontend code can
    226  // detect and recover from (e.g. by clamping the range to our reported
    227  // `rawNumPages`), but it can't do that until *after* we've completed this
    228  // problematic reflow and can reported an up-to-date `rawNumPages` to the
    229  // frontend.  So: to give the frontend a chance to intervene and apply some
    230  // correction/clamping to its print-range parameters, we soften this
    231  // assertion *specifically for the first printed sheet*.
    232  if (!GetPrevContinuation()) {
    233    NS_WARNING_ASSERTION(numPagesOnThisSheet > 0,
    234                         "Shouldn't create a sheet with no displayable pages "
    235                         "on it");
    236  } else {
    237    MOZ_ASSERT(numPagesOnThisSheet > 0,
    238               "Shouldn't create a sheet with no displayable pages on it");
    239  }
    240 
    241  MOZ_ASSERT(numPagesOnThisSheet <= desiredPagesPerSheet,
    242             "Shouldn't have more than desired number of displayable pages "
    243             "on this sheet");
    244  mNumPages = numPagesOnThisSheet;
    245 
    246  // Populate our ReflowOutput outparam -- just use up all the
    247  // available space, for both our desired size & overflow areas.
    248  aReflowOutput.ISize(wm) = aReflowInput.AvailableISize();
    249  if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
    250    aReflowOutput.BSize(wm) = aReflowInput.AvailableBSize();
    251  }
    252  aReflowOutput.SetOverflowAreasToDesiredBounds();
    253 
    254  FinishAndStoreOverflow(&aReflowOutput);
    255 }
    256 
    257 nsSize PrintedSheetFrame::ComputeSheetSize(const nsPresContext* aPresContext) {
    258  // We use the user selected page (sheet) dimensions, and default to the
    259  // orientation as specified by the user.
    260  nsSize sheetSize = aPresContext->GetPageSize();
    261 
    262  // Don't waste cycles changing the orientation of a square.
    263  if (sheetSize.width == sheetSize.height) {
    264    return sheetSize;
    265  }
    266 
    267  if (!StaticPrefs::
    268          print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) {
    269    if (mPD->mPrintSettings->HasOrthogonalPagesPerSheet()) {
    270      std::swap(sheetSize.width, sheetSize.height);
    271    }
    272    return sheetSize;
    273  }
    274 
    275  auto* firstChild = PrincipalChildList().FirstChild();
    276  MOZ_ASSERT(firstChild->IsPageFrame(),
    277             "PrintedSheetFrame only has nsPageFrame children");
    278  auto* sheetsFirstPageFrame = static_cast<nsPageFrame*>(firstChild);
    279 
    280  nsSize pageSize = sheetsFirstPageFrame->ComputePageSize();
    281 
    282  // Don't waste cycles changing the orientation of a square.
    283  if (pageSize.width == pageSize.height) {
    284    return sheetSize;
    285  }
    286 
    287  const bool pageIsRotated =
    288      sheetsFirstPageFrame->GetPageOrientationRotation(mPD) != 0.0;
    289 
    290  if (pageIsRotated && pageSize.width == pageSize.height) {
    291    // Straighforward rotation without needing sheet orientation optimization.
    292    std::swap(sheetSize.width, sheetSize.height);
    293    return sheetSize;
    294  }
    295 
    296  // Try to orient the sheet optimally based on the physical orientation of the
    297  // first/sole page on the sheet. (In the multiple pages-per-sheet case, the
    298  // first page is the only one that exists at this point in the code, so it is
    299  // the only one we can reason about. Any other pages may, or may not, have
    300  // the same physical orientation.)
    301 
    302  if (pageIsRotated) {
    303    // Fix up for its physical orientation:
    304    std::swap(pageSize.width, pageSize.height);
    305  }
    306 
    307  const bool pageIsPortrait = pageSize.width < pageSize.height;
    308  const bool sheetIsPortrait = sheetSize.width < sheetSize.height;
    309 
    310  // Switch the sheet orientation if the page orientation is different, or
    311  // if we need to switch it because the number of pages-per-sheet demands
    312  // orthogonal sheet layout, but not if both are true since then we'd
    313  // actually need to double switch.
    314  if ((sheetIsPortrait != pageIsPortrait) !=
    315      mPD->mPrintSettings->HasOrthogonalPagesPerSheet()) {
    316    std::swap(sheetSize.width, sheetSize.height);
    317  }
    318 
    319  return sheetSize;
    320 }
    321 
    322 void PrintedSheetFrame::ComputePagesPerSheetGridMetrics(
    323    const nsSize& aSheetSize) {
    324  MOZ_ASSERT(mPD->PagesPerSheetInfo()->mNumPages > 1,
    325             "Unnecessary to call this in a regular 1-page-per-sheet scenario; "
    326             "the computed values won't ever be used in that case");
    327 
    328  // Compute the space available for the pages-per-sheet "page grid" (just
    329  // subtract the sheet's unwriteable margin area):
    330  nsSize availSpaceOnSheet = aSheetSize;
    331  nsMargin uwm = mPD->mPrintSettings->GetIgnoreUnwriteableMargins()
    332                     ? nsMargin{}
    333                     : nsPresContext::CSSTwipsToAppUnits(
    334                           mPD->mPrintSettings->GetUnwriteableMarginInTwips());
    335 
    336  // XXXjwatt Once we support heterogeneous sheet orientations, we'll also need
    337  // to rotate uwm if this sheet is not the primary orientation.
    338  if (mPD->mPrintSettings->HasOrthogonalPagesPerSheet()) {
    339    // aSheetSize already takes account of the switch of *sheet* orientation
    340    // that we do in this case (the orientation implied by the page size
    341    // dimensions in the nsIPrintSettings applies to *pages*). That is not the
    342    // case for the unwriteable margins since we got them from the
    343    // nsIPrintSettings object ourself, so we need to adjust `uwm` here.
    344    //
    345    // Note: In practice, sheets with an orientation that is orthogonal to the
    346    // physical orientation of sheets output by a printer must be rotated 90
    347    // degrees for/by the printer. In that case the convention seems to be that
    348    // the "left" edge of the orthogonally oriented sheet becomes the "top",
    349    // and so forth. The rotation direction will matter in the case that the
    350    // top and bottom unwriteable margins are different, or the left and right
    351    // unwriteable margins are different. So we need to match this behavior,
    352    // which means we must rotate the `uwm` 90 degrees *counter-clockwise*.
    353    nsMargin rotated(uwm.right, uwm.bottom, uwm.left, uwm.top);
    354    uwm = rotated;
    355  }
    356 
    357  availSpaceOnSheet.width -= uwm.LeftRight();
    358  availSpaceOnSheet.height -= uwm.TopBottom();
    359 
    360  if (MOZ_UNLIKELY(availSpaceOnSheet.IsEmpty())) {
    361    // This sort of thing should be rare, but it can happen if there are
    362    // bizarre page sizes, and/or if there's an unexpectedly large unwriteable
    363    // margin area.
    364    NS_WARNING("Zero area for pages-per-sheet grid, or zero-sized grid");
    365    mGridOrigin = nsPoint(0, 0);
    366    mGridNumCols = 1;
    367    return;
    368  }
    369 
    370  // If there are a different number of rows vs. cols, we'll aim to put
    371  // the larger number of items in the longer axis.
    372  const auto* ppsInfo = mPD->PagesPerSheetInfo();
    373  uint32_t smallerNumTracks = ppsInfo->mNumPages / ppsInfo->mLargerNumTracks;
    374  bool sheetIsPortraitLike = aSheetSize.width < aSheetSize.height;
    375  auto numCols =
    376      sheetIsPortraitLike ? smallerNumTracks : ppsInfo->mLargerNumTracks;
    377  auto numRows =
    378      sheetIsPortraitLike ? ppsInfo->mLargerNumTracks : smallerNumTracks;
    379 
    380  mGridOrigin = nsPoint(uwm.left, uwm.top);
    381  mGridNumCols = numCols;
    382  mGridCellWidth = availSpaceOnSheet.width / nscoord(numCols);
    383  mGridCellHeight = availSpaceOnSheet.height / nscoord(numRows);
    384 }
    385 
    386 gfx::IntSize PrintedSheetFrame::GetPrintTargetSizeInPoints(
    387    const int32_t aAppUnitsPerPhysicalInch) const {
    388  const auto size = GetSize();
    389  MOZ_ASSERT(size.width > 0 && size.height > 0);
    390  const float pointsPerAppUnit =
    391      POINTS_PER_INCH_FLOAT / float(aAppUnitsPerPhysicalInch);
    392  return IntSize::Ceil(float(size.width) * pointsPerAppUnit,
    393                       float(size.height) * pointsPerAppUnit);
    394 }
    395 
    396 #ifdef DEBUG_FRAME_DUMP
    397 nsresult PrintedSheetFrame::GetFrameName(nsAString& aResult) const {
    398  return MakeFrameName(u"PrintedSheet"_ns, aResult);
    399 }
    400 #endif
    401 
    402 }  // namespace mozilla