forked from mirrors/gecko-dev
OS print drivers/devices know nothing about page dimensions unless we tell them. Previously, the physical page dimensions (including orientation) have always been the same, so communicating their dimensions once at the start of a print has been enough. In preparation for supporting different "physical" page dimensions (in the immediate future only different page orientations) when we save to PDF, we need to have the infrastructure to pass dimensions through on a page-by-page basis. This patch adds that. None of the PrintTarget subclasses do anything with this extra information yet, but in a follow-up patch PrintTargetPDF will use this information to create PDFs with mixed page orientations. Differential Revision: https://phabricator.services.mozilla.com/D179423
317 lines
14 KiB
C++
317 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
/* Rendering object for a printed or print-previewed sheet of paper */
|
|
|
|
#include "mozilla/PrintedSheetFrame.h"
|
|
|
|
#include <tuple>
|
|
|
|
#include "mozilla/StaticPrefs_print.h"
|
|
#include "nsCSSFrameConstructor.h"
|
|
#include "nsPageFrame.h"
|
|
#include "nsPageSequenceFrame.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
PrintedSheetFrame* NS_NewPrintedSheetFrame(PresShell* aPresShell,
|
|
ComputedStyle* aStyle) {
|
|
return new (aPresShell)
|
|
PrintedSheetFrame(aStyle, aPresShell->GetPresContext());
|
|
}
|
|
|
|
namespace mozilla {
|
|
|
|
NS_QUERYFRAME_HEAD(PrintedSheetFrame)
|
|
NS_QUERYFRAME_ENTRY(PrintedSheetFrame)
|
|
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(PrintedSheetFrame)
|
|
|
|
void PrintedSheetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|
const nsDisplayListSet& aLists) {
|
|
if (PresContext()->IsScreen()) {
|
|
// Draw the background/shadow/etc. of a blank sheet of paper, for
|
|
// print-preview.
|
|
DisplayBorderBackgroundOutline(aBuilder, aLists);
|
|
}
|
|
|
|
for (auto* frame : mFrames) {
|
|
if (!frame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE)) {
|
|
BuildDisplayListForChild(aBuilder, frame, aLists);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the given page is included in the user's page range, this function
|
|
// returns false. Otherwise, it tags the page with the
|
|
// NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state bit and returns true.
|
|
static bool TagIfSkippedByCustomRange(nsPageFrame* aPageFrame, int32_t aPageNum,
|
|
nsSharedPageData* aPD) {
|
|
if (!nsIPrintSettings::IsPageSkipped(aPageNum, aPD->mPageRanges)) {
|
|
MOZ_ASSERT(!aPageFrame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE),
|
|
"page frames NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state should "
|
|
"only be set if we actually want to skip the page");
|
|
return false;
|
|
}
|
|
|
|
aPageFrame->AddStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE);
|
|
return true;
|
|
}
|
|
|
|
void PrintedSheetFrame::ClaimPageFrameFromPrevInFlow() {
|
|
MoveOverflowToChildList();
|
|
}
|
|
|
|
void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,
|
|
ReflowOutput& aReflowOutput,
|
|
const ReflowInput& aReflowInput,
|
|
nsReflowStatus& aStatus) {
|
|
MarkInReflow();
|
|
DO_GLOBAL_REFLOW_COUNT("PrintedSheetFrame");
|
|
DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
|
|
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
|
|
|
|
// If we have a prev-in-flow, take its overflowing content:
|
|
MoveOverflowToChildList();
|
|
|
|
const WritingMode wm = aReflowInput.GetWritingMode();
|
|
|
|
// Count the number of pages that are displayed on this sheet (i.e. how many
|
|
// child frames we end up laying out, excluding any pages that are skipped
|
|
// due to not being in the user's page-range selection).
|
|
uint32_t numPagesOnThisSheet = 0;
|
|
|
|
// Target for numPagesOnThisSheet.
|
|
const uint32_t desiredPagesPerSheet = mPD->PagesPerSheetInfo()->mNumPages;
|
|
|
|
if (desiredPagesPerSheet > 1) {
|
|
ComputePagesPerSheetGridMetrics(
|
|
nsSize(aReflowInput.AvailableISize(), aReflowInput.AvailableBSize()));
|
|
}
|
|
|
|
// NOTE: I'm intentionally *not* using a range-based 'for' loop here, since
|
|
// we potentially mutate the frame list (appending to the end) during the
|
|
// list, which is not generally safe with range-based 'for' loops.
|
|
for (auto* childFrame = mFrames.FirstChild(); childFrame;
|
|
childFrame = childFrame->GetNextSibling()) {
|
|
MOZ_ASSERT(childFrame->IsPageFrame(),
|
|
"we're only expecting page frames as children");
|
|
auto* pageFrame = static_cast<nsPageFrame*>(childFrame);
|
|
|
|
// Be sure our child has a pointer to the nsSharedPageData and knows its
|
|
// page number:
|
|
pageFrame->SetSharedPageData(mPD);
|
|
pageFrame->DeterminePageNum();
|
|
|
|
if (!TagIfSkippedByCustomRange(pageFrame, pageFrame->GetPageNum(), mPD)) {
|
|
// The page is going to be displayed on this sheet. Tell it its index
|
|
// among the displayed pages, so we can use that to compute its "cell"
|
|
// when painting.
|
|
pageFrame->SetIndexOnSheet(numPagesOnThisSheet);
|
|
numPagesOnThisSheet++;
|
|
}
|
|
|
|
// This is the app-unit size of the page (in physical & logical units).
|
|
// Note: The page sizes come from CSS or else from the user selected size;
|
|
// pages are never reflowed to fit their sheet - if/when necessary they are
|
|
// scaled to fit their sheet. Hence why we get the page's own dimensions to
|
|
// use as its "available space"/"container size" here.
|
|
const nsSize physPageSize = pageFrame->ComputePageSize();
|
|
const LogicalSize pageSize(wm, physPageSize);
|
|
|
|
ReflowInput pageReflowInput(aPresContext, aReflowInput, pageFrame,
|
|
pageSize);
|
|
|
|
// For layout purposes, we position *all* our nsPageFrame children at our
|
|
// origin. Then, if we have multiple pages-per-sheet, we'll shrink & shift
|
|
// each one into the right position as a paint-time effect, in
|
|
// BuildDisplayList.
|
|
LogicalPoint pagePos(wm);
|
|
|
|
// Outparams for reflow:
|
|
ReflowOutput pageReflowOutput(pageReflowInput);
|
|
nsReflowStatus status;
|
|
|
|
ReflowChild(pageFrame, aPresContext, pageReflowOutput, pageReflowInput, wm,
|
|
pagePos, physPageSize, ReflowChildFlags::Default, status);
|
|
|
|
FinishReflowChild(pageFrame, aPresContext, pageReflowOutput,
|
|
&pageReflowInput, wm, pagePos, physPageSize,
|
|
ReflowChildFlags::Default);
|
|
|
|
// Since we don't support incremental reflow in printed documents (see the
|
|
// early-return in nsPageSequenceFrame::Reflow), we can assume that this
|
|
// was the first time that pageFrame has been reflowed, and so there's no
|
|
// way that it could already have a next-in-flow. If it *did* have a
|
|
// next-in-flow, we would need to handle it in the 'status' logic below.
|
|
NS_ASSERTION(!pageFrame->GetNextInFlow(), "bad child flow list");
|
|
|
|
// Did this page complete the document, or do we need to generate
|
|
// another page frame?
|
|
if (status.IsFullyComplete()) {
|
|
// The page we just reflowed is the final page! Record its page number
|
|
// as the number of pages:
|
|
mPD->mRawNumPages = pageFrame->GetPageNum();
|
|
} else {
|
|
// Create a continuation for our page frame. We add the continuation to
|
|
// our child list, and then potentially push it to our overflow list, if
|
|
// it really belongs on the next sheet.
|
|
nsIFrame* continuingPage =
|
|
PresShell()->FrameConstructor()->CreateContinuingFrame(pageFrame,
|
|
this);
|
|
mFrames.InsertFrame(nullptr, pageFrame, continuingPage);
|
|
const bool isContinuingPageSkipped =
|
|
TagIfSkippedByCustomRange(static_cast<nsPageFrame*>(continuingPage),
|
|
pageFrame->GetPageNum() + 1, mPD);
|
|
|
|
// If we've already reached the target number of pages for this sheet,
|
|
// and this continuation page that we just created is meant to be
|
|
// displayed (i.e. it's in the chosen page range), then we need to push it
|
|
// to our overflow list so that it'll go onto a subsequent sheet.
|
|
// Otherwise we leave it on this sheet. This ensures we *only* generate
|
|
// another sheet IFF there's a displayable page that will end up on it.
|
|
if (numPagesOnThisSheet >= desiredPagesPerSheet &&
|
|
!isContinuingPageSkipped) {
|
|
PushChildrenToOverflow(continuingPage, pageFrame);
|
|
aStatus.SetIncomplete();
|
|
}
|
|
}
|
|
}
|
|
|
|
// This should hold for the first sheet, because our UI should prevent the
|
|
// user from creating a 0-length page range; and it should hold for
|
|
// subsequent sheets because we should only create an additional sheet when
|
|
// we discover a displayable (i.e. non-skipped) page that we need to push
|
|
// to that new sheet.
|
|
|
|
// XXXdholbert In certain edge cases (e.g. after a page-orientation-flip that
|
|
// reduces the page count), it's possible for us to be given a page range
|
|
// that is *entirely out-of-bounds* (with "from" & "to" both being larger
|
|
// than our actual page-number count). This scenario produces a single
|
|
// PrintedSheetFrame with zero displayable pages on it, which is a weird
|
|
// state to be in. This is hopefully a scenario that the frontend code can
|
|
// detect and recover from (e.g. by clamping the range to our reported
|
|
// `rawNumPages`), but it can't do that until *after* we've completed this
|
|
// problematic reflow and can reported an up-to-date `rawNumPages` to the
|
|
// frontend. So: to give the frontend a chance to intervene and apply some
|
|
// correction/clamping to its print-range parameters, we soften this
|
|
// assertion *specifically for the first printed sheet*.
|
|
if (!GetPrevContinuation()) {
|
|
NS_WARNING_ASSERTION(numPagesOnThisSheet > 0,
|
|
"Shouldn't create a sheet with no displayable pages "
|
|
"on it");
|
|
} else {
|
|
MOZ_ASSERT(numPagesOnThisSheet > 0,
|
|
"Shouldn't create a sheet with no displayable pages on it");
|
|
}
|
|
|
|
MOZ_ASSERT(numPagesOnThisSheet <= desiredPagesPerSheet,
|
|
"Shouldn't have more than desired number of displayable pages "
|
|
"on this sheet");
|
|
mNumPages = numPagesOnThisSheet;
|
|
|
|
// Populate our ReflowOutput outparam -- just use up all the
|
|
// available space, for both our desired size & overflow areas.
|
|
aReflowOutput.ISize(wm) = aReflowInput.AvailableISize();
|
|
if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
|
|
aReflowOutput.BSize(wm) = aReflowInput.AvailableBSize();
|
|
}
|
|
aReflowOutput.SetOverflowAreasToDesiredBounds();
|
|
|
|
FinishAndStoreOverflow(&aReflowOutput);
|
|
}
|
|
|
|
nsSize PrintedSheetFrame::PrecomputeSheetSize(
|
|
const nsPresContext* aPresContext) {
|
|
mPrecomputedSize = aPresContext->GetPageSize();
|
|
if (mPD->mPrintSettings->HasOrthogonalSheetsAndPages()) {
|
|
std::swap(mPrecomputedSize.width, mPrecomputedSize.height);
|
|
}
|
|
return mPrecomputedSize;
|
|
}
|
|
|
|
void PrintedSheetFrame::ComputePagesPerSheetGridMetrics(
|
|
const nsSize& aSheetSize) {
|
|
MOZ_ASSERT(mPD->PagesPerSheetInfo()->mNumPages > 1,
|
|
"Unnecessary to call this in a regular 1-page-per-sheet scenario; "
|
|
"the computed values won't ever be used in that case");
|
|
|
|
// Compute the space available for the pages-per-sheet "page grid" (just
|
|
// subtract the sheet's unwriteable margin area):
|
|
nsSize availSpaceOnSheet = aSheetSize;
|
|
nsMargin uwm = mPD->mPrintSettings->GetIgnoreUnwriteableMargins()
|
|
? nsMargin{}
|
|
: nsPresContext::CSSTwipsToAppUnits(
|
|
mPD->mPrintSettings->GetUnwriteableMarginInTwips());
|
|
|
|
// XXXjwatt Once we support heterogeneous sheet orientations, we'll also need
|
|
// to rotate uwm if this sheet is not the primary orientation.
|
|
if (mPD->mPrintSettings->HasOrthogonalSheetsAndPages()) {
|
|
// aSheetSize already takes account of the switch of *sheet* orientation
|
|
// that we do in this case (the orientation implied by the page size
|
|
// dimensions in the nsIPrintSettings applies to *pages*). That is not the
|
|
// case for the unwriteable margins since we got them from the
|
|
// nsIPrintSettings object ourself, so we need to adjust `uwm` here.
|
|
//
|
|
// Note: In practice, sheets with an orientation that is orthogonal to the
|
|
// physical orientation of sheets output by a printer must be rotated 90
|
|
// degrees for/by the printer. In that case the convention seems to be that
|
|
// the "left" edge of the orthogonally oriented sheet becomes the "top",
|
|
// and so forth. The rotation direction will matter in the case that the
|
|
// top and bottom unwriteable margins are different, or the left and right
|
|
// unwriteable margins are different. So we need to match this behavior,
|
|
// which means we must rotate the `uwm` 90 degrees *counter-clockwise*.
|
|
nsMargin rotated(uwm.right, uwm.bottom, uwm.left, uwm.top);
|
|
uwm = rotated;
|
|
}
|
|
|
|
availSpaceOnSheet.width -= uwm.LeftRight();
|
|
availSpaceOnSheet.height -= uwm.TopBottom();
|
|
|
|
if (MOZ_UNLIKELY(availSpaceOnSheet.IsEmpty())) {
|
|
// This sort of thing should be rare, but it can happen if there are
|
|
// bizarre page sizes, and/or if there's an unexpectedly large unwriteable
|
|
// margin area.
|
|
NS_WARNING("Zero area for pages-per-sheet grid, or zero-sized grid");
|
|
mGridOrigin = nsPoint(0, 0);
|
|
mGridNumCols = 1;
|
|
return;
|
|
}
|
|
|
|
// If there are a different number of rows vs. cols, we'll aim to put
|
|
// the larger number of items in the longer axis.
|
|
const auto* ppsInfo = mPD->PagesPerSheetInfo();
|
|
uint32_t smallerNumTracks = ppsInfo->mNumPages / ppsInfo->mLargerNumTracks;
|
|
bool sheetIsPortraitLike = aSheetSize.width < aSheetSize.height;
|
|
auto numCols =
|
|
sheetIsPortraitLike ? smallerNumTracks : ppsInfo->mLargerNumTracks;
|
|
auto numRows =
|
|
sheetIsPortraitLike ? ppsInfo->mLargerNumTracks : smallerNumTracks;
|
|
|
|
mGridOrigin = nsPoint(uwm.left, uwm.top);
|
|
mGridNumCols = numCols;
|
|
mGridCellWidth = availSpaceOnSheet.width / nscoord(numCols);
|
|
mGridCellHeight = availSpaceOnSheet.height / nscoord(numRows);
|
|
}
|
|
|
|
gfx::IntSize PrintedSheetFrame::GetPrintTargetSizeInPoints(
|
|
const int32_t aAppUnitsPerPhysicalInch) const {
|
|
const auto size = GetSize();
|
|
MOZ_ASSERT(size.width > 0 && size.height > 0);
|
|
const float pointsPerAppUnit =
|
|
POINTS_PER_INCH_FLOAT / float(aAppUnitsPerPhysicalInch);
|
|
return IntSize::Ceil(float(size.width) * pointsPerAppUnit,
|
|
float(size.height) * pointsPerAppUnit);
|
|
}
|
|
|
|
#ifdef DEBUG_FRAME_DUMP
|
|
nsresult PrintedSheetFrame::GetFrameName(nsAString& aResult) const {
|
|
return MakeFrameName(u"PrintedSheet"_ns, aResult);
|
|
}
|
|
#endif
|
|
|
|
} // namespace mozilla
|