Bug 1874684 - Part 33: Defer allocation of options object for CalendarDateFromFields...
[gecko.git] / remote / shared / PDF.sys.mjs
blob10fc2b0bae2a6310cebc3d4e2eae5fe91470ad92
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   Log: "chrome://remote/content/shared/Log.sys.mjs",
10 });
12 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
14 export const print = {
15   maxScaleValue: 2.0,
16   minScaleValue: 0.1,
19 print.defaults = {
20   // The size of the page in centimeters.
21   page: {
22     width: 21.59,
23     height: 27.94,
24   },
25   margin: {
26     top: 1.0,
27     bottom: 1.0,
28     left: 1.0,
29     right: 1.0,
30   },
31   orientationValue: ["landscape", "portrait"],
34 print.addDefaultSettings = function (settings) {
35   const {
36     background = false,
37     margin = {},
38     orientation = "portrait",
39     page = {},
40     pageRanges = [],
41     scale = 1.0,
42     shrinkToFit = true,
43   } = settings;
45   lazy.assert.object(page, `Expected "page" to be a object, got ${page}`);
46   lazy.assert.object(margin, `Expected "margin" to be a object, got ${margin}`);
48   if (!("width" in page)) {
49     page.width = print.defaults.page.width;
50   }
52   if (!("height" in page)) {
53     page.height = print.defaults.page.height;
54   }
56   if (!("top" in margin)) {
57     margin.top = print.defaults.margin.top;
58   }
60   if (!("bottom" in margin)) {
61     margin.bottom = print.defaults.margin.bottom;
62   }
64   if (!("right" in margin)) {
65     margin.right = print.defaults.margin.right;
66   }
68   if (!("left" in margin)) {
69     margin.left = print.defaults.margin.left;
70   }
72   return {
73     background,
74     margin,
75     orientation,
76     page,
77     pageRanges,
78     scale,
79     shrinkToFit,
80   };
83 print.getPrintSettings = function (settings) {
84   const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
85     Ci.nsIPrintSettingsService
86   );
88   let cmToInches = cm => cm / 2.54;
89   const printSettings = psService.createNewPrintSettings();
90   printSettings.isInitializedFromPrinter = true;
91   printSettings.isInitializedFromPrefs = true;
92   printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
93   printSettings.printerName = "marionette";
94   printSettings.printSilent = true;
96   // Setting the paperSizeUnit to kPaperSizeMillimeters doesn't work on mac
97   printSettings.paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches;
98   printSettings.paperWidth = cmToInches(settings.page.width);
99   printSettings.paperHeight = cmToInches(settings.page.height);
100   printSettings.usePageRuleSizeAsPaperSize = true;
102   printSettings.marginBottom = cmToInches(settings.margin.bottom);
103   printSettings.marginLeft = cmToInches(settings.margin.left);
104   printSettings.marginRight = cmToInches(settings.margin.right);
105   printSettings.marginTop = cmToInches(settings.margin.top);
107   printSettings.printBGColors = settings.background;
108   printSettings.printBGImages = settings.background;
109   printSettings.scaling = settings.scale;
110   printSettings.shrinkToFit = settings.shrinkToFit;
112   printSettings.headerStrCenter = "";
113   printSettings.headerStrLeft = "";
114   printSettings.headerStrRight = "";
115   printSettings.footerStrCenter = "";
116   printSettings.footerStrLeft = "";
117   printSettings.footerStrRight = "";
119   // Override any os-specific unwriteable margins
120   printSettings.unwriteableMarginTop = 0;
121   printSettings.unwriteableMarginLeft = 0;
122   printSettings.unwriteableMarginBottom = 0;
123   printSettings.unwriteableMarginRight = 0;
125   if (settings.orientation === "landscape") {
126     printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation;
127   }
129   if (settings.pageRanges?.length) {
130     printSettings.pageRanges = parseRanges(settings.pageRanges);
131   }
133   return printSettings;
137  * Convert array of strings of the form ["1-3", "2-4", "7", "9-"] to an flat array of
138  * limits, like [1, 4, 7, 7, 9, 2**31 - 1] (meaning 1-4, 7, 9-end)
140  * @param {Array.<string|number>} ranges
141  *     Page ranges to print, e.g., ['1-5', '8', '11-13'].
142  *     Defaults to the empty string, which means print all pages.
144  * @returns {Array.<number>}
145  *     Even-length array containing page range limits
146  */
147 function parseRanges(ranges) {
148   const MAX_PAGES = 0x7fffffff;
150   if (ranges.length === 0) {
151     return [];
152   }
154   let allLimits = [];
156   for (let range of ranges) {
157     let limits;
158     if (typeof range !== "string") {
159       // We got a single integer so the limits are just that page
160       lazy.assert.positiveInteger(range);
161       limits = [range, range];
162     } else {
163       // We got a string presumably of the form <int> | <int>? "-" <int>?
164       const msg = `Expected a range of the form <int> or <int>-<int>, got ${range}`;
166       limits = range.split("-").map(x => x.trim());
167       lazy.assert.that(o => [1, 2].includes(o.length), msg)(limits);
169       // Single numbers map to a range with that page at the start and the end
170       if (limits.length == 1) {
171         limits.push(limits[0]);
172       }
174       // Need to check that both limits are strings conisting only of
175       // decimal digits (or empty strings)
176       const assertNumeric = lazy.assert.that(o => /^\d*$/.test(o), msg);
177       limits.every(x => assertNumeric(x));
179       // Convert from strings representing numbers to actual numbers
180       // If we don't have an upper bound, choose something very large;
181       // the print code will later truncate this to the number of pages
182       limits = limits.map((limitStr, i) => {
183         if (limitStr == "") {
184           return i == 0 ? 1 : MAX_PAGES;
185         }
186         return parseInt(limitStr);
187       });
188     }
189     lazy.assert.that(
190       x => x[0] <= x[1],
191       "Lower limit ${parts[0]} is higher than upper limit ${parts[1]}"
192     )(limits);
194     allLimits.push(limits);
195   }
196   // Order by lower limit
197   allLimits.sort((a, b) => a[0] - b[0]);
198   let parsedRanges = [allLimits.shift()];
199   for (let limits of allLimits) {
200     let prev = parsedRanges[parsedRanges.length - 1];
201     let prevMax = prev[1];
202     let [min, max] = limits;
203     if (min <= prevMax) {
204       // min is inside previous range, so extend the max if needed
205       if (max > prevMax) {
206         prev[1] = max;
207       }
208     } else {
209       // Otherwise we have a new range
210       parsedRanges.push(limits);
211     }
212   }
214   let rv = parsedRanges.flat();
215   lazy.logger.debug(`Got page ranges [${rv.join(", ")}]`);
216   return rv;
219 print.printToBinaryString = async function (browsingContext, printSettings) {
220   // Create a stream to write to.
221   const stream = Cc["@mozilla.org/storagestream;1"].createInstance(
222     Ci.nsIStorageStream
223   );
224   stream.init(4096, 0xffffffff);
226   printSettings.outputDestination =
227     Ci.nsIPrintSettings.kOutputDestinationStream;
228   printSettings.outputStream = stream.getOutputStream(0);
230   await browsingContext.print(printSettings);
232   const inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
233     Ci.nsIBinaryInputStream
234   );
236   inputStream.setInputStream(stream.newInputStream(0));
238   const available = inputStream.available();
239   const bytes = inputStream.readBytes(available);
241   stream.close();
243   return bytes;