Commit c8fa25f2 authored by Jonathan Watt's avatar Jonathan Watt
Browse files

Bug 1833244 p3. Implement sheet orientation switching/rotation for...

Bug 1833244 p3. Implement sheet orientation switching/rotation for `page-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 the pages in the PDF files that we output are correct.

- in the multiple pages-per-sheet case, we already rotate individual pages in
  their grid cell. This change 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 best place the page to make
  maximum use of the space.

Depends on D179423

Differential Revision: https://phabricator.services.mozilla.com/D179448
parent 597c1a27
Loading
Loading
Loading
Loading
+18 −2
Original line number Diff line number Diff line
@@ -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"
@@ -218,7 +219,15 @@ nsresult PrintTargetCG::BeginPage(const IntSize& aSizeInPoints) {
    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;
@@ -233,8 +242,15 @@ nsresult PrintTargetCG::BeginPage(const IntSize& aSizeInPoints) {
    }
  }

  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.
+12 −0
Original line number Diff line number Diff line
@@ -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"

@@ -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();
+1 −0
Original line number Diff line number Diff line
@@ -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;

+88 −8
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@

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

@@ -64,6 +65,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,
@@ -80,6 +94,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).
@@ -89,8 +121,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
@@ -225,13 +256,62 @@ void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,
  FinishAndStoreOverflow(&aReflowOutput);
}

nsSize PrintedSheetFrame::PrecomputeSheetSize(
    const nsPresContext* aPresContext) {
  mPrecomputedSize = aPresContext->GetPageSize();
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;
  }

  if (!StaticPrefs::layout_css_page_orientation_enabled()) {
    if (mPD->mPrintSettings->HasOrthogonalSheetsAndPages()) {
    std::swap(mPrecomputedSize.width, mPrecomputedSize.height);
      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);
  }
  return mPrecomputedSize;

  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(
+17 −14
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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.
Loading