Bug 1793220 - Use at-page size rule as paper size when printing to PDF r=dholbert,AlaskanEmily

Adds a usePageRuleSizeAsPaperSize setting to allow overriding the default paper size when printing to a PDF file.

Print preview now checks for at-page size rules and when enabled will use that size for the previewed sheet of paper.

The preview will also return the page width and height (in inches) during its callback for the frontend to use to override the default paper settings.

Differential Revision: https://phabricator.services.mozilla.com/D160303
This commit is contained in:
Fred Chasen 2023-02-27 20:22:18 +00:00
parent 2663a62047
commit 42c8df00ac
20 changed files with 479 additions and 5 deletions

View File

@ -3353,6 +3353,13 @@ already_AddRefed<Promise> nsFrameLoader::PrintPreview(
} else {
MOZ_ASSERT(info.mOrientation == Orientation::Unspecified);
}
if (aInfo.pageWidth()) {
info.mPageWidth = aInfo.pageWidth().value();
}
if (aInfo.pageHeight()) {
info.mPageHeight = aInfo.pageHeight().value();
}
promise->MaybeResolve(info);
} else {
promise->MaybeRejectWithUnknownError("Print preview failed");

View File

@ -228,6 +228,16 @@ dictionary PrintPreviewSuccessInfo {
* Specified orientation of the document, or "unspecified".
*/
PrintPreviewOrientation orientation = "unspecified";
/**
* Specified page width of the document in inches, or null if no @page size was specified.
*/
float? pageWidth = null;
/**
* Specified page height of the document in inches, or null if no @page size was specified.
*/
float? pageHeight = null;
};
FrameLoader includes WebBrowserPersistable;

View File

@ -2320,7 +2320,7 @@ mozilla::ipc::IPCResult BrowserChild::RecvPrintPreview(
auto sendCallbackError = MakeScopeExit([&] {
if (aCallback) {
// signal error
aCallback(PrintPreviewResultInfo(0, 0, false, false, false, {}));
aCallback(PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
}
});

View File

@ -171,6 +171,10 @@ struct PrintPreviewResultInfo
// If present, indicates if the page should be printed landscape when true or
// portrait when false;
bool? printLandscape;
// The at-page specified page width or null when no width is provided.
float? pageWidth;
// The at-page specified page height or null when no height is provided.
float? pageHeight;
};
/**

View File

@ -468,7 +468,7 @@ nsresult nsPrintJob::PrintPreview(Document& aDoc,
if (mPrintPreviewCallback) {
// signal error
mPrintPreviewCallback(
PrintPreviewResultInfo(0, 0, false, false, false, {}));
PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
mPrintPreviewCallback = nullptr;
}
}
@ -615,7 +615,7 @@ void nsPrintJob::FirePrintingErrorEvent(nsresult aPrintError) {
if (mPrintPreviewCallback) {
// signal error
mPrintPreviewCallback(
PrintPreviewResultInfo(0, 0, false, false, false, {}));
PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
mPrintPreviewCallback = nullptr;
}
@ -1379,6 +1379,16 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
}
}
if (mPrintSettings->GetUsePageRuleSizeAsPaperSize()) {
mMaybeCSSPageSize =
aPO->mDocument->GetPresShell()->StyleSet()->GetPageSizeForPageName(
firstPageName);
if (mMaybeCSSPageSize) {
pageSize = *mMaybeCSSPageSize;
aPO->mPresContext->SetPageSize(pageSize);
}
}
// If the document has a specified CSS page-size, we rotate the page to
// reflect this. Changing the orientation is reflected by the result of
// FinishPrintPreview, so that the frontend can reflect this.
@ -2009,10 +2019,20 @@ nsresult nsPrintJob::FinishPrintPreview() {
if (mPrintPreviewCallback) {
const bool hasSelection = !mDisallowSelectionPrint && mSelectionRoot;
Maybe<float> pageWidth;
Maybe<float> pageHeight;
if (mMaybeCSSPageSize) {
nsSize cssPageSize = *mMaybeCSSPageSize;
pageWidth = Some(float(cssPageSize.width) / float(AppUnitsPerCSSInch()));
pageHeight =
Some(float(cssPageSize.height) / float(AppUnitsPerCSSInch()));
}
mPrintPreviewCallback(PrintPreviewResultInfo(
GetPrintPreviewNumSheets(), GetRawNumPages(), GetIsEmpty(),
hasSelection, hasSelection && mPrintObject->HasSelection(),
mMaybeCSSPageLandscape));
mMaybeCSSPageLandscape, pageWidth, pageHeight));
mPrintPreviewCallback = nullptr;
}

View File

@ -289,6 +289,10 @@ class nsPrintJob final : public nsIWebProgressListener,
// if there is no page-size-orientation.
mozilla::Maybe<bool> mMaybeCSSPageLandscape;
// Indicates if the page has a specific size from @page { size }.
// Stores the page size if one was found.
mozilla::Maybe<nsSize> mMaybeCSSPageSize;
// If true, indicates that we have started Printing but have not gone to the
// timer to start printing the pages. It gets turned off right before we go
// to the timer.

View File

@ -689,6 +689,16 @@ Maybe<StylePageSizeOrientation> ServoStyleSet::GetDefaultPageSizeOrientation(
return Nothing();
}
Maybe<nsSize> ServoStyleSet::GetPageSizeForPageName(const nsAtom* aPageName) {
const RefPtr<ComputedStyle> style = ResolvePageContentStyle(aPageName);
const StylePageSize& pageSize = style->StylePage()->mSize;
if (pageSize.IsSize()) {
return Some(nsSize{pageSize.AsSize().width.ToAppUnits(),
pageSize.AsSize().height.ToAppUnits()});
}
return Nothing();
}
void ServoStyleSet::AppendAllNonDocumentAuthorSheets(
nsTArray<StyleSheet*>& aArray) const {
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {

View File

@ -26,6 +26,7 @@
#include "nsIMemoryReporter.h"
#include "nsTArray.h"
#include "nsIMemoryReporter.h"
#include "nsSize.h"
namespace mozilla {
enum class MediaFeatureChangeReason : uint16_t;
@ -274,6 +275,11 @@ class ServoStyleSet {
Maybe<StylePageSizeOrientation> GetDefaultPageSizeOrientation(
const nsAtom* aFirstPageName);
// Gets the page size specified in CSS pages of a given page name.
// Return the page size width and height as app units.
// If the value is auto, then returns nothing.
Maybe<nsSize> GetPageSizeForPageName(const nsAtom* aPageName);
void AppendAllNonDocumentAuthorSheets(nsTArray<StyleSheet*>& aArray) const;
// Manage the set of style sheets in the style set

View File

@ -12600,6 +12600,12 @@
value: false
mirror: always
# Whether we use the CSS @page size as the paper size in PDF output.
- name: print.save_as_pdf.use_page_rule_size_as_paper_size.enabled
type: RelaxedAtomicBool
value: false
mirror: always
# The default DPI for printing.
#
# For PDF-based output, DPI should ideally be irrelevant, but in fact it is not

View File

@ -843,7 +843,7 @@ var PrintEventHandler = {
.add(elapsed);
}
let totalPageCount, sheetCount, isEmpty, orientation;
let totalPageCount, sheetCount, isEmpty, orientation, pageWidth, pageHeight;
try {
// This resolves with a PrintPreviewSuccessInfo dictionary.
let { sourceVersion } = this.viewSettings;
@ -854,6 +854,8 @@ var PrintEventHandler = {
sheetCount,
isEmpty,
orientation,
pageWidth,
pageHeight,
} = await this.printPreviewEl.printPreview(settings, {
sourceVersion,
sourceURI,
@ -876,6 +878,35 @@ var PrintEventHandler = {
document.dispatchEvent(new CustomEvent("hide-orientation"));
}
// If the page size is set, check whether we should use it as our paper size.
let isUsingPageRuleSizeAsPaperSize =
settings.usePageRuleSizeAsPaperSize &&
pageWidth !== null &&
pageHeight !== null;
if (isUsingPageRuleSizeAsPaperSize) {
// We canonically represent paper sizes using the width/height of a portrait-oriented sheet,
// with landscape-orientation applied as a supplemental rotation.
// If the page-size is landscape oriented, we flip the pageWidth / pageHeight here
// in order to pass a canonical representation into the paper-size settings.
if (orientation == "landscape") {
[pageHeight, pageWidth] = [pageWidth, pageHeight];
}
let matchedPaper = PrintSettingsViewProxy.getBestPaperMatch(
pageWidth,
pageHeight,
settings.kPaperSizeInches
);
if (matchedPaper) {
settings.paperId = matchedPaper.id;
}
settings.paperWidth = pageWidth;
settings.paperHeight = pageHeight;
settings.paperSizeUnit = settings.kPaperSizeInches;
document.dispatchEvent(new CustomEvent("hide-paper-size"));
}
this.previewIsEmpty = isEmpty;
// If the preview is empty, we know our range is greater than the number of pages.
// We have to send a pageRange update to display a non-empty page.
@ -1213,6 +1244,10 @@ var PrintSettingsViewProxy = {
Ci.nsIPrintSettings.kOutputFormatPDF;
printerInfo.defaultSettings.outputDestination =
Ci.nsIPrintSettings.kOutputDestinationFile;
printerInfo.defaultSettings.usePageRuleSizeAsPaperSize = Services.prefs.getBoolPref(
"print.save_as_pdf.use_page_rule_size_as_paper_size.enabled",
false
);
printerInfo.paperList = this.fallbackPaperList;
}
printerInfo.settings = printerInfo.defaultSettings.clone();
@ -1933,6 +1968,8 @@ class PaperSizePicker extends PrintSettingSelect {
initialize() {
super.initialize();
this._printerName = null;
this._section = this.closest(".section-block");
document.addEventListener("hide-paper-size", this);
}
update(settings) {
@ -1941,6 +1978,19 @@ class PaperSizePicker extends PrintSettingSelect {
this.setOptions(settings.paperSizes);
}
this.value = settings.paperId;
// Unhide the paper-size picker, if we've stopped using the page size as paper-size.
if (this._section.hidden && !settings.usePageRuleSizeAsPaperSize) {
this._section.hidden = false;
}
}
handleEvent(e) {
super.handleEvent(e);
const { type } = e;
if (type == "hide-paper-size") {
this._section.hidden = true;
}
}
}
customElements.define("paper-size-select", PaperSizePicker, {

View File

@ -33,6 +33,11 @@ skip-if = (verify && (os == 'mac')) # bug 1675609
[browser_print_selection.js]
[browser_print_stream.js]
[browser_print_page_range.js]
[browser_print_page_size.js]
support-files =
page_size.html
page_size_a5.html
page_size_a5_landscape.html
[browser_print_pdf_on_frame_load.js]
support-files =
file_print_pdf_on_frame_load.html

View File

@ -0,0 +1,234 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const pdfPrinterName = "Mozilla Save to PDF";
const someOtherPrinter = "Other Printer";
async function changeDestination(helper, dir) {
let picker = helper.get("printer-picker");
let changed = BrowserTestUtils.waitForEvent(picker, "change");
let pickerOpened = BrowserTestUtils.waitForSelectPopupShown(window);
picker.focus();
EventUtils.sendKey("space", helper.win);
await pickerOpened;
EventUtils.sendKey(dir, window);
EventUtils.sendKey("return", window);
await changed;
}
add_task(async function testShowAndHidePaperSizeSectionWithPageSize() {
await PrintHelper.withTestPage(async helper => {
await helper.addMockPrinter(someOtherPrinter);
await SpecialPowers.pushPrefEnv({
set: [
["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
],
});
await helper.startPrint();
await helper.setupMockPrint();
helper.assertSettingsMatch({
printerName: pdfPrinterName,
usePageRuleSizeAsPaperSize: true,
});
await helper.openMoreSettings();
let paperSize = helper.get("paper-size");
ok(BrowserTestUtils.is_hidden(paperSize), "Paper size section is hidden");
await helper.waitForSettingsEvent(async () => {
await changeDestination(helper, "down");
});
await helper.awaitAnimationFrame();
helper.assertSettingsMatch({
printerName: someOtherPrinter,
usePageRuleSizeAsPaperSize: false,
});
ok(BrowserTestUtils.is_visible(paperSize), "Paper size section is shown");
await helper.closeDialog();
}, "page_size.html");
});
add_task(async function testShowPaperSizeSectionWithoutPageSize() {
await PrintHelper.withTestPage(async helper => {
await helper.addMockPrinter(someOtherPrinter);
await SpecialPowers.pushPrefEnv({
set: [
["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
],
});
await helper.startPrint();
await helper.setupMockPrint();
helper.assertSettingsMatch({
printerName: pdfPrinterName,
usePageRuleSizeAsPaperSize: true,
});
await helper.openMoreSettings();
let paperSize = helper.get("paper-size");
ok(BrowserTestUtils.is_visible(paperSize), "Paper size section is shown");
await helper.waitForSettingsEvent(async () => {
await changeDestination(helper, "down");
});
await helper.awaitAnimationFrame();
helper.assertSettingsMatch({
printerName: someOtherPrinter,
usePageRuleSizeAsPaperSize: false,
});
ok(
BrowserTestUtils.is_visible(paperSize),
"Paper size section is still shown"
);
await helper.closeDialog();
});
});
add_task(async function testCustomPageSizePassedToPrinter() {
await PrintHelper.withTestPage(async helper => {
await SpecialPowers.pushPrefEnv({
set: [
["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
],
});
await helper.startPrint();
await helper.setupMockPrint();
is(helper.settings.paperWidth.toFixed(1), "4.0", "Check paperWidth");
is(helper.settings.paperHeight.toFixed(1), "12.0", "Check paperHeight");
is(
helper.settings.orientation,
Ci.nsIPrintSettings.kPortraitOrientation,
"Check orientation"
);
is(
helper.settings.paperSizeUnit,
helper.settings.kPaperSizeInches,
"Check paperSizeUnit"
);
await helper.closeDialog();
}, "page_size.html");
});
add_task(async function testNamedPageSizePassedToPrinter() {
await PrintHelper.withTestPage(async helper => {
await SpecialPowers.pushPrefEnv({
set: [
["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
],
});
await helper.startPrint();
await helper.setupMockPrint();
helper.assertSettingsMatch({
printerName: pdfPrinterName,
usePageRuleSizeAsPaperSize: true,
});
is(helper.settings.paperId, "iso_a5", "Check paper id is A5");
is(
helper.settings.paperWidth.toFixed(1),
"5.8",
"Check paperWidth is ~148mm (in inches)"
);
is(
helper.settings.paperHeight.toFixed(1),
"8.3",
"Check paperHeight is ~210mm (in inches)"
);
is(
helper.settings.paperSizeUnit,
helper.settings.kPaperSizeInches,
"Check paperSizeUnit"
);
is(
helper.settings.orientation,
Ci.nsIPrintSettings.kPortraitOrientation,
"Check orientation"
);
await helper.closeDialog();
}, "page_size_a5.html");
});
add_task(async function testLandscapePageSizePassedToPrinter() {
await PrintHelper.withTestPage(async helper => {
await SpecialPowers.pushPrefEnv({
set: [
["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
],
});
await helper.startPrint();
await helper.setupMockPrint();
helper.assertSettingsMatch({
printerName: pdfPrinterName,
usePageRuleSizeAsPaperSize: true,
});
is(helper.settings.paperId, "iso_a5", "Check paper id is A5");
is(
helper.settings.orientation,
Ci.nsIPrintSettings.kLandscapeOrientation,
"Check orientation is landscape"
);
is(
helper.settings.paperWidth.toFixed(1),
"5.8",
"Check paperWidth is ~148mm (in inches)"
);
is(
helper.settings.paperHeight.toFixed(1),
"8.3",
"Check paperHeight is ~210mm (in inches)"
);
is(
helper.settings.paperSizeUnit,
helper.settings.kPaperSizeInches,
"Check paperSizeUnit"
);
await helper.closeDialog();
}, "page_size_a5_landscape.html");
});
add_task(async function testDefaultSizePassedToPrinter() {
await PrintHelper.withTestPage(async helper => {
await SpecialPowers.pushPrefEnv({
set: [
["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
],
});
await helper.startPrint();
await helper.setupMockPrint();
helper.assertSettingsMatch({
printerName: pdfPrinterName,
usePageRuleSizeAsPaperSize: true,
});
is(helper.settings.paperWidth.toFixed(1), "8.5", "Check paperWidth");
is(helper.settings.paperHeight.toFixed(1), "11.0", "Check paperHeight");
is(
helper.settings.paperSizeUnit,
helper.settings.kPaperSizeInches,
"Check paperSizeUnit"
);
await helper.closeDialog();
});
});

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>At-Page Size</title>
<style>
body {
margin: 0;
}
.rect {
/* An outlined rect that is 1inch smaller in each dimension,
to help visualize if the output page is the right size or not: */
width: calc(4in - 1in - 4px);
height: calc(12in - 1in - 4px);
border: 2px solid black;
}
@page {
size: 4in 12in;
margin: 0.5in;
}
</style>
</head>
<body>
<div class="rect">
<h3>Expectation:</h3>
If you print-preview using the "Save to PDF" page target, you should see an
4in-by-12in-size sheet of paper by default, just 1 inch wider (0.5in margins) than the
black-outlined box.
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>At-Page Size: A5</title>
<style>
body {
margin: 0;
}
.rect {
/* An outlined rect that is 1inch smaller in each dimension,
to help visualize if the output page is the right size or not: */
width: calc(148mm - 1in - 4px);
height: calc(210mm - 1in - 4px);
border: 2px solid black;
}
@page {
size: A5; /* 5-7/8 x 8-1/4 in or 148 x 210 mm */
margin: 0.5in;
}
</style>
</head>
<body>
<div class="rect">
<h3>Expectation:</h3>
If you print-preview using the "Save to PDF" page target, you should see an
A5-size sheet of paper by default, just 1 inch wider (0.5in margins) than the black-outlined
box.
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>At-Page Size: A5 Landscape</title>
<style>
body {
margin: 0;
}
.rect {
/* An outlined rect that is 1inch smaller in each dimension,
to help visualize if the output page is the right size or not: */
width: calc(210mm - 1in - 4px);
height: calc(148mm - 1in - 4px);
border: 2px solid black;
}
@page {
size: A5 landscape; /* 8-1/4 x 5-7/8 in or 210 x 148 mm */
margin: 0.5in;
}
</style>
</head>
<body>
<div class="rect">
<h3>Expectation:</h3>
If you print-preview using the "Save to PDF" page target, you should see an
A5-size sheet of paper in landscape, just 1 inch wider (0.5in margins) than the black-outlined
box.
</div>
</body>
</html>

View File

@ -31,6 +31,7 @@ struct PrintData {
bool printBGColors;
bool printBGImages;
bool honorPageRuleMargins;
bool usePageRuleSizeAsPaperSize;
bool ignoreUnwriteableMargins;
bool showMarginGuides;
bool printSelectionOnly;

View File

@ -261,6 +261,11 @@ interface nsIPrintSettings : nsISupports
*/
[infallible] attribute boolean honorPageRuleMargins;
/**
* Whether @page rule size should be used for the output paper size.
*/
[infallible] attribute boolean usePageRuleSizeAsPaperSize;
/**
* Whether unwritable margins should be ignored. This should be set when
* when the user explicitly requests "Margins: None", e.g. for documents

View File

@ -518,6 +518,16 @@ NS_IMETHODIMP nsPrintSettings::SetHonorPageRuleMargins(bool aHonor) {
return NS_OK;
}
NS_IMETHODIMP nsPrintSettings::GetUsePageRuleSizeAsPaperSize(bool* aResult) {
*aResult = mUsePageRuleSizeAsPaperSize;
return NS_OK;
}
NS_IMETHODIMP nsPrintSettings::SetUsePageRuleSizeAsPaperSize(bool aHonor) {
mUsePageRuleSizeAsPaperSize = aHonor;
return NS_OK;
}
NS_IMETHODIMP nsPrintSettings::GetIgnoreUnwriteableMargins(bool* aResult) {
*aResult = mIgnoreUnwriteableMargins;
return NS_OK;
@ -765,6 +775,10 @@ nsresult nsPrintSettings::EquivalentTo(nsIPrintSettings* aPrintSettings,
if (GetHonorPageRuleMargins() != aPrintSettings->GetHonorPageRuleMargins()) {
return NS_OK;
}
if (GetUsePageRuleSizeAsPaperSize() !=
aPrintSettings->GetUsePageRuleSizeAsPaperSize()) {
return NS_OK;
}
if (GetIgnoreUnwriteableMargins() !=
aPrintSettings->GetIgnoreUnwriteableMargins()) {
return NS_OK;
@ -875,6 +889,7 @@ nsPrintSettings& nsPrintSettings::operator=(const nsPrintSettings& rhs) {
mShrinkToFit = rhs.mShrinkToFit;
mShowMarginGuides = rhs.mShowMarginGuides;
mHonorPageRuleMargins = rhs.mHonorPageRuleMargins;
mUsePageRuleSizeAsPaperSize = rhs.mUsePageRuleSizeAsPaperSize;
mIgnoreUnwriteableMargins = rhs.mIgnoreUnwriteableMargins;
mPrintSelectionOnly = rhs.mPrintSelectionOnly;
mPaperId = rhs.mPaperId;

View File

@ -99,6 +99,7 @@ class nsPrintSettings : public nsIPrintSettings {
bool mShrinkToFit = true;
bool mShowMarginGuides = false;
bool mHonorPageRuleMargins = true;
bool mUsePageRuleSizeAsPaperSize = false;
bool mIgnoreUnwriteableMargins = false;
bool mPrintSelectionOnly = false;

View File

@ -122,6 +122,8 @@ nsPrintSettingsService::SerializeToPrintData(nsIPrintSettings* aSettings,
data->ignoreUnwriteableMargins() = aSettings->GetIgnoreUnwriteableMargins();
data->honorPageRuleMargins() = aSettings->GetHonorPageRuleMargins();
data->usePageRuleSizeAsPaperSize() =
aSettings->GetUsePageRuleSizeAsPaperSize();
data->showMarginGuides() = aSettings->GetShowMarginGuides();
data->printSelectionOnly() = aSettings->GetPrintSelectionOnly();
@ -196,6 +198,7 @@ nsPrintSettingsService::DeserializeToPrintSettings(const PrintData& data,
settings->SetPrintBGColors(data.printBGColors());
settings->SetPrintBGImages(data.printBGImages());
settings->SetHonorPageRuleMargins(data.honorPageRuleMargins());
settings->SetUsePageRuleSizeAsPaperSize(data.usePageRuleSizeAsPaperSize());
settings->SetIgnoreUnwriteableMargins(data.ignoreUnwriteableMargins());
settings->SetShowMarginGuides(data.showMarginGuides());
settings->SetPrintSelectionOnly(data.printSelectionOnly());