Skip to content

Commit

Permalink
Bug 1833244 p2. Implement sheet orientation switching/rotation for `p…
Browse files Browse the repository at this point in the history
…age-orientation`. r=AlaskanEmily,dholbert

Where supported (print preview and print-to-PDF), this implements changing the
orientation and/or rotation of print sheets, as appropriate, in response to CSS
`page-orientation`. When supported we:

- in the single page-per-sheet case, rotate the sheet in order to implement
  any `page-orientation` rotation on the sheet. Rotating the sheet is necessary
  so that when we output PDF files the pages visually have the correct
  orientation.

- in the multiple pages-per-sheet case, we already rotate individual pages in
  their grid cell. This commit keeps such pages rotated, as appropriate, but
  augments that behavior by switching the orientation of the sheet (based on
  the first page on the sheet), if necessary, to make best use of the space on
  the sheet. (We can't know what orientation any subsequent pages on the sheet
  will have, so we assume the same orientation as the first one.)

Depends on D179423

Differential Revision: https://phabricator.services.mozilla.com/D179448
  • Loading branch information
jwatt committed Aug 21, 2023
1 parent 06a7480 commit a4b6e0c
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 74 deletions.
20 changes: 18 additions & 2 deletions gfx/thebes/PrintTargetCG.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "cairo.h"
#include "cairo-quartz.h"
#include "mozilla/gfx/HelpersCairo.h"
#include "mozilla/StaticPrefs_layout.h"
#include "nsObjCExceptions.h"
#include "nsString.h"
#include "nsIOutputStream.h"
Expand Down Expand Up @@ -218,7 +219,15 @@ static CGContextRef CreatePrintToStreamContext(nsIOutputStream* aOutputStream,
CGContextBeginPage(mPrintToStreamContext, nullptr);
context = mPrintToStreamContext;
} else {
// XXX Why are we calling this if we don't check the return value?
PMSessionError(mPrintSession);

if (StaticPrefs::layout_css_page_orientation_enabled()) {
::PMOrientation pageOrientation =
aSizeInPoints.width < aSizeInPoints.height ? kPMPortrait : kPMLandscape;
::PMSetOrientation(mPageFormat, pageOrientation, kPMUnlocked);
// We don't need to reset the orientation, since we set it for every page.
}
OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, nullptr);
if (status != noErr) {
return NS_ERROR_ABORT;
Expand All @@ -233,8 +242,15 @@ static CGContextRef CreatePrintToStreamContext(nsIOutputStream* aOutputStream,
}
}

unsigned int width = static_cast<unsigned int>(mSize.width);
unsigned int height = static_cast<unsigned int>(mSize.height);
unsigned int width;
unsigned int height;
if (StaticPrefs::layout_css_page_orientation_enabled()) {
width = static_cast<unsigned int>(aSizeInPoints.width);
height = static_cast<unsigned int>(aSizeInPoints.height);
} else {
width = static_cast<unsigned int>(mSize.width);
height = static_cast<unsigned int>(mSize.height);
}

// Initially, origin is at bottom-left corner of the paper.
// Here, we translate it to top-left corner of the paper.
Expand Down
12 changes: 12 additions & 0 deletions gfx/thebes/PrintTargetPDF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "cairo.h"
#include "cairo-pdf.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/StaticPrefs_layout.h"
#include "nsContentUtils.h"
#include "nsString.h"

Expand Down Expand Up @@ -74,6 +75,17 @@ already_AddRefed<PrintTargetPDF> PrintTargetPDF::CreateOrNull(
return target.forget();
}

nsresult PrintTargetPDF::BeginPage(const IntSize& aSizeInPoints) {
if (StaticPrefs::layout_css_page_orientation_enabled()) {
cairo_pdf_surface_set_size(mCairoSurface, aSizeInPoints.width,
aSizeInPoints.height);
if (cairo_surface_status(mCairoSurface)) {
return NS_ERROR_FAILURE;
}
}
return PrintTarget::BeginPage(aSizeInPoints);
}

