@napi-rs/canvas 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +34 -0
- package/README.md +30 -49
- package/geometry.js +166 -33
- package/index.d.ts +232 -47
- package/index.js +578 -25
- package/load-image.js +18 -12
- package/node-canvas.d.ts +6 -1
- package/node-canvas.js +22 -3
- package/package.json +13 -18
package/index.js
CHANGED
|
@@ -5,9 +5,11 @@ const {
|
|
|
5
5
|
clearAllCache,
|
|
6
6
|
CanvasRenderingContext2D,
|
|
7
7
|
CanvasElement,
|
|
8
|
+
CanvasGradient,
|
|
9
|
+
CanvasPattern,
|
|
8
10
|
SVGCanvas,
|
|
9
11
|
Path: Path2D,
|
|
10
|
-
ImageData,
|
|
12
|
+
ImageData: NativeImageData,
|
|
11
13
|
Image,
|
|
12
14
|
FontKey,
|
|
13
15
|
GlobalFonts,
|
|
@@ -20,6 +22,10 @@ const {
|
|
|
20
22
|
GifEncoder,
|
|
21
23
|
GifDisposal,
|
|
22
24
|
LottieAnimation,
|
|
25
|
+
TextMetrics,
|
|
26
|
+
_argumentCounts,
|
|
27
|
+
_imageDataConstructor,
|
|
28
|
+
_toNumber: toNumber,
|
|
23
29
|
} = require('./js-binding')
|
|
24
30
|
|
|
25
31
|
const { DOMPoint, DOMMatrix, DOMRect } = require('./geometry')
|
|
@@ -33,6 +39,45 @@ if (GifEncoder && typeof Symbol.dispose !== 'undefined') {
|
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
// napi-rs defines a class's accessors in name order. Chrome's TextMetrics
|
|
43
|
+
// prototype has them in IDL order (text_metrics.idl), which for...in shows,
|
|
44
|
+
// then the constructor: define them again in that order.
|
|
45
|
+
for (const name of [
|
|
46
|
+
'width',
|
|
47
|
+
'actualBoundingBoxLeft',
|
|
48
|
+
'actualBoundingBoxRight',
|
|
49
|
+
'fontBoundingBoxAscent',
|
|
50
|
+
'fontBoundingBoxDescent',
|
|
51
|
+
'actualBoundingBoxAscent',
|
|
52
|
+
'actualBoundingBoxDescent',
|
|
53
|
+
'hangingBaseline',
|
|
54
|
+
'alphabeticBaseline',
|
|
55
|
+
'ideographicBaseline',
|
|
56
|
+
'emHeightAscent',
|
|
57
|
+
'emHeightDescent',
|
|
58
|
+
'constructor',
|
|
59
|
+
]) {
|
|
60
|
+
const descriptor = Object.getOwnPropertyDescriptor(TextMetrics.prototype, name)
|
|
61
|
+
delete TextMetrics.prototype[name]
|
|
62
|
+
Object.defineProperty(TextMetrics.prototype, name, descriptor)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Chrome's Symbol.toStringTag, an own property of each prototype. The
|
|
66
|
+
// canvas and Image classes take their HTML element names; SVGCanvas has no
|
|
67
|
+
// counterpart and no tag.
|
|
68
|
+
for (const [Class, tag] of [
|
|
69
|
+
[CanvasRenderingContext2D, 'CanvasRenderingContext2D'],
|
|
70
|
+
[CanvasGradient, 'CanvasGradient'],
|
|
71
|
+
[CanvasPattern, 'CanvasPattern'],
|
|
72
|
+
[Path2D, 'Path2D'],
|
|
73
|
+
[NativeImageData, 'ImageData'],
|
|
74
|
+
[TextMetrics, 'TextMetrics'],
|
|
75
|
+
[CanvasElement, 'HTMLCanvasElement'],
|
|
76
|
+
[Image, 'HTMLImageElement'],
|
|
77
|
+
]) {
|
|
78
|
+
Object.defineProperty(Class.prototype, Symbol.toStringTag, { value: tag, configurable: true })
|
|
79
|
+
}
|
|
80
|
+
|
|
36
81
|
const SvgExportFlag = {
|
|
37
82
|
ConvertTextToPaths: 0x01,
|
|
38
83
|
NoPrettyXML: 0x02,
|
|
@@ -58,25 +103,61 @@ if (!('has' in GlobalFonts)) {
|
|
|
58
103
|
})
|
|
59
104
|
}
|
|
60
105
|
|
|
61
|
-
|
|
62
|
-
|
|
106
|
+
// Re-evaluating this module (Jest resets its module registry per test
|
|
107
|
+
// file; the .node binding and its prototypes stay loaded) must wrap the
|
|
108
|
+
// native methods again, not the previous wrappers: their argument padding
|
|
109
|
+
// would corrupt the argument count the native methods read.
|
|
110
|
+
const NATIVE_METHODS = Symbol.for('@napi-rs/canvas:native-methods')
|
|
111
|
+
function nativeMethods(proto) {
|
|
112
|
+
let stash = proto[NATIVE_METHODS]
|
|
113
|
+
if (stash === undefined) {
|
|
114
|
+
stash = {}
|
|
115
|
+
Object.defineProperty(proto, NATIVE_METHODS, { value: stash })
|
|
116
|
+
}
|
|
117
|
+
return stash
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const canvasNative = nativeMethods(CanvasElement.prototype)
|
|
121
|
+
const _toBlob = (canvasNative.toBlob ??= CanvasElement.prototype.toBlob)
|
|
122
|
+
const _convertToBlob = (canvasNative.convertToBlob ??= CanvasElement.prototype.convertToBlob)
|
|
123
|
+
// A native method of CanvasElement for the receiver check: with no arguments
|
|
124
|
+
// it has no side effects.
|
|
125
|
+
const canvasBrand = (canvasNative.getContext ??= CanvasElement.prototype.getContext)
|
|
63
126
|
if ('Blob' in globalThis) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
// toBlob(callback, type, quality), as Chrome: the receiver, the argument
|
|
128
|
+
// count and the callback are checked first, then the native method converts
|
|
129
|
+
// the type and encodes. The callback gets the Blob, or null without pixels
|
|
130
|
+
// or when the encoder fails, from a task. A throw in it is an uncaught
|
|
131
|
+
// exception, as in Chrome (window.onerror).
|
|
132
|
+
CanvasElement.prototype.toBlob = function toBlob(callback) {
|
|
133
|
+
if (arguments.length < 1) {
|
|
134
|
+
argumentCountError(canvasBrand, this, 'toBlob', 'HTMLCanvasElement', 1, 0)
|
|
135
|
+
}
|
|
136
|
+
if (typeof callback !== 'function') {
|
|
137
|
+
bindingTypeError(canvasBrand, this, 'toBlob', 'HTMLCanvasElement', "parameter 1 is not of type 'Function'.")
|
|
138
|
+
}
|
|
139
|
+
// Only the arguments that were passed: past arguments.length,
|
|
140
|
+
// arguments[i] reads Object.prototype[i], which Chrome never reads.
|
|
141
|
+
const type = arguments.length > 1 ? arguments[1] : undefined
|
|
142
|
+
const quality = arguments.length > 2 ? arguments[2] : undefined
|
|
143
|
+
_toBlob.call(this, type, quality).then(
|
|
144
|
+
(result) => {
|
|
145
|
+
const blob = result === null ? null : new Blob([result.data], { type: result.type })
|
|
146
|
+
queueMicrotask(() => callback(blob))
|
|
70
147
|
},
|
|
71
|
-
|
|
72
|
-
quality,
|
|
148
|
+
() => queueMicrotask(() => callback(null)),
|
|
73
149
|
)
|
|
74
150
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
151
|
+
// convertToBlob(options): Chrome's OffscreenCanvas method. An options error
|
|
152
|
+
// rejects the promise, as a WebIDL conversion error of a promise method.
|
|
153
|
+
CanvasElement.prototype.convertToBlob = function convertToBlob() {
|
|
154
|
+
let pending
|
|
155
|
+
try {
|
|
156
|
+
pending = _convertToBlob.call(this, arguments.length > 0 ? arguments[0] : undefined)
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return Promise.reject(error)
|
|
159
|
+
}
|
|
160
|
+
return pending.then((result) => new Blob([result.data], { type: result.type }))
|
|
80
161
|
}
|
|
81
162
|
} else {
|
|
82
163
|
// oxlint-disable-next-line no-unused-vars
|
|
@@ -89,7 +170,8 @@ if ('Blob' in globalThis) {
|
|
|
89
170
|
}
|
|
90
171
|
}
|
|
91
172
|
|
|
92
|
-
const
|
|
173
|
+
const ctxNative = nativeMethods(CanvasRenderingContext2D.prototype)
|
|
174
|
+
const _getTransform = (ctxNative.getTransform ??= CanvasRenderingContext2D.prototype.getTransform)
|
|
93
175
|
|
|
94
176
|
CanvasRenderingContext2D.prototype.getTransform = function getTransform() {
|
|
95
177
|
const transform = _getTransform.apply(this, arguments)
|
|
@@ -101,12 +183,470 @@ CanvasRenderingContext2D.prototype.getTransform = function getTransform() {
|
|
|
101
183
|
return new DOMMatrix([a, b, c, d, e, f])
|
|
102
184
|
}
|
|
103
185
|
|
|
186
|
+
// Chrome checks the receiver ('Illegal invocation'), then the argument count,
|
|
187
|
+
// before it converts any argument. `brand` is a native method of the class: a
|
|
188
|
+
// call on another receiver throws 'Illegal invocation'. As Blink's binding
|
|
189
|
+
// errors, the stack keeps the bare message and `message` gets the prefix, as
|
|
190
|
+
// an enumerable own property.
|
|
191
|
+
function argumentCountError(brand, self, method, iface, required, present) {
|
|
192
|
+
brand.call(self)
|
|
193
|
+
const message = `${required} argument${required === 1 ? '' : 's'} required, but only ${present} present.`
|
|
194
|
+
const error = new TypeError(message)
|
|
195
|
+
void error.stack
|
|
196
|
+
Object.defineProperty(error, 'message', {
|
|
197
|
+
value: `Failed to execute '${method}' on '${iface}': ${message}`,
|
|
198
|
+
writable: true,
|
|
199
|
+
enumerable: true,
|
|
200
|
+
configurable: true,
|
|
201
|
+
})
|
|
202
|
+
throw error
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// A TypeError as Blink's bindings throw it after the receiver check, with the
|
|
206
|
+
// message formatted as argumentCountError does.
|
|
207
|
+
function bindingTypeError(brand, self, method, iface, message) {
|
|
208
|
+
brand.call(self)
|
|
209
|
+
const error = new TypeError(message)
|
|
210
|
+
void error.stack
|
|
211
|
+
Object.defineProperty(error, 'message', {
|
|
212
|
+
value: `Failed to execute '${method}' on '${iface}': ${message}`,
|
|
213
|
+
writable: true,
|
|
214
|
+
enumerable: true,
|
|
215
|
+
configurable: true,
|
|
216
|
+
})
|
|
217
|
+
throw error
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Wraps `proto[method]` with Chrome's argument count check; the wrapper's
|
|
221
|
+
// `length` is the required argument count, as Chrome's Function.length.
|
|
222
|
+
// napi pads missing arguments with undefined, so the native method cannot
|
|
223
|
+
// tell f(1) from f(1, undefined); an explicit undefined counts in Chrome.
|
|
224
|
+
function requireArguments(proto, brand, iface, method, required) {
|
|
225
|
+
const stash = nativeMethods(proto)
|
|
226
|
+
const native = (stash[method] ??= proto[method])
|
|
227
|
+
const wrapper = {
|
|
228
|
+
[method]() {
|
|
229
|
+
if (arguments.length < required) {
|
|
230
|
+
argumentCountError(brand, this, method, iface, required, arguments.length)
|
|
231
|
+
}
|
|
232
|
+
return native.apply(this, arguments)
|
|
233
|
+
},
|
|
234
|
+
}[method]
|
|
235
|
+
Object.defineProperty(wrapper, 'length', { value: required })
|
|
236
|
+
proto[method] = wrapper
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const ctxBrand = CanvasRenderingContext2D.prototype.getContextAttributes
|
|
240
|
+
for (const [method, required] of [
|
|
241
|
+
['arc', 5],
|
|
242
|
+
['arcTo', 5],
|
|
243
|
+
['bezierCurveTo', 6],
|
|
244
|
+
['clearRect', 4],
|
|
245
|
+
['createConicGradient', 3],
|
|
246
|
+
['createLinearGradient', 4],
|
|
247
|
+
['createRadialGradient', 6],
|
|
248
|
+
['ellipse', 7],
|
|
249
|
+
['fillRect', 4],
|
|
250
|
+
['fillText', 3],
|
|
251
|
+
['getImageData', 4],
|
|
252
|
+
['lineTo', 2],
|
|
253
|
+
['measureText', 1],
|
|
254
|
+
['moveTo', 2],
|
|
255
|
+
['quadraticCurveTo', 4],
|
|
256
|
+
['rect', 4],
|
|
257
|
+
['rotate', 1],
|
|
258
|
+
['roundRect', 4],
|
|
259
|
+
['scale', 2],
|
|
260
|
+
['setLineDash', 1],
|
|
261
|
+
['strokeRect', 4],
|
|
262
|
+
['strokeText', 3],
|
|
263
|
+
['transform', 6],
|
|
264
|
+
['translate', 2],
|
|
265
|
+
]) {
|
|
266
|
+
requireArguments(CanvasRenderingContext2D.prototype, ctxBrand, 'CanvasRenderingContext2D', method, required)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// getContext(contextId, options): Chrome needs the id. The native method is
|
|
270
|
+
// its own brand: with no arguments it converts undefined to 'undefined' and
|
|
271
|
+
// returns null, without side effects.
|
|
272
|
+
for (const [Class, iface] of [
|
|
273
|
+
[CanvasElement, 'HTMLCanvasElement'],
|
|
274
|
+
[SVGCanvas, 'SVGCanvas'],
|
|
275
|
+
]) {
|
|
276
|
+
const stash = nativeMethods(Class.prototype)
|
|
277
|
+
requireArguments(Class.prototype, (stash.getContext ??= Class.prototype.getContext), iface, 'getContext', 1)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const pathBrand = Path2D.prototype.getFillType
|
|
281
|
+
for (const [method, required] of [
|
|
282
|
+
['arc', 5],
|
|
283
|
+
['arcTo', 5],
|
|
284
|
+
['bezierCurveTo', 6],
|
|
285
|
+
['ellipse', 7],
|
|
286
|
+
['lineTo', 2],
|
|
287
|
+
['moveTo', 2],
|
|
288
|
+
['quadraticCurveTo', 4],
|
|
289
|
+
['rect', 4],
|
|
290
|
+
['roundRect', 4],
|
|
291
|
+
]) {
|
|
292
|
+
requireArguments(Path2D.prototype, pathBrand, 'Path2D', method, required)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// The argument-count markers for 0 to 9 arguments. napi pads missing
|
|
296
|
+
// arguments with undefined, so the wrappers below pass the real count after
|
|
297
|
+
// the positional arguments, as one of these markers: the native methods read
|
|
298
|
+
// a count only from a marker, and any other value there (from a raw
|
|
299
|
+
// js-binding call) is an extra argument.
|
|
300
|
+
const ARGUMENT_COUNTS = _argumentCounts()
|
|
301
|
+
|
|
302
|
+
// new ImageData(): Chrome checks `new` and the real argument count before it
|
|
303
|
+
// picks an overload (image_data.idl). The class napi-rs makes sees neither,
|
|
304
|
+
// so a native function checks them and builds the instance itself, from
|
|
305
|
+
// new.target.prototype. Instances share the class prototype, so instanceof
|
|
306
|
+
// works for those that createImageData and getImageData return.
|
|
307
|
+
const ImageData = _imageDataConstructor()
|
|
308
|
+
Object.defineProperty(ImageData, 'length', { value: 2 })
|
|
309
|
+
Object.defineProperty(ImageData, 'prototype', { value: NativeImageData.prototype, writable: false })
|
|
310
|
+
Object.defineProperty(NativeImageData.prototype, 'constructor', {
|
|
311
|
+
value: ImageData,
|
|
312
|
+
writable: true,
|
|
313
|
+
enumerable: false,
|
|
314
|
+
configurable: true,
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// What the wrappers below pass after their `n` positional arguments, for a
|
|
318
|
+
// call with `count` (1 to `n`) of them, the last one `last`. Without a marker
|
|
319
|
+
// the native method counts up to the last argument that is not undefined:
|
|
320
|
+
// that is `count` unless `last` is an explicit undefined, and only then is
|
|
321
|
+
// the marker needed.
|
|
322
|
+
function argumentCount(count, last) {
|
|
323
|
+
return last === undefined ? ARGUMENT_COUNTS[count] : undefined
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// fill(), clip(), stroke(), isPointInPath() and isPointInStroke(): WebIDL
|
|
327
|
+
// picks the overload from the argument count (canvas_2d_recorder_context.idl),
|
|
328
|
+
// and an explicit undefined counts: fill(path, undefined) is the nonzero
|
|
329
|
+
// fill of path, fill(undefined, 'evenodd') a TypeError.
|
|
330
|
+
// These wrappers and drawImage's read only the arguments the call has: past
|
|
331
|
+
// arguments.length, arguments[i] would read Object.prototype[i], which
|
|
332
|
+
// Chrome never reads.
|
|
333
|
+
const _fill = (ctxNative.fill ??= CanvasRenderingContext2D.prototype.fill)
|
|
334
|
+
CanvasRenderingContext2D.prototype.fill = function fill() {
|
|
335
|
+
const count = Math.min(arguments.length, 2)
|
|
336
|
+
return _fill.call(
|
|
337
|
+
this,
|
|
338
|
+
count > 0 ? arguments[0] : undefined,
|
|
339
|
+
count > 1 ? arguments[1] : undefined,
|
|
340
|
+
count === 0 ? undefined : argumentCount(count, arguments[count - 1]),
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const _clip = (ctxNative.clip ??= CanvasRenderingContext2D.prototype.clip)
|
|
345
|
+
CanvasRenderingContext2D.prototype.clip = function clip() {
|
|
346
|
+
const count = Math.min(arguments.length, 2)
|
|
347
|
+
return _clip.call(
|
|
348
|
+
this,
|
|
349
|
+
count > 0 ? arguments[0] : undefined,
|
|
350
|
+
count > 1 ? arguments[1] : undefined,
|
|
351
|
+
count === 0 ? undefined : argumentCount(count, arguments[count - 1]),
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const _stroke = (ctxNative.stroke ??= CanvasRenderingContext2D.prototype.stroke)
|
|
356
|
+
CanvasRenderingContext2D.prototype.stroke = function stroke() {
|
|
357
|
+
if (arguments.length === 0) {
|
|
358
|
+
return _stroke.call(this, undefined, undefined)
|
|
359
|
+
}
|
|
360
|
+
const path = arguments[0]
|
|
361
|
+
return _stroke.call(this, path, argumentCount(1, path))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const _isPointInPath = (ctxNative.isPointInPath ??= CanvasRenderingContext2D.prototype.isPointInPath)
|
|
365
|
+
CanvasRenderingContext2D.prototype.isPointInPath = function isPointInPath() {
|
|
366
|
+
if (arguments.length < 2) {
|
|
367
|
+
argumentCountError(ctxBrand, this, 'isPointInPath', 'CanvasRenderingContext2D', 2, arguments.length)
|
|
368
|
+
}
|
|
369
|
+
const count = Math.min(arguments.length, 4)
|
|
370
|
+
return _isPointInPath.call(
|
|
371
|
+
this,
|
|
372
|
+
arguments[0],
|
|
373
|
+
arguments[1],
|
|
374
|
+
count > 2 ? arguments[2] : undefined,
|
|
375
|
+
count > 3 ? arguments[3] : undefined,
|
|
376
|
+
// (x, y, undefined) converts as (x, y) when x is not an object (only an
|
|
377
|
+
// object can be a Path2D), so the count is not needed.
|
|
378
|
+
count === 3 && typeof arguments[0] !== 'object' ? undefined : argumentCount(count, arguments[count - 1]),
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
Object.defineProperty(CanvasRenderingContext2D.prototype.isPointInPath, 'length', { value: 2 })
|
|
382
|
+
|
|
383
|
+
const _isPointInStroke = (ctxNative.isPointInStroke ??= CanvasRenderingContext2D.prototype.isPointInStroke)
|
|
384
|
+
CanvasRenderingContext2D.prototype.isPointInStroke = function isPointInStroke() {
|
|
385
|
+
if (arguments.length < 2) {
|
|
386
|
+
argumentCountError(ctxBrand, this, 'isPointInStroke', 'CanvasRenderingContext2D', 2, arguments.length)
|
|
387
|
+
}
|
|
388
|
+
const count = Math.min(arguments.length, 3)
|
|
389
|
+
return _isPointInStroke.call(
|
|
390
|
+
this,
|
|
391
|
+
arguments[0],
|
|
392
|
+
arguments[1],
|
|
393
|
+
count > 2 ? arguments[2] : undefined,
|
|
394
|
+
argumentCount(count, arguments[count - 1]),
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
Object.defineProperty(CanvasRenderingContext2D.prototype.isPointInStroke, 'length', { value: 2 })
|
|
398
|
+
|
|
399
|
+
// A TypeError as Chrome's bindings throw it, as in argumentCountError: the
|
|
400
|
+
// stack keeps the bare message, and `message` gets the prefix.
|
|
401
|
+
function contextTypeError(prefix, message) {
|
|
402
|
+
const error = new TypeError(message)
|
|
403
|
+
void error.stack
|
|
404
|
+
Object.defineProperty(error, 'message', {
|
|
405
|
+
value: prefix + message,
|
|
406
|
+
writable: true,
|
|
407
|
+
enumerable: true,
|
|
408
|
+
configurable: true,
|
|
409
|
+
})
|
|
410
|
+
return error
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// An `unrestricted double` member of a matrix dictionary; undefined when it
|
|
414
|
+
// is absent. The native ToNumber keeps the engine's texts, and the error
|
|
415
|
+
// names the member, as in Chrome.
|
|
416
|
+
function matrixMember(value, key, dictionary, prefix) {
|
|
417
|
+
if (typeof value === 'number' || value === undefined) return value
|
|
418
|
+
return toNumber(value, `${prefix}Failed to read the '${key}' property from '${dictionary}': `)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// a and m11 (and so on) disagree: both given and not equal. Two NaNs agree.
|
|
422
|
+
const mismatch = (x, y) => x !== undefined && y !== undefined && x !== y && (x === x || y === y)
|
|
423
|
+
// A 3D member against its identity value; absent is the identity. As C++
|
|
424
|
+
// truthiness, NaN counts as 3D and -0 as 0.
|
|
425
|
+
const notZero = (v) => v !== undefined && v !== 0
|
|
426
|
+
const notOne = (v) => v !== undefined && v !== 1
|
|
427
|
+
|
|
428
|
+
// The matrix of a DOMMatrix2DInit, or of a DOMMatrixInit when `full`, as
|
|
429
|
+
// [m11, m12, m21, m22, m41, m42]; undefined and null are {}. As Blink's
|
|
430
|
+
// bindings, every member is read with [[Get]], in order (a..f, m11, m12,
|
|
431
|
+
// m21, m22, m41, m42, then is2D and m13..m44), then checked as
|
|
432
|
+
// DOMMatrixReadOnly::ValidateAndFixup (dom_matrix_read_only.cc). A DOMMatrix
|
|
433
|
+
// is read the same way: an own property, a patched getter or a Proxy may
|
|
434
|
+
// make its members disagree. The result is one shared array: read it before
|
|
435
|
+
// any other user code runs.
|
|
436
|
+
const MATRIX = [1, 0, 0, 1, 0, 0]
|
|
437
|
+
function matrix2DFromInit(init, prefix, full) {
|
|
438
|
+
if (init === undefined || init === null) {
|
|
439
|
+
MATRIX[0] = 1
|
|
440
|
+
MATRIX[1] = 0
|
|
441
|
+
MATRIX[2] = 0
|
|
442
|
+
MATRIX[3] = 1
|
|
443
|
+
MATRIX[4] = 0
|
|
444
|
+
MATRIX[5] = 0
|
|
445
|
+
return MATRIX
|
|
446
|
+
}
|
|
447
|
+
if (typeof init !== 'object' && typeof init !== 'function') {
|
|
448
|
+
throw contextTypeError(prefix, `The provided value is not of type '${full ? 'DOMMatrixInit' : 'DOMMatrix2DInit'}'.`)
|
|
449
|
+
}
|
|
450
|
+
const D2 = 'DOMMatrix2DInit'
|
|
451
|
+
const a = matrixMember(init.a, 'a', D2, prefix)
|
|
452
|
+
const b = matrixMember(init.b, 'b', D2, prefix)
|
|
453
|
+
const c = matrixMember(init.c, 'c', D2, prefix)
|
|
454
|
+
const d = matrixMember(init.d, 'd', D2, prefix)
|
|
455
|
+
const e = matrixMember(init.e, 'e', D2, prefix)
|
|
456
|
+
const f = matrixMember(init.f, 'f', D2, prefix)
|
|
457
|
+
const m11 = matrixMember(init.m11, 'm11', D2, prefix)
|
|
458
|
+
const m12 = matrixMember(init.m12, 'm12', D2, prefix)
|
|
459
|
+
const m21 = matrixMember(init.m21, 'm21', D2, prefix)
|
|
460
|
+
const m22 = matrixMember(init.m22, 'm22', D2, prefix)
|
|
461
|
+
const m41 = matrixMember(init.m41, 'm41', D2, prefix)
|
|
462
|
+
const m42 = matrixMember(init.m42, 'm42', D2, prefix)
|
|
463
|
+
let is3DIn2D = false
|
|
464
|
+
if (full) {
|
|
465
|
+
const D = 'DOMMatrixInit'
|
|
466
|
+
const is2D = init.is2D
|
|
467
|
+
const m13 = matrixMember(init.m13, 'm13', D, prefix)
|
|
468
|
+
const m14 = matrixMember(init.m14, 'm14', D, prefix)
|
|
469
|
+
const m23 = matrixMember(init.m23, 'm23', D, prefix)
|
|
470
|
+
const m24 = matrixMember(init.m24, 'm24', D, prefix)
|
|
471
|
+
const m31 = matrixMember(init.m31, 'm31', D, prefix)
|
|
472
|
+
const m32 = matrixMember(init.m32, 'm32', D, prefix)
|
|
473
|
+
const m33 = matrixMember(init.m33, 'm33', D, prefix)
|
|
474
|
+
const m34 = matrixMember(init.m34, 'm34', D, prefix)
|
|
475
|
+
const m43 = matrixMember(init.m43, 'm43', D, prefix)
|
|
476
|
+
const m44 = matrixMember(init.m44, 'm44', D, prefix)
|
|
477
|
+
// is2D is a boolean member: ToBoolean when present.
|
|
478
|
+
is3DIn2D =
|
|
479
|
+
is2D !== undefined &&
|
|
480
|
+
!!is2D &&
|
|
481
|
+
(notZero(m31) ||
|
|
482
|
+
notZero(m32) ||
|
|
483
|
+
notZero(m13) ||
|
|
484
|
+
notZero(m23) ||
|
|
485
|
+
notZero(m43) ||
|
|
486
|
+
notZero(m14) ||
|
|
487
|
+
notZero(m24) ||
|
|
488
|
+
notZero(m34) ||
|
|
489
|
+
notOne(m33) ||
|
|
490
|
+
notOne(m44))
|
|
491
|
+
}
|
|
492
|
+
if (
|
|
493
|
+
mismatch(a, m11) ||
|
|
494
|
+
mismatch(b, m12) ||
|
|
495
|
+
mismatch(c, m21) ||
|
|
496
|
+
mismatch(d, m22) ||
|
|
497
|
+
mismatch(e, m41) ||
|
|
498
|
+
mismatch(f, m42)
|
|
499
|
+
) {
|
|
500
|
+
throw contextTypeError(prefix, 'Property mismatch on matrix initialization.')
|
|
501
|
+
}
|
|
502
|
+
if (is3DIn2D) {
|
|
503
|
+
throw contextTypeError(prefix, 'The is2D member is set to true but the input matrix is a 3d matrix.')
|
|
504
|
+
}
|
|
505
|
+
MATRIX[0] = m11 !== undefined ? m11 : a !== undefined ? a : 1
|
|
506
|
+
MATRIX[1] = m12 !== undefined ? m12 : b !== undefined ? b : 0
|
|
507
|
+
MATRIX[2] = m21 !== undefined ? m21 : c !== undefined ? c : 0
|
|
508
|
+
MATRIX[3] = m22 !== undefined ? m22 : d !== undefined ? d : 1
|
|
509
|
+
MATRIX[4] = m41 !== undefined ? m41 : e !== undefined ? e : 0
|
|
510
|
+
MATRIX[5] = m42 !== undefined ? m42 : f !== undefined ? f : 0
|
|
511
|
+
return MATRIX
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// setTransform(): six or more arguments are six numbers, fewer a DOMMatrixInit
|
|
515
|
+
// (canvas_2d_recorder_context.idl), and an explicit undefined counts. The
|
|
516
|
+
// DOMMatrixInit is read above; the native method takes six numbers. It reads
|
|
517
|
+
// a first argument that is not a number as a DOMMatrixInit, the form a raw
|
|
518
|
+
// js-binding call may use, so ToNumber runs here for such an `a`.
|
|
519
|
+
const _setTransform = (ctxNative.setTransform ??= CanvasRenderingContext2D.prototype.setTransform)
|
|
520
|
+
// A cheap native method: on another receiver it throws 'Illegal invocation',
|
|
521
|
+
// which Chrome checks before it converts any argument.
|
|
522
|
+
const ctxReceiver = CanvasRenderingContext2D.prototype.isContextLost
|
|
523
|
+
const SET_TRANSFORM = "Failed to execute 'setTransform' on 'CanvasRenderingContext2D': "
|
|
524
|
+
CanvasRenderingContext2D.prototype.setTransform = function setTransform() {
|
|
525
|
+
if (arguments.length >= 6) {
|
|
526
|
+
let a = arguments[0]
|
|
527
|
+
if (typeof a !== 'number') {
|
|
528
|
+
ctxReceiver.call(this)
|
|
529
|
+
a = toNumber(a, SET_TRANSFORM)
|
|
530
|
+
}
|
|
531
|
+
return _setTransform.call(this, a, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5])
|
|
532
|
+
}
|
|
533
|
+
// Past arguments.length, arguments[0] would read Object.prototype[0].
|
|
534
|
+
const init = arguments.length > 0 ? arguments[0] : undefined
|
|
535
|
+
if (init !== undefined && init !== null) ctxReceiver.call(this)
|
|
536
|
+
const m = matrix2DFromInit(init, SET_TRANSFORM, true)
|
|
537
|
+
return _setTransform.call(this, m[0], m[1], m[2], m[3], m[4], m[5])
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// addPath(path, transform = {}): Chrome checks the receiver, the argument
|
|
541
|
+
// count and the path, then reads the DOMMatrix2DInit, read above. The native
|
|
542
|
+
// method takes six numbers after the path; anything else there is a
|
|
543
|
+
// DOMMatrix2DInit, which only a raw js-binding call passes. Without a
|
|
544
|
+
// transform (undefined or null is `{}`), a native method that takes only the
|
|
545
|
+
// path adds it as it is; Chrome's Path2D has no such method, so it comes off
|
|
546
|
+
// the prototype.
|
|
547
|
+
const pathNative = nativeMethods(Path2D.prototype)
|
|
548
|
+
const _addPath = (pathNative.addPath ??= Path2D.prototype.addPath)
|
|
549
|
+
const _addPathWithoutTransform = (pathNative._addPathWithoutTransform ??= Path2D.prototype._addPathWithoutTransform)
|
|
550
|
+
delete Path2D.prototype._addPathWithoutTransform
|
|
551
|
+
const ADD_PATH = "Failed to execute 'addPath' on 'Path2D': "
|
|
552
|
+
Path2D.prototype.addPath = function addPath() {
|
|
553
|
+
if (arguments.length < 1) {
|
|
554
|
+
argumentCountError(pathBrand, this, 'addPath', 'Path2D', 1, arguments.length)
|
|
555
|
+
}
|
|
556
|
+
const path = arguments[0]
|
|
557
|
+
// Past arguments.length, arguments[1] would read Object.prototype[1].
|
|
558
|
+
const transform = arguments.length > 1 ? arguments[1] : undefined
|
|
559
|
+
if (transform === undefined || transform === null) {
|
|
560
|
+
return _addPathWithoutTransform.call(this, path)
|
|
561
|
+
}
|
|
562
|
+
pathBrand.call(this)
|
|
563
|
+
try {
|
|
564
|
+
pathBrand.call(path)
|
|
565
|
+
} catch {
|
|
566
|
+
throw contextTypeError(ADD_PATH, "parameter 1 is not of type 'Path2D'.")
|
|
567
|
+
}
|
|
568
|
+
const m = matrix2DFromInit(transform, ADD_PATH, false)
|
|
569
|
+
return _addPath.call(this, path, m[0], m[1], m[2], m[3], m[4], m[5])
|
|
570
|
+
}
|
|
571
|
+
Object.defineProperty(Path2D.prototype.addPath, 'length', { value: 1 })
|
|
572
|
+
|
|
573
|
+
// createImageData(): WebIDL picks the overload from the argument count, and an
|
|
574
|
+
// explicit undefined counts: createImageData(imagedata, undefined) is the
|
|
575
|
+
// (sw, sh) form.
|
|
576
|
+
const _createImageData = (ctxNative.createImageData ??= CanvasRenderingContext2D.prototype.createImageData)
|
|
577
|
+
|
|
578
|
+
CanvasRenderingContext2D.prototype.createImageData = function createImageData() {
|
|
579
|
+
const count = arguments.length
|
|
580
|
+
// Past arguments.length, arguments[i] would read Object.prototype[i]: pass
|
|
581
|
+
// undefined there, as napi pads missing arguments.
|
|
582
|
+
return _createImageData.call(
|
|
583
|
+
this,
|
|
584
|
+
count > 0 ? arguments[0] : undefined,
|
|
585
|
+
count > 1 ? arguments[1] : undefined,
|
|
586
|
+
count > 2 ? arguments[2] : undefined,
|
|
587
|
+
count > 3 ? arguments[3] : undefined,
|
|
588
|
+
ARGUMENT_COUNTS[Math.min(count, 4)],
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
Object.defineProperty(CanvasRenderingContext2D.prototype.createImageData, 'length', { value: 1 })
|
|
592
|
+
|
|
593
|
+
// putImageData(imagedata, dx, dy) or (imagedata, dx, dy, dirtyX, dirtyY,
|
|
594
|
+
// dirtyWidth, dirtyHeight): as in Chrome, it needs 3 arguments; the native
|
|
595
|
+
// method picks the overload from the argument count (3 or 7).
|
|
596
|
+
const _putImageData = (ctxNative.putImageData ??= CanvasRenderingContext2D.prototype.putImageData)
|
|
597
|
+
|
|
598
|
+
CanvasRenderingContext2D.prototype.putImageData = function putImageData() {
|
|
599
|
+
const count = arguments.length
|
|
600
|
+
if (count < 3) {
|
|
601
|
+
argumentCountError(ctxBrand, this, 'putImageData', 'CanvasRenderingContext2D', 3, count)
|
|
602
|
+
}
|
|
603
|
+
// Past arguments.length, arguments[i] would read Object.prototype[i]: pass
|
|
604
|
+
// undefined there, as napi pads missing arguments.
|
|
605
|
+
return _putImageData.call(
|
|
606
|
+
this,
|
|
607
|
+
arguments[0],
|
|
608
|
+
arguments[1],
|
|
609
|
+
arguments[2],
|
|
610
|
+
count > 3 ? arguments[3] : undefined,
|
|
611
|
+
count > 4 ? arguments[4] : undefined,
|
|
612
|
+
count > 5 ? arguments[5] : undefined,
|
|
613
|
+
count > 6 ? arguments[6] : undefined,
|
|
614
|
+
ARGUMENT_COUNTS[Math.min(count, 7)],
|
|
615
|
+
)
|
|
616
|
+
}
|
|
617
|
+
Object.defineProperty(CanvasRenderingContext2D.prototype.putImageData, 'length', { value: 3 })
|
|
618
|
+
|
|
619
|
+
// createPattern(image, repetitionType): as in Chrome, both arguments are
|
|
620
|
+
// required, and the image is converted before the repetition, a
|
|
621
|
+
// [LegacyNullToEmptyString] DOMString (canvas_2d_recorder_context.idl), so
|
|
622
|
+
// null is '' and undefined is 'undefined'.
|
|
623
|
+
const _createPattern = (ctxNative.createPattern ??= CanvasRenderingContext2D.prototype.createPattern)
|
|
624
|
+
|
|
625
|
+
CanvasRenderingContext2D.prototype.createPattern = function createPattern(image, repetition) {
|
|
626
|
+
return _createPattern.call(this, image, repetition, ARGUMENT_COUNTS[Math.min(arguments.length, 4)])
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// addColorStop(offset, color): both arguments are required. CanvasGradient
|
|
630
|
+
// has no other native method to check the receiver first, so the native
|
|
631
|
+
// method itself reads the real count from the marker after them.
|
|
632
|
+
const gradientNative = nativeMethods(CanvasGradient.prototype)
|
|
633
|
+
const _addColorStop = (gradientNative.addColorStop ??= CanvasGradient.prototype.addColorStop)
|
|
634
|
+
CanvasGradient.prototype.addColorStop = function addColorStop(offset, color) {
|
|
635
|
+
return _addColorStop.call(this, offset, color, ARGUMENT_COUNTS[Math.min(arguments.length, 2)])
|
|
636
|
+
}
|
|
637
|
+
|
|
104
638
|
// Workaround for webpack bundling issue with drawImage
|
|
105
639
|
// Store the original drawImage method
|
|
106
|
-
const _drawImage = CanvasRenderingContext2D.prototype.drawImage
|
|
640
|
+
const _drawImage = (ctxNative.drawImage ??= CanvasRenderingContext2D.prototype.drawImage)
|
|
107
641
|
|
|
108
|
-
// Override drawImage to ensure proper type recognition in bundled environments
|
|
109
|
-
|
|
642
|
+
// Override drawImage to ensure proper type recognition in bundled environments.
|
|
643
|
+
// As in Chrome, it needs 3 arguments; the native method picks the overload
|
|
644
|
+
// from the argument count (3, 5 or 9).
|
|
645
|
+
CanvasRenderingContext2D.prototype.drawImage = function drawImage() {
|
|
646
|
+
if (arguments.length < 3) {
|
|
647
|
+
argumentCountError(ctxBrand, this, 'drawImage', 'CanvasRenderingContext2D', 3, arguments.length)
|
|
648
|
+
}
|
|
649
|
+
let image = arguments[0]
|
|
110
650
|
// If the image is a Canvas-like object but not recognized due to bundling,
|
|
111
651
|
// we need to ensure it's properly identified
|
|
112
652
|
if (image && typeof image === 'object') {
|
|
@@ -129,8 +669,22 @@ CanvasRenderingContext2D.prototype.drawImage = function drawImage(image, ...args
|
|
|
129
669
|
}
|
|
130
670
|
|
|
131
671
|
// Call the original drawImage with the potentially corrected image
|
|
132
|
-
|
|
672
|
+
const count = Math.min(arguments.length, 9)
|
|
673
|
+
return _drawImage.call(
|
|
674
|
+
this,
|
|
675
|
+
image,
|
|
676
|
+
arguments[1],
|
|
677
|
+
arguments[2],
|
|
678
|
+
count > 3 ? arguments[3] : undefined,
|
|
679
|
+
count > 4 ? arguments[4] : undefined,
|
|
680
|
+
count > 5 ? arguments[5] : undefined,
|
|
681
|
+
count > 6 ? arguments[6] : undefined,
|
|
682
|
+
count > 7 ? arguments[7] : undefined,
|
|
683
|
+
count > 8 ? arguments[8] : undefined,
|
|
684
|
+
argumentCount(count, arguments[count - 1]),
|
|
685
|
+
)
|
|
133
686
|
}
|
|
687
|
+
Object.defineProperty(CanvasRenderingContext2D.prototype.drawImage, 'length', { value: 3 })
|
|
134
688
|
|
|
135
689
|
function createCanvas(width, height, flag) {
|
|
136
690
|
const isSvgBackend = typeof flag !== 'undefined'
|
|
@@ -148,16 +702,15 @@ class Canvas {
|
|
|
148
702
|
}
|
|
149
703
|
|
|
150
704
|
if (!process.env.DISABLE_SYSTEM_FONTS_LOAD) {
|
|
151
|
-
|
|
705
|
+
// macOS: CoreText's installed fonts, user fonts included, listed at the
|
|
706
|
+
// first font lookup.
|
|
707
|
+
GlobalFonts.enableSystemFonts()
|
|
152
708
|
const platformName = platform()
|
|
153
709
|
const homedirPath = homedir()
|
|
154
710
|
switch (platformName) {
|
|
155
711
|
case 'win32':
|
|
156
712
|
GlobalFonts.loadFontsFromDir(join(homedirPath, 'AppData', 'Local', 'Microsoft', 'Windows', 'Fonts'))
|
|
157
713
|
break
|
|
158
|
-
case 'darwin':
|
|
159
|
-
GlobalFonts.loadFontsFromDir(join(homedirPath, 'Library', 'Fonts'))
|
|
160
|
-
break
|
|
161
714
|
case 'linux':
|
|
162
715
|
GlobalFonts.loadFontsFromDir(join('usr', 'local', 'share', 'fonts'))
|
|
163
716
|
GlobalFonts.loadFontsFromDir(join(homedirPath, '.fonts'))
|