Source code

Revision control

Copy as Markdown

Other Tools

/* 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 http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
Log: "chrome://remote/content/shared/Log.sys.mjs",
pprint: "chrome://remote/content/shared/Format.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
export const print = {
maxScaleValue: 2.0,
minScaleValue: 0.1,
};
export const MIN_PAGE_SIZE = 0.0352;
print.defaults = {
// The size of the page in centimeters.
page: {
width: 21.59,
height: 27.94,
},
margin: {
top: 1.0,
bottom: 1.0,
left: 1.0,
right: 1.0,
},
orientationValue: ["landscape", "portrait"],
};
print.addDefaultSettings = function (settings) {
const {
background = false,
margin = {},
orientation = "portrait",
page = {},
pageRanges = [],
scale = 1.0,
shrinkToFit = true,
} = settings;
lazy.assert.object(
page,
lazy.pprint`Expected "page" to be an object, got ${page}`
);
lazy.assert.object(
margin,
lazy.pprint`Expected "margin" to be an object, got ${margin}`
);
if (!("width" in page)) {
page.width = print.defaults.page.width;
}
if (!("height" in page)) {
page.height = print.defaults.page.height;
}
if (page.width < MIN_PAGE_SIZE) {
throw new lazy.error.InvalidArgumentError(
`Expected "page.width" to be greater than or equal to ${MIN_PAGE_SIZE}cm, got ${page.width}cm.`
);
}
if (page.height < MIN_PAGE_SIZE) {
throw new lazy.error.InvalidArgumentError(
`Expected "page.height" to be greater than or equal to ${MIN_PAGE_SIZE}cm, got ${page.height}cm.`
);
}
if (!("top" in margin)) {
margin.top = print.defaults.margin.top;
}
if (!("bottom" in margin)) {
margin.bottom = print.defaults.margin.bottom;
}
if (!("right" in margin)) {
margin.right = print.defaults.margin.right;
}
if (!("left" in margin)) {
margin.left = print.defaults.margin.left;
}
return {
background,
margin,
orientation,
page,
pageRanges,
scale,
shrinkToFit,
};
};
print.getPrintSettings = function (settings) {
const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
Ci.nsIPrintSettingsService
);
let cmToInches = cm => cm / 2.54;
const printSettings = psService.createNewPrintSettings();
printSettings.isInitializedFromPrinter = true;
printSettings.isInitializedFromPrefs = true;
printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
printSettings.printerName = "marionette";
printSettings.printSilent = true;
// Setting the paperSizeUnit to kPaperSizeMillimeters doesn't work on mac
printSettings.paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches;
printSettings.paperWidth = cmToInches(settings.page.width);
printSettings.paperHeight = cmToInches(settings.page.height);
printSettings.usePageRuleSizeAsPaperSize = true;
printSettings.marginBottom = cmToInches(settings.margin.bottom);
printSettings.marginLeft = cmToInches(settings.margin.left);
printSettings.marginRight = cmToInches(settings.margin.right);
printSettings.marginTop = cmToInches(settings.margin.top);
printSettings.printBGColors = settings.background;
printSettings.printBGImages = settings.background;
printSettings.scaling = settings.scale;
printSettings.shrinkToFit = settings.shrinkToFit;
printSettings.headerStrCenter = "";
printSettings.headerStrLeft = "";
printSettings.headerStrRight = "";
printSettings.footerStrCenter = "";
printSettings.footerStrLeft = "";
printSettings.footerStrRight = "";
// Override any os-specific unwriteable margins
printSettings.unwriteableMarginTop = 0;
printSettings.unwriteableMarginLeft = 0;
printSettings.unwriteableMarginBottom = 0;
printSettings.unwriteableMarginRight = 0;
if (settings.orientation === "landscape") {
printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation;
}
if (settings.pageRanges?.length) {
printSettings.pageRanges = parseRanges(settings.pageRanges);
}
return printSettings;
};
/**
* Convert array of strings of the form ["1-3", "2-4", "7", "9-"] to an flat array of
* limits, like [1, 4, 7, 7, 9, 2**31 - 1] (meaning 1-4, 7, 9-end)
*
* @param {Array.<string|number>} ranges
* Page ranges to print, e.g., ['1-5', '8', '11-13'].
* Defaults to the empty string, which means print all pages.
*
* @returns {Array.<number>}
* Even-length array containing page range limits
*/
function parseRanges(ranges) {
const MAX_PAGES = 0x7fffffff;
if (ranges.length === 0) {
return [];
}
let allLimits = [];
for (let range of ranges) {
let limits;
if (typeof range !== "string") {
// We got a single integer so the limits are just that page
lazy.assert.positiveInteger(
range,
lazy.pprint`Expected "range" to be a string or a positive integer, got ${range}`
);
limits = [range, range];
} else {
// We got a string presumably of the form <int> | <int>? "-" <int>?
const msg = lazy.pprint`Expected "range" to be of the form <int> or <int>-<int>, got ${range}`;
limits = range.split("-").map(x => x.trim());
lazy.assert.that(o => [1, 2].includes(o.length), msg)(limits);
// Single numbers map to a range with that page at the start and the end
if (limits.length == 1) {
limits.push(limits[0]);
}
// Need to check that both limits are strings consisting only of
// decimal digits (or empty strings)
const assertNumeric = lazy.assert.that(o => /^\d*$/.test(o), msg);
limits.every(x => assertNumeric(x));
// Convert from strings representing numbers to actual numbers
// If we don't have an upper bound, choose something very large;
// the print code will later truncate this to the number of pages
limits = limits.map((limitStr, i) => {
if (limitStr == "") {
return i == 0 ? 1 : MAX_PAGES;
}
return parseInt(limitStr);
});
}
lazy.assert.that(
x => x[0] <= x[1],
lazy.pprint`Expected "range" lower limit to be less than the upper limit, got ${range}`
)(limits);
allLimits.push(limits);
}
// Order by lower limit
allLimits.sort((a, b) => a[0] - b[0]);
let parsedRanges = [allLimits.shift()];
for (let limits of allLimits) {
let prev = parsedRanges[parsedRanges.length - 1];
let prevMax = prev[1];
let [min, max] = limits;
if (min <= prevMax) {
// min is inside previous range, so extend the max if needed
if (max > prevMax) {
prev[1] = max;
}
} else {
// Otherwise we have a new range
parsedRanges.push(limits);
}
}
let rv = parsedRanges.flat();
lazy.logger.debug(`Got page ranges [${rv.join(", ")}]`);
return rv;
}
print.printToBinaryString = async function (browsingContext, printSettings) {
// Create a stream to write to.
const stream = Cc["@mozilla.org/storagestream;1"].createInstance(
Ci.nsIStorageStream
);
stream.init(4096, 0xffffffff);
printSettings.outputDestination =
Ci.nsIPrintSettings.kOutputDestinationStream;
printSettings.outputStream = stream.getOutputStream(0);
await browsingContext.print(printSettings);
const inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
Ci.nsIBinaryInputStream
);
inputStream.setInputStream(stream.newInputStream(0));
const available = inputStream.available();
const bytes = inputStream.readBytes(available);
stream.close();
return bytes;
};