PDF.sys.mjs (7861B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", 9 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 10 Log: "chrome://remote/content/shared/Log.sys.mjs", 11 pprint: "chrome://remote/content/shared/Format.sys.mjs", 12 }); 13 14 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); 15 16 export const print = { 17 maxScaleValue: 2.0, 18 minScaleValue: 0.1, 19 }; 20 21 export const MIN_PAGE_SIZE = 0.0352; 22 23 print.defaults = { 24 // The size of the page in centimeters. 25 page: { 26 width: 21.59, 27 height: 27.94, 28 }, 29 margin: { 30 top: 1.0, 31 bottom: 1.0, 32 left: 1.0, 33 right: 1.0, 34 }, 35 orientationValue: ["landscape", "portrait"], 36 }; 37 38 print.addDefaultSettings = function (settings) { 39 const { 40 background = false, 41 margin = {}, 42 orientation = "portrait", 43 page = {}, 44 pageRanges = [], 45 scale = 1.0, 46 shrinkToFit = true, 47 } = settings; 48 49 lazy.assert.object( 50 page, 51 lazy.pprint`Expected "page" to be an object, got ${page}` 52 ); 53 lazy.assert.object( 54 margin, 55 lazy.pprint`Expected "margin" to be an object, got ${margin}` 56 ); 57 58 if (!("width" in page)) { 59 page.width = print.defaults.page.width; 60 } 61 62 if (!("height" in page)) { 63 page.height = print.defaults.page.height; 64 } 65 66 if (page.width < MIN_PAGE_SIZE) { 67 throw new lazy.error.InvalidArgumentError( 68 `Expected "page.width" to be greater than or equal to ${MIN_PAGE_SIZE}cm, got ${page.width}cm.` 69 ); 70 } 71 72 if (page.height < MIN_PAGE_SIZE) { 73 throw new lazy.error.InvalidArgumentError( 74 `Expected "page.height" to be greater than or equal to ${MIN_PAGE_SIZE}cm, got ${page.height}cm.` 75 ); 76 } 77 78 if (!("top" in margin)) { 79 margin.top = print.defaults.margin.top; 80 } 81 82 if (!("bottom" in margin)) { 83 margin.bottom = print.defaults.margin.bottom; 84 } 85 86 if (!("right" in margin)) { 87 margin.right = print.defaults.margin.right; 88 } 89 90 if (!("left" in margin)) { 91 margin.left = print.defaults.margin.left; 92 } 93 94 return { 95 background, 96 margin, 97 orientation, 98 page, 99 pageRanges, 100 scale, 101 shrinkToFit, 102 }; 103 }; 104 105 print.getPrintSettings = function (settings) { 106 const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService( 107 Ci.nsIPrintSettingsService 108 ); 109 110 let cmToInches = cm => cm / 2.54; 111 const printSettings = psService.createNewPrintSettings(); 112 printSettings.isInitializedFromPrinter = true; 113 printSettings.isInitializedFromPrefs = true; 114 printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; 115 printSettings.printerName = "marionette"; 116 printSettings.printSilent = true; 117 118 // Setting the paperSizeUnit to kPaperSizeMillimeters doesn't work on mac 119 printSettings.paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches; 120 printSettings.paperWidth = cmToInches(settings.page.width); 121 printSettings.paperHeight = cmToInches(settings.page.height); 122 printSettings.usePageRuleSizeAsPaperSize = true; 123 124 printSettings.marginBottom = cmToInches(settings.margin.bottom); 125 printSettings.marginLeft = cmToInches(settings.margin.left); 126 printSettings.marginRight = cmToInches(settings.margin.right); 127 printSettings.marginTop = cmToInches(settings.margin.top); 128 129 printSettings.printBGColors = settings.background; 130 printSettings.printBGImages = settings.background; 131 printSettings.scaling = settings.scale; 132 printSettings.shrinkToFit = settings.shrinkToFit; 133 134 printSettings.headerStrCenter = ""; 135 printSettings.headerStrLeft = ""; 136 printSettings.headerStrRight = ""; 137 printSettings.footerStrCenter = ""; 138 printSettings.footerStrLeft = ""; 139 printSettings.footerStrRight = ""; 140 141 // Override any os-specific unwriteable margins 142 printSettings.unwriteableMarginTop = 0; 143 printSettings.unwriteableMarginLeft = 0; 144 printSettings.unwriteableMarginBottom = 0; 145 printSettings.unwriteableMarginRight = 0; 146 147 if (settings.orientation === "landscape") { 148 printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation; 149 } 150 151 if (settings.pageRanges?.length) { 152 printSettings.pageRanges = parseRanges(settings.pageRanges); 153 } 154 155 return printSettings; 156 }; 157 158 /** 159 * Convert array of strings of the form ["1-3", "2-4", "7", "9-"] to an flat array of 160 * limits, like [1, 4, 7, 7, 9, 2**31 - 1] (meaning 1-4, 7, 9-end) 161 * 162 * @param {Array.<string|number>} ranges 163 * Page ranges to print, e.g., ['1-5', '8', '11-13']. 164 * Defaults to the empty string, which means print all pages. 165 * 166 * @returns {Array.<number>} 167 * Even-length array containing page range limits 168 */ 169 function parseRanges(ranges) { 170 const MAX_PAGES = 0x7fffffff; 171 172 if (ranges.length === 0) { 173 return []; 174 } 175 176 let allLimits = []; 177 178 for (let range of ranges) { 179 let limits; 180 if (typeof range !== "string") { 181 // We got a single integer so the limits are just that page 182 lazy.assert.positiveInteger( 183 range, 184 lazy.pprint`Expected "range" to be a string or a positive integer, got ${range}` 185 ); 186 limits = [range, range]; 187 } else { 188 // We got a string presumably of the form <int> | <int>? "-" <int>? 189 const msg = lazy.pprint`Expected "range" to be of the form <int> or <int>-<int>, got ${range}`; 190 191 limits = range.split("-").map(x => x.trim()); 192 lazy.assert.that(o => [1, 2].includes(o.length), msg)(limits); 193 194 // Single numbers map to a range with that page at the start and the end 195 if (limits.length == 1) { 196 limits.push(limits[0]); 197 } 198 199 // Need to check that both limits are strings consisting only of 200 // decimal digits (or empty strings) 201 const assertNumeric = lazy.assert.that(o => /^\d*$/.test(o), msg); 202 limits.every(x => assertNumeric(x)); 203 204 // Convert from strings representing numbers to actual numbers 205 // If we don't have an upper bound, choose something very large; 206 // the print code will later truncate this to the number of pages 207 limits = limits.map((limitStr, i) => { 208 if (limitStr == "") { 209 return i == 0 ? 1 : MAX_PAGES; 210 } 211 return parseInt(limitStr); 212 }); 213 } 214 lazy.assert.that( 215 x => x[0] <= x[1], 216 lazy.pprint`Expected "range" lower limit to be less than the upper limit, got ${range}` 217 )(limits); 218 219 allLimits.push(limits); 220 } 221 // Order by lower limit 222 allLimits.sort((a, b) => a[0] - b[0]); 223 let parsedRanges = [allLimits.shift()]; 224 for (let limits of allLimits) { 225 let prev = parsedRanges[parsedRanges.length - 1]; 226 let prevMax = prev[1]; 227 let [min, max] = limits; 228 if (min <= prevMax) { 229 // min is inside previous range, so extend the max if needed 230 if (max > prevMax) { 231 prev[1] = max; 232 } 233 } else { 234 // Otherwise we have a new range 235 parsedRanges.push(limits); 236 } 237 } 238 239 let rv = parsedRanges.flat(); 240 lazy.logger.debug(`Got page ranges [${rv.join(", ")}]`); 241 return rv; 242 } 243 244 print.printToBinaryString = async function (browsingContext, printSettings) { 245 // Create a stream to write to. 246 const stream = Cc["@mozilla.org/storagestream;1"].createInstance( 247 Ci.nsIStorageStream 248 ); 249 stream.init(4096, 0xffffffff); 250 251 printSettings.outputDestination = 252 Ci.nsIPrintSettings.kOutputDestinationStream; 253 printSettings.outputStream = stream.getOutputStream(0); 254 255 await browsingContext.print(printSettings); 256 257 const inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( 258 Ci.nsIBinaryInputStream 259 ); 260 261 inputStream.setInputStream(stream.newInputStream(0)); 262 263 const available = inputStream.available(); 264 const bytes = inputStream.readBytes(available); 265 266 stream.close(); 267 268 return bytes; 269 };