feat: add more options to printToPDF (#21906)
This commit is contained in:
parent
1b4eb0b679
commit
548b290ea7
4 changed files with 171 additions and 53 deletions
|
@ -1273,9 +1273,11 @@ Returns [`PrinterInfo[]`](structures/printer-info.md)
|
||||||
* `pagesPerSheet` Number (optional) - The number of pages to print per page sheet.
|
* `pagesPerSheet` Number (optional) - The number of pages to print per page sheet.
|
||||||
* `collate` Boolean (optional) - Whether the web page should be collated.
|
* `collate` Boolean (optional) - Whether the web page should be collated.
|
||||||
* `copies` Number (optional) - The number of copies of the web page to print.
|
* `copies` Number (optional) - The number of copies of the web page to print.
|
||||||
* `pageRanges` Record<string, number> (optional) - The page range to print. Should have two keys: `from` and `to`.
|
* `pageRanges` Record<string, number> (optional) - The page range to print.
|
||||||
|
* `from` Number - the start page.
|
||||||
|
* `to` Number - the end page.
|
||||||
* `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
|
* `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
|
||||||
* `dpi` Object (optional)
|
* `dpi` Record<string, number> (optional)
|
||||||
* `horizontal` Number (optional) - The horizontal dpi.
|
* `horizontal` Number (optional) - The horizontal dpi.
|
||||||
* `vertical` Number (optional) - The vertical dpi.
|
* `vertical` Number (optional) - The vertical dpi.
|
||||||
* `header` String (optional) - String to be printed as page header.
|
* `header` String (optional) - String to be printed as page header.
|
||||||
|
@ -1301,14 +1303,21 @@ win.webContents.print(options, (success, errorType) => {
|
||||||
#### `contents.printToPDF(options)`
|
#### `contents.printToPDF(options)`
|
||||||
|
|
||||||
* `options` Object
|
* `options` Object
|
||||||
|
* `headerFooter` Record<string, string> (optional) - the header and footer for the PDF.
|
||||||
|
* `title` String - The title for the PDF header.
|
||||||
|
* `url` String - the url for the PDF footer.
|
||||||
|
* `landscape` Boolean (optional) - `true` for landscape, `false` for portrait.
|
||||||
* `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for
|
* `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for
|
||||||
default margin, 1 for no margin, and 2 for minimum margin.
|
default margin, 1 for no margin, and 2 for minimum margin.
|
||||||
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
|
|
||||||
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`
|
|
||||||
and `width` in microns.
|
and `width` in microns.
|
||||||
|
* `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100.
|
||||||
|
* `pageRanges` Record<string, number> (optional) - The page range to print.
|
||||||
|
* `from` Number - the first page to print.
|
||||||
|
* `to` Number - the last page to print (inclusive).
|
||||||
|
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
|
||||||
|
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`
|
||||||
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.
|
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.
|
||||||
* `printSelectionOnly` Boolean (optional) - Whether to print selection only.
|
* `printSelectionOnly` Boolean (optional) - Whether to print selection only.
|
||||||
* `landscape` Boolean (optional) - `true` for landscape, `false` for portrait.
|
|
||||||
|
|
||||||
Returns `Promise<Buffer>` - Resolves with the generated PDF data.
|
Returns `Promise<Buffer>` - Resolves with the generated PDF data.
|
||||||
|
|
||||||
|
@ -1324,7 +1333,9 @@ By default, an empty `options` will be regarded as:
|
||||||
marginsType: 0,
|
marginsType: 0,
|
||||||
printBackground: false,
|
printBackground: false,
|
||||||
printSelectionOnly: false,
|
printSelectionOnly: false,
|
||||||
landscape: false
|
landscape: false,
|
||||||
|
pageSize: 'A4',
|
||||||
|
scaleFactor: 100
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -556,14 +556,21 @@ Prints `webview`'s web page. Same as `webContents.print([options])`.
|
||||||
### `<webview>.printToPDF(options)`
|
### `<webview>.printToPDF(options)`
|
||||||
|
|
||||||
* `options` Object
|
* `options` Object
|
||||||
|
* `headerFooter` Record<string, string> (optional) - the header and footer for the PDF.
|
||||||
|
* `title` String - The title for the PDF header.
|
||||||
|
* `url` String - the url for the PDF footer.
|
||||||
|
* `landscape` Boolean (optional) - `true` for landscape, `false` for portrait.
|
||||||
* `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for
|
* `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for
|
||||||
default margin, 1 for no margin, and 2 for minimum margin.
|
default margin, 1 for no margin, and 2 for minimum margin.
|
||||||
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
|
|
||||||
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`
|
|
||||||
and `width` in microns.
|
and `width` in microns.
|
||||||
|
* `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100.
|
||||||
|
* `pageRanges` Record<string, number> (optional) - The page range to print.
|
||||||
|
* `from` Number - the first page to print.
|
||||||
|
* `to` Number - the last page to print (inclusive).
|
||||||
|
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
|
||||||
|
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`
|
||||||
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.
|
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.
|
||||||
* `printSelectionOnly` Boolean (optional) - Whether to print selection only.
|
* `printSelectionOnly` Boolean (optional) - Whether to print selection only.
|
||||||
* `landscape` Boolean (optional) - `true` for landscape, `false` for portrait.
|
|
||||||
|
|
||||||
Returns `Promise<Uint8Array>` - Resolves with the generated PDF data.
|
Returns `Promise<Uint8Array>` - Resolves with the generated PDF data.
|
||||||
|
|
||||||
|
|
|
@ -65,32 +65,34 @@ const PDFPageSizes = {
|
||||||
|
|
||||||
// Default printing setting
|
// Default printing setting
|
||||||
const defaultPrintingSetting = {
|
const defaultPrintingSetting = {
|
||||||
pageRage: [],
|
// Customizable.
|
||||||
|
pageRange: [],
|
||||||
mediaSize: {},
|
mediaSize: {},
|
||||||
landscape: false,
|
landscape: false,
|
||||||
color: 2,
|
|
||||||
headerFooterEnabled: false,
|
headerFooterEnabled: false,
|
||||||
marginsType: 0,
|
marginsType: 0,
|
||||||
isFirstRequest: false,
|
scaleFactor: 100,
|
||||||
previewUIID: 0,
|
shouldPrintBackgrounds: false,
|
||||||
previewModifiable: true,
|
shouldPrintSelectionOnly: false,
|
||||||
printToPDF: true,
|
// Non-customizable.
|
||||||
printWithCloudPrint: false,
|
printWithCloudPrint: false,
|
||||||
printWithPrivet: false,
|
printWithPrivet: false,
|
||||||
printWithExtension: false,
|
printWithExtension: false,
|
||||||
pagesPerSheet: 1,
|
pagesPerSheet: 1,
|
||||||
|
isFirstRequest: false,
|
||||||
|
previewUIID: 0,
|
||||||
|
previewModifiable: true,
|
||||||
|
printToPDF: true,
|
||||||
deviceName: 'Save as PDF',
|
deviceName: 'Save as PDF',
|
||||||
generateDraftData: true,
|
generateDraftData: true,
|
||||||
fitToPageEnabled: false,
|
|
||||||
scaleFactor: 100,
|
|
||||||
dpiHorizontal: 72,
|
dpiHorizontal: 72,
|
||||||
dpiVertical: 72,
|
dpiVertical: 72,
|
||||||
rasterizePDF: false,
|
rasterizePDF: false,
|
||||||
duplex: 0,
|
duplex: 0,
|
||||||
copies: 1,
|
copies: 1,
|
||||||
collate: true,
|
// 2 = color - see ColorModel in //printing/print_job_constants.h
|
||||||
shouldPrintBackgrounds: false,
|
color: 2,
|
||||||
shouldPrintSelectionOnly: false
|
collate: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// JavaScript implementations of WebContents.
|
// JavaScript implementations of WebContents.
|
||||||
|
@ -206,60 +208,135 @@ WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (code, h
|
||||||
|
|
||||||
// Translate the options of printToPDF.
|
// Translate the options of printToPDF.
|
||||||
WebContents.prototype.printToPDF = function (options) {
|
WebContents.prototype.printToPDF = function (options) {
|
||||||
const printingSetting = {
|
const printSettings = {
|
||||||
...defaultPrintingSetting,
|
...defaultPrintingSetting,
|
||||||
requestID: getNextId()
|
requestID: getNextId()
|
||||||
}
|
}
|
||||||
if (options.landscape) {
|
|
||||||
printingSetting.landscape = options.landscape
|
if (options.landscape !== undefined) {
|
||||||
}
|
if (typeof options.landscape !== 'boolean') {
|
||||||
if (options.fitToPageEnabled) {
|
const error = new Error('landscape must be a Boolean')
|
||||||
printingSetting.fitToPageEnabled = options.fitToPageEnabled
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
if (options.scaleFactor) {
|
printSettings.landscape = options.landscape
|
||||||
printingSetting.scaleFactor = options.scaleFactor
|
|
||||||
}
|
|
||||||
if (options.marginsType) {
|
|
||||||
printingSetting.marginsType = options.marginsType
|
|
||||||
}
|
|
||||||
if (options.printSelectionOnly) {
|
|
||||||
printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly
|
|
||||||
}
|
|
||||||
if (options.printBackground) {
|
|
||||||
printingSetting.shouldPrintBackgrounds = options.printBackground
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.pageSize) {
|
if (options.scaleFactor !== undefined) {
|
||||||
|
if (typeof options.scaleFactor !== 'number') {
|
||||||
|
const error = new Error('scaleFactor must be a Number')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
printSettings.scaleFactor = options.scaleFactor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.marginsType !== undefined) {
|
||||||
|
if (typeof options.marginsType !== 'number') {
|
||||||
|
const error = new Error('marginsType must be a Number')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
printSettings.marginsType = options.marginsType
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.printSelectionOnly !== undefined) {
|
||||||
|
if (typeof options.printSelectionOnly !== 'boolean') {
|
||||||
|
const error = new Error('printSelectionOnly must be a Boolean')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
printSettings.shouldPrintSelectionOnly = options.printSelectionOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.printBackground !== undefined) {
|
||||||
|
if (typeof options.printBackground !== 'boolean') {
|
||||||
|
const error = new Error('printBackground must be a Boolean')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
printSettings.shouldPrintBackgrounds = options.printBackground
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.pageRanges !== undefined) {
|
||||||
|
const pageRanges = options.pageRanges
|
||||||
|
if (!pageRanges.hasOwnProperty('from') || !pageRanges.hasOwnProperty('to')) {
|
||||||
|
const error = new Error(`pageRanges must be an Object with 'from' and 'to' properties`)
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof pageRanges.from !== 'number') {
|
||||||
|
const error = new Error('pageRanges.from must be a Number')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof pageRanges.to !== 'number') {
|
||||||
|
const error = new Error('pageRanges.to must be a Number')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chromium uses 1-based page ranges, so increment each by 1.
|
||||||
|
printSettings.pageRange = [{
|
||||||
|
from: pageRanges.from + 1,
|
||||||
|
to: pageRanges.to + 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.headerFooter !== undefined) {
|
||||||
|
const headerFooter = options.headerFooter
|
||||||
|
printSettings.headerFooterEnabled = true
|
||||||
|
if (typeof headerFooter === 'object') {
|
||||||
|
if (!headerFooter.url || !headerFooter.title) {
|
||||||
|
const error = new Error('url and title properties are required for headerFooter')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
if (typeof headerFooter.title !== 'string') {
|
||||||
|
const error = new Error('headerFooter.title must be a String')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
printSettings.title = headerFooter.title
|
||||||
|
|
||||||
|
if (typeof headerFooter.url !== 'string') {
|
||||||
|
const error = new Error('headerFooter.url must be a String')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
printSettings.url = headerFooter.url
|
||||||
|
} else {
|
||||||
|
const error = new Error('headerFooter must be an Object')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally set size for PDF.
|
||||||
|
if (options.pageSize !== undefined) {
|
||||||
const pageSize = options.pageSize
|
const pageSize = options.pageSize
|
||||||
if (typeof pageSize === 'object') {
|
if (typeof pageSize === 'object') {
|
||||||
if (!pageSize.height || !pageSize.width) {
|
if (!pageSize.height || !pageSize.width) {
|
||||||
return Promise.reject(new Error('Must define height and width for pageSize'))
|
const error = new Error('height and width properties are required for pageSize')
|
||||||
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
// Dimensions in Microns
|
// Dimensions in Microns
|
||||||
// 1 meter = 10^6 microns
|
// 1 meter = 10^6 microns
|
||||||
printingSetting.mediaSize = {
|
printSettings.mediaSize = {
|
||||||
name: 'CUSTOM',
|
name: 'CUSTOM',
|
||||||
custom_display_name: 'Custom',
|
custom_display_name: 'Custom',
|
||||||
height_microns: Math.ceil(pageSize.height),
|
height_microns: Math.ceil(pageSize.height),
|
||||||
width_microns: Math.ceil(pageSize.width)
|
width_microns: Math.ceil(pageSize.width)
|
||||||
}
|
}
|
||||||
} else if (PDFPageSizes[pageSize]) {
|
} else if (PDFPageSizes[pageSize]) {
|
||||||
printingSetting.mediaSize = PDFPageSizes[pageSize]
|
printSettings.mediaSize = PDFPageSizes[pageSize]
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error(`Does not support pageSize with ${pageSize}`))
|
const error = new Error(`Unsupported pageSize: ${pageSize}`)
|
||||||
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printingSetting.mediaSize = PDFPageSizes['A4']
|
printSettings.mediaSize = PDFPageSizes['A4']
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chromium expects this in a 0-100 range number, not as float
|
// Chromium expects this in a 0-100 range number, not as float
|
||||||
printingSetting.scaleFactor = Math.ceil(printingSetting.scaleFactor) % 100
|
printSettings.scaleFactor = Math.ceil(printSettings.scaleFactor) % 100
|
||||||
// PrinterType enum from //printing/print_job_constants.h
|
// PrinterType enum from //printing/print_job_constants.h
|
||||||
printingSetting.printerType = 2
|
printSettings.printerType = 2
|
||||||
if (features.isPrintingEnabled()) {
|
if (features.isPrintingEnabled()) {
|
||||||
return this._printToPDF(printingSetting)
|
return this._printToPDF(printSettings)
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Printing feature is disabled'))
|
const error = new Error('Printing feature is disabled')
|
||||||
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1414,17 +1414,40 @@ describe('webContents module', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
ifdescribe(features.isPrintingEnabled())('printToPDF()', () => {
|
ifdescribe(features.isPrintingEnabled())('printToPDF()', () => {
|
||||||
afterEach(closeAllWindows)
|
let w: BrowserWindow
|
||||||
it('can print to PDF', async () => {
|
|
||||||
const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } })
|
beforeEach(async () => {
|
||||||
|
w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } })
|
||||||
await w.loadURL('data:text/html,<h1>Hello, World!</h1>')
|
await w.loadURL('data:text/html,<h1>Hello, World!</h1>')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(closeAllWindows)
|
||||||
|
|
||||||
|
it('rejects on incorrectly typed parameters', async () => {
|
||||||
|
const badTypes = {
|
||||||
|
marginsType: 'terrible',
|
||||||
|
scaleFactor: 'not-a-number',
|
||||||
|
landscape: [],
|
||||||
|
pageRanges: { 'oops': 'im-not-the-right-key' },
|
||||||
|
headerFooter: '123',
|
||||||
|
printSelectionOnly: 1,
|
||||||
|
printBackground: 2,
|
||||||
|
pageSize: 'IAmAPageSize'
|
||||||
|
}
|
||||||
|
|
||||||
|
// These will hard crash in Chromium unless we type-check
|
||||||
|
for (const [key, value] of Object.entries(badTypes)) {
|
||||||
|
const param = { [key]: value }
|
||||||
|
await expect(w.webContents.printToPDF(param)).to.eventually.be.rejected()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can print to PDF', async () => {
|
||||||
const data = await w.webContents.printToPDF({})
|
const data = await w.webContents.printToPDF({})
|
||||||
expect(data).to.be.an.instanceof(Buffer).that.is.not.empty()
|
expect(data).to.be.an.instanceof(Buffer).that.is.not.empty()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not crash when called multiple times', async () => {
|
it('does not crash when called multiple times', async () => {
|
||||||
const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } })
|
|
||||||
await w.loadURL('data:text/html,<h1>Hello, World!</h1>')
|
|
||||||
const promises = []
|
const promises = []
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
promises.push(w.webContents.printToPDF({}))
|
promises.push(w.webContents.printToPDF({}))
|
||||||
|
|
Loading…
Reference in a new issue