-
-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathrender.js
207 lines (183 loc) · 5.66 KB
/
render.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/* global Plotly:false */
const semver = require('semver')
const remote = require('../../util/remote')
const cst = require('./constants')
/**
* @param {object} info : info object
* - figure
* - format
* - width
* - height
* - scale
* - encoded
* @param {object} opts : component options
* - mapboxAccessToken
* - plotGlPixelRatio
* @param {function} sendToMain
* - errorCode
* - result
* - imgData
*/
function render (info, opts, sendToMain) {
const figure = info.figure
const format = info.format
const encoded = info.encoded
const config = Object.assign({
mapboxAccessToken: opts.mapboxAccessToken || null,
plotGlPixelRatio: opts.plotGlPixelRatio || cst.plotGlPixelRatio
}, figure.config)
const result = {}
let errorCode = null
const done = () => {
if (errorCode) {
result.msg = cst.statusMsg[errorCode]
}
sendToMain(errorCode, result)
}
const PRINT_TO_PDF = (format === 'pdf' || format === 'eps')
const PRINT_TO_EMF = (format === 'emf')
// stash `paper_bgcolor` here in order to set the pdf window bg color
let bgColor
const pdfBackground = (gd, _bgColor) => {
if (!bgColor) bgColor = _bgColor
gd._fullLayout.paper_bgcolor = 'rgba(0,0,0,0)'
}
const imgOpts = {
format: (PRINT_TO_PDF || PRINT_TO_EMF) ? 'svg' : format,
width: info.width,
height: info.height,
// only works as of plotly.js v1.31.0
scale: info.scale,
// return image data w/o the leading 'data:image' spec
imageDataOnly: PRINT_TO_EMF || (!PRINT_TO_PDF && !encoded),
// blend (emf|jpeg) background color as (emf|jpeg) does not support transparency
setBackground: (format === 'jpeg' || format === 'emf') ? 'opaque'
: PRINT_TO_PDF ? pdfBackground
: ''
}
let promise
if (semver.gte(Plotly.version, '1.30.0')) {
promise = Plotly
.toImage({ data: figure.data, layout: figure.layout, config: config }, imgOpts)
.then((imgData) => {
if (PRINT_TO_PDF) {
return toPDF(imgData, imgOpts, bgColor)
} else {
return imgData
}
})
} else if (semver.gte(Plotly.version, '1.11.0')) {
const gd = document.createElement('div')
promise = Plotly
.newPlot(gd, figure.data, figure.layout, config)
.then(() => Plotly.toImage(gd, imgOpts))
.then((imgData) => {
Plotly.purge(gd)
switch (format) {
case 'png':
case 'jpeg':
case 'webp':
if (encoded) {
return imgData
} else {
return imgData.replace(cst.imgPrefix.base64, '')
}
case 'svg':
if (encoded) {
return imgData
} else {
return decodeSVG(imgData)
}
case 'pdf':
case 'eps':
return toPDF(imgData, imgOpts, bgColor)
}
})
} else {
errorCode = 526
result.error = `plotly.js version: ${Plotly.version}`
return done()
}
promise.then((imgData) => {
result.imgData = imgData
return done()
}).catch((err) => {
errorCode = 525
result.error = JSON.stringify(err, ['message', 'arguments', 'type', 'name'])
return done()
})
}
function decodeSVG (imgData) {
return window.decodeURIComponent(imgData.replace(cst.imgPrefix.svg, ''))
}
function toPDF (imgData, imgOpts, bgColor) {
const wPx = imgOpts.scale * imgOpts.width
const hPx = imgOpts.scale * imgOpts.height
const pxByMicrometer = 0.00377957517575025
const offset = 6
// See other available options:
// https://github.com/electron/electron/blob/master/docs/api/web-contents.md#contentsprinttopdfoptions-callback
const printOpts = {
// no margins
marginsType: 1,
// make bg (set to `paper_bgcolor` value) appear in export
printBackground: true,
// printToPDF expects page size setting in micrometer (1e-6 m)
// - Px by micrometer factor is taken from
// https://www.translatorscafe.com/unit-converter/en/length/13-110/micrometer-pixel/
// - Even under the `marginsType: 1` setting (meaning no margins), printToPDF still
// outputs small margins. We need to take this into consideration so that the output PDF
// does not span multiple pages. The offset value was found empirically via trial-and-error.
pageSize: {
width: (wPx + offset) / pxByMicrometer,
height: (hPx + offset) / pxByMicrometer
}
}
return new Promise((resolve, reject) => {
let win = remote.createBrowserWindow({
width: wPx,
height: hPx,
show: !!imgOpts.debug
})
win.on('closed', () => {
win = null
})
const html = window.encodeURIComponent(`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
margin: 0;
padding: 0;
background-color: ${bgColor}
}
</style>
</head>
<body><img/></body>
</html>`)
// we can't set image src into html as chromium has a 2MB URL limit
// https://craignicol.wordpress.com/2016/07/19/excellent-export-and-the-chrome-url-limit/
win.loadURL(`data:text/html,${html}`)
win.webContents.executeJavaScript(`new Promise((resolve, reject) => {
const img = document.body.firstChild
img.onload = resolve
img.onerror = reject
img.src = "${imgData}"
setTimeout(() => reject(new Error('too long to load image')), ${cst.pdfPageLoadImgTimeout})
})`).then(() => {
win.webContents.printToPDF(printOpts, (err, pdfData) => {
if (err) {
reject(err)
} else {
resolve(pdfData)
}
win.close()
})
}).catch((err) => {
reject(err)
win.close()
})
})
}
module.exports = render