tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 };