nsresult PrintTargetPDF::EndPage() {
cairo_surface_show_page(mCairoSurface);
return PrintTarget::EndPage();
Expand Down
1 change: 1 addition & 0 deletions gfx/thebes/PrintTargetPDF.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class PrintTargetPDF final : public PrintTarget {
static already_AddRefed<PrintTargetPDF> CreateOrNull(
nsIOutputStream* aStream, const IntSize& aSizeInPoints);

nsresult BeginPage(const IntSize& aSizeInPoints) override;
nsresult EndPage() override;
void Finish() override;

Expand Down
97 changes: 89 additions & 8 deletions layout/generic/PrintedSheetFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

#include <tuple>

#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_print.h"
#include "nsCSSFrameConstructor.h"
#include "nsPageContentFrame.h"
#include "nsPageFrame.h"
#include "nsPageSequenceFrame.h"

Expand Down Expand Up @@ -64,6 +66,19 @@ static bool TagIfSkippedByCustomRange(nsPageFrame* aPageFrame, int32_t aPageNum,

void PrintedSheetFrame::ClaimPageFrameFromPrevInFlow() {
MoveOverflowToChildList();
if (!GetPrevContinuation()) {
// The first page content frame of each document will not yet have its page
// style set yet. This is because normally page style is set either from
// the previous page content frame, or using the new page name when named
// pages cause a page break in block reflow. Ensure that, for the first
// page, it is set here so that all nsPageContentFrames have their page
// style set before reflow.
auto* firstChild = PrincipalChildList().FirstChild();
MOZ_ASSERT(firstChild && firstChild->IsPageFrame(),
"PrintedSheetFrame only has nsPageFrame children");
auto* pageFrame = static_cast<nsPageFrame*>(firstChild);
pageFrame->PageContentFrame()->EnsurePageName();
}
}

void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,
Expand All @@ -80,6 +95,24 @@ void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,

const WritingMode wm = aReflowInput.GetWritingMode();

// See the comments for GetSizeForChildren.
// Note that nsPageFrame::ComputeSinglePPSPageSizeScale depends on this value
// and is currently called while reflowing a single nsPageFrame child (i.e.
// before we've finished reflowing ourself). Ideally our children wouldn't be
// accessing our dimensions until after we've finished reflowing ourself -
// see bug 1835782.
mSizeForChildren =
nsSize(aReflowInput.AvailableISize(), aReflowInput.AvailableBSize());
if (mPD->PagesPerSheetInfo()->mNumPages == 1) {
auto* firstChild = PrincipalChildList().FirstChild();
MOZ_ASSERT(firstChild && firstChild->IsPageFrame(),
"PrintedSheetFrame only has nsPageFrame children");
if (static_cast<nsPageFrame*>(firstChild)
->GetPageOrientationRotation(mPD) != 0.0) {
std::swap(mSizeForChildren.width, mSizeForChildren.height);
}
}

// 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).
Expand All @@ -89,8 +122,7 @@ void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,
const uint32_t desiredPagesPerSheet = mPD->PagesPerSheetInfo()->mNumPages;

if (desiredPagesPerSheet > 1) {
ComputePagesPerSheetGridMetrics(
nsSize(aReflowInput.AvailableISize(), aReflowInput.AvailableBSize()));
ComputePagesPerSheetGridMetrics(mSizeForChildren);
}

// NOTE: I'm intentionally *not* using a range-based 'for' loop here, since
Expand Down Expand Up @@ -225,13 +257,62 @@ void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,
FinishAndStoreOverflow(&aReflowOutput);
}

nsSize PrintedSheetFrame::PrecomputeSheetSize(
const nsPresContext* aPresContext) {
mPrecomputedSize = aPresContext->GetPageSize();
if (mPD->mPrintSettings->HasOrthogonalSheetsAndPages()) {
std::swap(mPrecomputedSize.width, mPrecomputedSize.height);
nsSize PrintedSheetFrame::ComputeSheetSize(const nsPresContext* aPresContext) {
// We use the user selected page (sheet) dimensions, and default to the
// orientation as specified by the user.
nsSize sheetSize = aPresContext->GetPageSize();

// Don't waste cycle changing the orientation of a square.
if (sheetSize.width == sheetSize.height) {
return sheetSize;
}
return mPrecomputedSize;

if (!StaticPrefs::layout_css_page_orientation_enabled()) {
if (mPD->mPrintSettings->HasOrthogonalSheetsAndPages()) {
std::swap(sheetSize.width, sheetSize.height);
}
return sheetSize;
}

auto* firstChild = PrincipalChildList().FirstChild();
MOZ_ASSERT(firstChild->IsPageFrame(),
"PrintedSheetFrame only has nsPageFrame children");
auto* sheetsFirstPageFrame = static_cast<nsPageFrame*>(firstChild);

nsSize pageSize = sheetsFirstPageFrame->ComputePageSize();
const bool pageIsRotated =
sheetsFirstPageFrame->GetPageOrientationRotation(mPD) != 0.0;

if (pageIsRotated && pageSize.width == pageSize.height) {
// Straighforward rotation without needing sheet orientation optimization.
std::swap(sheetSize.width, sheetSize.height);
return sheetSize;
}

// Try to orient the sheet optimally based on the physical orientation of the
// first/sole page on the sheet. (In the multiple pages-per-sheet case, the
// first page is the only one that exists at this point in the code, so it is
// the only one we can reason about. Any other pages may, or may not, have
// the same physical orientation.)

if (pageIsRotated) {
// Fix up for its physical orientation:
std::swap(pageSize.width, pageSize.height);
}

const bool pageIsPortrait = pageSize.width < pageSize.height;
const bool sheetIsPortrait = sheetSize.width < sheetSize.height;

// Switch the sheet orientation if the page orientation is different, or
// if we need to switch it because the number of pages-per-sheet demands
// orthogonal sheet layout, but not if both are true since then we'd
// actually need to double switch.
if ((sheetIsPortrait != pageIsPortrait) !=
mPD->mPrintSettings->HasOrthogonalSheetsAndPages()) {
std::swap(sheetSize.width, sheetSize.height);
}

return sheetSize;
}

void PrintedSheetFrame::ComputePagesPerSheetGridMetrics(
Expand Down
31 changes: 17 additions & 14 deletions layout/generic/PrintedSheetFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class PrintedSheetFrame final : public nsContainerFrame {

void SetSharedPageData(nsSharedPageData* aPD) { mPD = aPD; }

// XXX: this needs a better name, since it also updates style.
// Invokes MoveOverflowToChildList.
// This is intended for use by callers that need to be able to get our first/
// only nsPageFrame from our child list to examine its computed style just
Expand Down Expand Up @@ -58,21 +59,22 @@ class PrintedSheetFrame final : public nsContainerFrame {
nscoord GetGridCellWidth() const { return mGridCellWidth; }
nscoord GetGridCellHeight() const { return mGridCellHeight; }

nsSize ComputeSheetSize(const nsPresContext* aPresContext);

/**
* A helper that is called just prior to this frame being relfowed to
* pre-compute and cache the size that the sheet should be given. This is
* called before any child nsPageFrames are reflowed, and it is cached so
* that those nsPageFrames can obtain their sheet frame's size while they're
* reflowing (the normal reflow code doesn't give the sheet frame its size
* until after the nsPageFrames have been reflowed).
* If we get rid of nsPageFrame::ComputeSinglePPSPageSizeScale (bug 1835782),
* which is the only consumer of GetPrecomputedSheetSize, then we can get rid
* of GetPrecomputedSheetSize and the member variable and rename
* PrecomputeSheetSize to ComputeSheetSize, which will then only be called
* once during reflow.
* When we're printing one page-per-sheet and `page-orientation` on our
* single nsPageFrame child should cause the page to rotate, then we want to
* essentially rotate the sheet. We implement that by switching the
* dimensions of this sheet (changing its orientation), sizing the
* nsPageFrame to the original dimensions, and then applying the rotation to
* the nsPageFrame child.
*
* This returns the dimensions that this frame would have without any
* dimension swap we may have done to implement `page-orientation`. If
* there is no rotation caused by `page-orientation`, then the value returned
* and mRect.Size() are identical.
*/
nsSize PrecomputeSheetSize(const nsPresContext* aPresContext);
nsSize GetPrecomputedSheetSize() const { return mPrecomputedSize; }
nsSize GetSizeForChildren() const { return mSizeForChildren; }

/**
* This method returns the dimensions of the physical page that the target
Expand Down Expand Up @@ -110,7 +112,8 @@ class PrintedSheetFrame final : public nsContainerFrame {
// a sensible amount of spacing between pages.)
void ComputePagesPerSheetGridMetrics(const nsSize& aSheetSize);

nsSize mPrecomputedSize;
// See GetSizeForChildren.
nsSize mSizeForChildren;

// Note: this will be set before reflow, and it's strongly owned by our
// nsPageSequenceFrame, which outlives us.
Expand Down
Loading

0 comments on commit a4b6e0c

Please sign in to comment.