diff options
Diffstat (limited to 'remote/shared/PDF.sys.mjs')
-rw-r--r-- | remote/shared/PDF.sys.mjs | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/remote/shared/PDF.sys.mjs b/remote/shared/PDF.sys.mjs new file mode 100644 index 0000000000..10fc2b0bae --- /dev/null +++ b/remote/shared/PDF.sys.mjs @@ -0,0 +1,244 @@ +/* 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", + Log: "chrome://remote/content/shared/Log.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); + +export const print = { + maxScaleValue: 2.0, + minScaleValue: 0.1, +}; + +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, `Expected "page" to be a object, got ${page}`); + lazy.assert.object(margin, `Expected "margin" to be a object, got ${margin}`); + + if (!("width" in page)) { + page.width = print.defaults.page.width; + } + + if (!("height" in page)) { + page.height = print.defaults.page.height; + } + + 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); + limits = [range, range]; + } else { + // We got a string presumably of the form <int> | <int>? "-" <int>? + const msg = `Expected a range 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 conisting 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], + "Lower limit ${parts[0]} is higher than upper limit ${parts[1]}" + )(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; +}; |