Backed out changeset a6e4191fa2ce (bug 1919287) for causing 1923870. a=backout
[gecko.git] / remote / shared / PDF.sys.mjs
blob0ee08c993c20b7035734c3e17a27e83ac0e36469
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/. */
5 const lazy = {};
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 });
14 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
16 export const print = {
17   maxScaleValue: 2.0,
18   minScaleValue: 0.1,
21 export const MIN_PAGE_SIZE = 0.0352;
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"],
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;
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   );
58   if (!("width" in page)) {
59     page.width = print.defaults.page.width;
60   }
62   if (!("height" in page)) {
63     page.height = print.defaults.page.height;
64   }
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   }
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   }
78   if (!("top" in margin)) {
79     margin.top = print.defaults.margin.top;
80   }
82   if (!("bottom" in margin)) {
83     margin.bottom = print.defaults.margin.bottom;
84   }
86   if (!("right" in margin)) {
87     margin.right = print.defaults.margin.right;
88   }
90   if (!("left" in margin)) {
91     margin.left = print.defaults.margin.left;
92   }
94   return {
95     background,
96     margin,
97     orientation,
98     page,
99     pageRanges,
100     scale,
101     shrinkToFit,
102   };
105 print.getPrintSettings = function (settings) {
106   const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
107     Ci.nsIPrintSettingsService
108   );
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;
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;
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);
129   printSettings.printBGColors = settings.background;
130   printSettings.printBGImages = settings.background;
131   printSettings.scaling = settings.scale;
132   printSettings.shrinkToFit = settings.shrinkToFit;
134   printSettings.headerStrCenter = "";
135   printSettings.headerStrLeft = "";
136   printSettings.headerStrRight = "";
137   printSettings.footerStrCenter = "";
138   printSettings.footerStrLeft = "";
139   printSettings.footerStrRight = "";
141   // Override any os-specific unwriteable margins
142   printSettings.unwriteableMarginTop = 0;
143   printSettings.unwriteableMarginLeft = 0;
144   printSettings.unwriteableMarginBottom = 0;
145   printSettings.unwriteableMarginRight = 0;
147   if (settings.orientation === "landscape") {
148     printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation;
149   }
151   if (settings.pageRanges?.length) {
152     printSettings.pageRanges = parseRanges(settings.pageRanges);
153   }
155   return printSettings;
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)
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.
166  * @returns {Array.<number>}
167  *     Even-length array containing page range limits
168  */
169 function parseRanges(ranges) {
170   const MAX_PAGES = 0x7fffffff;
172   if (ranges.length === 0) {
173     return [];
174   }
176   let allLimits = [];
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}`;
191       limits = range.split("-").map(x => x.trim());
192       lazy.assert.that(o => [1, 2].includes(o.length), msg)(limits);
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       }
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));
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);
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   }
239   let rv = parsedRanges.flat();
240   lazy.logger.debug(`Got page ranges [${rv.join(", ")}]`);
241   return rv;
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);
251   printSettings.outputDestination =
252     Ci.nsIPrintSettings.kOutputDestinationStream;
253   printSettings.outputStream = stream.getOutputStream(0);
255   await browsingContext.print(printSettings);
257   const inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
258     Ci.nsIBinaryInputStream
259   );
261   inputStream.setInputStream(stream.newInputStream(0));
263   const available = inputStream.available();
264   const bytes = inputStream.readBytes(available);
266   stream.close();
268   return bytes;