skia-canvas 2.0.2 → 3.0.0
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/README.md +215 -40
- package/lib/browser.js +36 -160
- package/lib/classes/canvas.js +109 -54
- package/lib/classes/context.js +58 -37
- package/lib/classes/css.js +3 -4
- package/lib/classes/geometry.js +17 -4
- package/lib/classes/gui.js +195 -158
- package/lib/classes/imagery.js +170 -63
- package/lib/classes/neon.js +40 -8
- package/lib/classes/path.js +19 -16
- package/lib/classes/typography.js +7 -21
- package/lib/fonts/fonts.conf +25 -0
- package/lib/index.d.ts +164 -24
- package/lib/prebuild.mjs +131 -0
- package/package.json +34 -30
- package/CHANGELOG.md +0 -434
package/lib/classes/canvas.js
CHANGED
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
"use strict"
|
|
6
6
|
|
|
7
|
-
const {
|
|
8
|
-
{
|
|
7
|
+
const {fileURLToPath} = require('url'),
|
|
8
|
+
{RustClass, core, inspect, argc, REPR} = require('./neon'),
|
|
9
|
+
{Image, ImageData, pixelSize, getSharp} = require('./imagery'),
|
|
10
|
+
{Path2D} = require('./path'),
|
|
9
11
|
{toSkMatrix} = require('./geometry')
|
|
10
12
|
|
|
11
13
|
class Canvas extends RustClass{
|
|
12
14
|
#contexts
|
|
13
15
|
|
|
14
|
-
constructor(width, height){
|
|
15
|
-
super(Canvas).alloc()
|
|
16
|
+
constructor(width, height, {textContrast=0, textGamma=1.4, gpu=true}={}){
|
|
17
|
+
super(Canvas).alloc({textContrast, textGamma, gpu:!!gpu})
|
|
16
18
|
this.#contexts = []
|
|
17
19
|
Object.assign(this, {width, height})
|
|
18
20
|
}
|
|
@@ -28,13 +30,13 @@ class Canvas extends RustClass{
|
|
|
28
30
|
|
|
29
31
|
get width(){ return this.prop('width') }
|
|
30
32
|
set width(w){
|
|
31
|
-
this.prop('width',
|
|
33
|
+
this.prop('width', !Number.isNaN(+w) && +w>=0 ? w : 300)
|
|
32
34
|
if (this.#contexts[0]) this.getContext("2d").ƒ('resetSize', core(this))
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
get height(){ return this.prop('height') }
|
|
36
38
|
set height(h){
|
|
37
|
-
this.prop('height',
|
|
39
|
+
this.prop('height', !Number.isNaN(+h) && +h>=0 ? h : 150)
|
|
38
40
|
if (this.#contexts[0]) this.getContext("2d").ƒ('resetSize', core(this))
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -52,52 +54,70 @@ class Canvas extends RustClass{
|
|
|
52
54
|
return this.#contexts.slice().reverse()
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
get raw(){ return this.toBuffer("raw") }
|
|
55
58
|
get png(){ return this.toBuffer("png") }
|
|
56
59
|
get jpg(){ return this.toBuffer("jpg") }
|
|
57
60
|
get pdf(){ return this.toBuffer("pdf") }
|
|
58
61
|
get svg(){ return this.toBuffer("svg") }
|
|
59
62
|
get webp(){ return this.toBuffer("webp") }
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
// Warn about renamed methods but map them to the new names (for now)
|
|
65
|
+
saveAs(){ _deprecated('Canvas.saveAs()'); this.toFile(...arguments) }
|
|
66
|
+
saveAsSync(){ _deprecated('Canvas.saveAsSync()'); this.toFileSync(...arguments) }
|
|
67
|
+
toDataURLSync(){ _deprecated('Canvas.toDataURLSync()'); this.toURLSync(...arguments) }
|
|
68
|
+
|
|
69
|
+
toFile(filename, opts={}){
|
|
70
|
+
let {pages, padding, pattern, ...rest} = exportOptions(this, {filename}, opts),
|
|
71
|
+
args = [pages.map(core), pattern, padding, rest]
|
|
65
72
|
return this.ƒ("save", ...args)
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.ƒ("saveSync", pages.map(core), pattern, padding, {format, quality, density, outline, matte, msaa, colorType})
|
|
75
|
+
toFileSync(filename, opts={}){
|
|
76
|
+
let {pages, padding, pattern, ...rest} = exportOptions(this, {filename}, opts)
|
|
77
|
+
this.ƒ("saveSync", pages.map(core), pattern, padding, rest)
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
toBuffer(extension="png", opts={}){
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
args = [pages.map(core), {format, quality, density, outline, matte, msaa, colorType}];
|
|
78
|
-
return this.ƒ("toBuffer", ...args)
|
|
81
|
+
let {pages, ...rest} = exportOptions(this, {extension}, opts)
|
|
82
|
+
return this.ƒ("toBuffer", pages.map(core), rest)
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
toBufferSync(extension="png", opts={}){
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return this.ƒ("toBufferSync", pages.map(core), {format, quality, density, outline, matte, msaa, colorType})
|
|
86
|
+
let {pages, ...rest} = exportOptions(this, {extension}, opts)
|
|
87
|
+
return this.ƒ("toBufferSync", pages.map(core), rest)
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let {mime} = exportOptions(this.pages, {extension, ...opts}),
|
|
90
|
+
toURL(extension="png", opts={}){
|
|
91
|
+
let {mime} = exportOptions(this, {extension}, opts),
|
|
90
92
|
buffer = this.toBuffer(extension, opts);
|
|
91
93
|
return buffer.then(data => `data:${mime};base64,${data.toString('base64')}`)
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
let {mime} = exportOptions(this.pages, {extension, ...opts}),
|
|
96
|
+
toURLSync(extension="png", opts={}){
|
|
97
|
+
let {mime} = exportOptions(this, {extension}, opts),
|
|
97
98
|
buffer = this.toBufferSync(extension, opts);
|
|
98
99
|
return `data:${mime};base64,${buffer.toString('base64')}`
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
// Match the browser API in only accepting a single optional quality argument
|
|
103
|
+
toDataURL(extension="png", quality){
|
|
104
|
+
if (quality!==undefined && typeof quality!=='number'){
|
|
105
|
+
throw TypeError("Expected a number in the range 0–1 for `quality` (use toURL() for additional rendering options)")
|
|
106
|
+
}
|
|
107
|
+
return this.toURLSync(extension, {quality})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
toSharp({page, matte, msaa, density=1}={}){
|
|
111
|
+
const {Readable} = require('node:stream'),
|
|
112
|
+
sharp = getSharp(),
|
|
113
|
+
buffer = this.toBuffer("raw", {page, matte, density, msaa})
|
|
114
|
+
|
|
115
|
+
return Readable.from(
|
|
116
|
+
(async function * (){ yield buffer })()
|
|
117
|
+
).pipe(sharp({
|
|
118
|
+
raw: {width:this.width*density, height:this.height*density, channels:4}
|
|
119
|
+
}).withMetadata({density:density * 72}))
|
|
120
|
+
}
|
|
101
121
|
|
|
102
122
|
[REPR](depth, options) {
|
|
103
123
|
let {width, height, gpu, engine, pages} = this
|
|
@@ -114,8 +134,7 @@ class CanvasGradient extends RustClass{
|
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
addColorStop(offset, color){
|
|
117
|
-
|
|
118
|
-
else throw new Error("Color stop offsets must be between 0.0 and 1.0")
|
|
137
|
+
this.ƒ('addColorStop', ...arguments)
|
|
119
138
|
}
|
|
120
139
|
|
|
121
140
|
[REPR](depth, options) {
|
|
@@ -125,15 +144,16 @@ class CanvasGradient extends RustClass{
|
|
|
125
144
|
|
|
126
145
|
class CanvasPattern extends RustClass{
|
|
127
146
|
constructor(canvas, src, repeat){
|
|
147
|
+
repeat = [...arguments].slice(2)
|
|
128
148
|
super(CanvasPattern)
|
|
129
149
|
if (src instanceof Image){
|
|
130
150
|
let {width, height} = canvas
|
|
131
|
-
this.init('from_image', core(src), width, height, repeat)
|
|
151
|
+
this.init('from_image', core(src), width, height, ...repeat)
|
|
132
152
|
}else if (src instanceof ImageData){
|
|
133
|
-
this.init('from_image_data', src, repeat)
|
|
153
|
+
this.init('from_image_data', src, ...repeat)
|
|
134
154
|
}else if (src instanceof Canvas){
|
|
135
155
|
let ctx = src.getContext('2d')
|
|
136
|
-
this.init('from_canvas', core(ctx), repeat)
|
|
156
|
+
this.init('from_canvas', core(ctx), ...repeat)
|
|
137
157
|
}else{
|
|
138
158
|
throw new Error("CanvasPatterns require a source Image or a Canvas")
|
|
139
159
|
}
|
|
@@ -147,14 +167,18 @@ class CanvasPattern extends RustClass{
|
|
|
147
167
|
}
|
|
148
168
|
|
|
149
169
|
class CanvasTexture extends RustClass{
|
|
150
|
-
constructor(spacing, {path, line,
|
|
170
|
+
constructor(spacing, {path, color, angle, line, cap="butt", outline=false, offset=0}={}){
|
|
151
171
|
super(CanvasTexture)
|
|
152
|
-
|
|
153
|
-
let [
|
|
172
|
+
argc(arguments, 1)
|
|
173
|
+
let [x, y] = Array.isArray(offset) ? offset.concat(offset).slice(0, 2) : [offset, offset]
|
|
174
|
+
let [h, v] = Array.isArray(spacing) ? spacing.concat(spacing).slice(0, 2) : [spacing, spacing]
|
|
175
|
+
if (path!==undefined && !(path instanceof Path2D)){
|
|
176
|
+
throw TypeError("Expected a Path2D object for `path`")
|
|
177
|
+
}
|
|
154
178
|
path = core(path)
|
|
155
179
|
line = line != null ? line : (path ? 0 : 1)
|
|
156
180
|
angle = angle != null ? angle : (path ? 0 : -Math.PI / 4)
|
|
157
|
-
this.alloc(path, color, line, angle, h, v, x, y)
|
|
181
|
+
this.alloc(path, color, line, cap, angle, !!outline, h, v, x, y)
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
[REPR](depth, options) {
|
|
@@ -201,16 +225,33 @@ class Format{
|
|
|
201
225
|
|
|
202
226
|
const {basename, extname} = require('path')
|
|
203
227
|
|
|
204
|
-
function exportOptions(
|
|
228
|
+
function exportOptions(canvas, {filename='', extension=''}, opts){
|
|
229
|
+
// a single number will be interpreted as a quality setting
|
|
230
|
+
if (typeof opts=='number') opts = {quality:opts}
|
|
231
|
+
|
|
232
|
+
// unpack common export options
|
|
233
|
+
let {page, quality, matte, density, msaa, outline, downsample, colorType} = opts
|
|
234
|
+
|
|
235
|
+
// only allow format overrides in toFile()
|
|
236
|
+
let imageFormat = !!filename ? opts.format : undefined
|
|
237
|
+
|
|
238
|
+
if (filename instanceof URL){
|
|
239
|
+
if (filename.protocol=='file:') filename = fileURLToPath(filename)
|
|
240
|
+
else throw Error(`URLs must use 'file' protocol (got '${filename.protocol.replace(':', '')}')`)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ensure the canvas has a context (so it can at least generate an empty image)
|
|
244
|
+
if (!canvas.pages.length) canvas.getContext("2d")
|
|
245
|
+
|
|
205
246
|
var {fromMime, toMime, expected} = new Format(),
|
|
206
|
-
ext =
|
|
247
|
+
ext = imageFormat || extension.replace(/@\d+x$/i,'') || extname(filename),
|
|
207
248
|
format = fromMime(toMime(ext) || ext),
|
|
208
249
|
mime = toMime(format),
|
|
250
|
+
pages = canvas.pages,
|
|
209
251
|
pp = pages.length
|
|
210
252
|
|
|
211
253
|
if (!ext) throw new Error(`Cannot determine image format (use a filename extension or 'format' argument)`)
|
|
212
254
|
if (!format) throw new Error(`Unsupported file format "${ext}" (expected ${expected})`)
|
|
213
|
-
if (!pp) throw new RangeError(`Canvas has no associated contexts (try calling getContext or newPage first)`)
|
|
214
255
|
|
|
215
256
|
let padding, isSequence, pattern = filename.replace(/{(\d*)}/g, (_, width) => {
|
|
216
257
|
isSequence = true
|
|
@@ -233,11 +274,14 @@ function exportOptions(pages, {filename='', extension='', format, page, quality,
|
|
|
233
274
|
: isSequence || format=='pdf' ? pages
|
|
234
275
|
: pages.slice(-1) // default to the 'current' context
|
|
235
276
|
|
|
277
|
+
// inherit text settings from the canvas (since they can't be changed on a per-render basis due to glyph caching)
|
|
278
|
+
const {textContrast, textGamma} = canvas.engine
|
|
279
|
+
|
|
236
280
|
if (quality===undefined){
|
|
237
281
|
quality = 0.92
|
|
238
282
|
}else{
|
|
239
283
|
if (typeof quality!='number' || !isFinite(quality) || quality<0 || quality>1){
|
|
240
|
-
throw new TypeError("
|
|
284
|
+
throw new TypeError("Expected a number between 0.0–1.0 for `quality`")
|
|
241
285
|
}
|
|
242
286
|
}
|
|
243
287
|
|
|
@@ -245,20 +289,12 @@ function exportOptions(pages, {filename='', extension='', format, page, quality,
|
|
|
245
289
|
let m = (extension || basename(filename, ext)).match(/@(\d+)x$/i)
|
|
246
290
|
density = m ? parseInt(m[1], 10) : 1
|
|
247
291
|
}else if (typeof density!='number' || !Number.isInteger(density) || density<1){
|
|
248
|
-
throw new TypeError("
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (outline===undefined){
|
|
252
|
-
outline = true
|
|
253
|
-
}else if (format == 'svg'){
|
|
254
|
-
outline = !!outline
|
|
292
|
+
throw new TypeError("Expected a non-negative integer for `density`")
|
|
255
293
|
}
|
|
256
294
|
|
|
257
|
-
if (msaa===undefined) {
|
|
258
|
-
msaa = undefined //
|
|
259
|
-
}else if (!msaa){
|
|
260
|
-
msaa = 0 // null, false, etc. should all disable it
|
|
261
|
-
}else if (typeof msaa!='number' || !isFinite(msaa) || msaa<0){
|
|
295
|
+
if (msaa===undefined || msaa===true) {
|
|
296
|
+
msaa = undefined // use the default 4x msaa
|
|
297
|
+
}else if (!isFinite(+msaa) || +msaa<0){
|
|
262
298
|
throw new TypeError("The number of MSAA samples must be an integer ≥0")
|
|
263
299
|
}
|
|
264
300
|
|
|
@@ -266,7 +302,26 @@ function exportOptions(pages, {filename='', extension='', format, page, quality,
|
|
|
266
302
|
pixelSize(colorType) // throw an error if invalid
|
|
267
303
|
}
|
|
268
304
|
|
|
269
|
-
|
|
305
|
+
// default to false, otherwise detect truthy
|
|
306
|
+
downsample = !!downsample
|
|
307
|
+
outline = !!outline
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
filename, pattern, format, mime, pages, padding, quality, matte,
|
|
311
|
+
density, msaa, outline, textContrast, textGamma, downsample, colorType
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// emit a deprecation warning, once per API per process
|
|
316
|
+
let _warnings = {
|
|
317
|
+
"Canvas.saveAs()": "Canvas.toFile()",
|
|
318
|
+
"Canvas.saveAsSync()": "Canvas.toFileSync()",
|
|
319
|
+
"Canvas.toDataURLSync()": "Canvas.toURLSync() (see also Canvas.toDataURL() which is now synchronous)",
|
|
320
|
+
}
|
|
321
|
+
function _deprecated(oldAPI){
|
|
322
|
+
let newAPI = _warnings[oldAPI]
|
|
323
|
+
if (newAPI) console.error(`Deprecation warning: ${oldAPI} has been renamed to ${newAPI} and will stop working in a future release.`)
|
|
324
|
+
delete _warnings[oldAPI]
|
|
270
325
|
}
|
|
271
326
|
|
|
272
|
-
module.exports = {Canvas, CanvasGradient, CanvasPattern, CanvasTexture}
|
|
327
|
+
module.exports = {Canvas, CanvasGradient, CanvasPattern, CanvasTexture, getSharp}
|
package/lib/classes/context.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
"use strict"
|
|
6
6
|
|
|
7
|
-
const {RustClass, core, wrap, inspect, REPR} = require('./neon'),
|
|
7
|
+
const {RustClass, core, wrap, inspect, argc, REPR} = require('./neon'),
|
|
8
8
|
{Canvas, CanvasGradient, CanvasPattern, CanvasTexture} = require('./canvas'),
|
|
9
9
|
{fromSkMatrix, toSkMatrix} = require('./geometry'),
|
|
10
10
|
{Image, ImageData} = require('./imagery'),
|
|
@@ -63,7 +63,8 @@ class CanvasRenderingContext2D extends RustClass{
|
|
|
63
63
|
quadraticCurveTo(cpx, cpy, x, y){ this.ƒ('quadraticCurveTo', ...arguments) }
|
|
64
64
|
conicCurveTo(cpx, cpy, x, y, weight){ this.ƒ("conicCurveTo", ...arguments) }
|
|
65
65
|
closePath(){ this.ƒ('closePath') }
|
|
66
|
-
roundRect(x, y, w, h, r){
|
|
66
|
+
roundRect(x, y, w, h, r=0){
|
|
67
|
+
argc(arguments, 4, 5)
|
|
67
68
|
let radii = css.radii(r)
|
|
68
69
|
if (radii){
|
|
69
70
|
if (w < 0) radii = [radii[1], radii[0], radii[3], radii[2]]
|
|
@@ -75,32 +76,32 @@ class CanvasRenderingContext2D extends RustClass{
|
|
|
75
76
|
|
|
76
77
|
// -- using paths -----------------------------------------------------------
|
|
77
78
|
fill(path, rule){
|
|
78
|
-
if (path instanceof Path2D)
|
|
79
|
-
|
|
79
|
+
if (path instanceof Path2D) arguments[0] = core(path)
|
|
80
|
+
return this.ƒ('fill', ...arguments)
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
stroke(path
|
|
83
|
-
if (path instanceof Path2D)
|
|
84
|
-
|
|
83
|
+
stroke(path){
|
|
84
|
+
if (path instanceof Path2D) arguments[0] = core(path)
|
|
85
|
+
return this.ƒ('stroke', ...arguments)
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
clip(path, rule){
|
|
88
|
-
if (path instanceof Path2D)
|
|
89
|
-
|
|
89
|
+
if (path instanceof Path2D) arguments[0] = core(path)
|
|
90
|
+
return this.ƒ('clip', ...arguments)
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
isPointInPath(path, x, y){
|
|
93
|
-
|
|
94
|
-
return this.ƒ('isPointInPath', ...
|
|
93
|
+
isPointInPath(path, x, y, rule){
|
|
94
|
+
if (path instanceof Path2D) arguments[0] = core(path)
|
|
95
|
+
return this.ƒ('isPointInPath', ...arguments)
|
|
95
96
|
}
|
|
96
97
|
isPointInStroke(path, x, y){
|
|
97
|
-
|
|
98
|
-
return this.ƒ('isPointInStroke', ...
|
|
98
|
+
if (path instanceof Path2D) arguments[0] = core(path)
|
|
99
|
+
return this.ƒ('isPointInStroke', ...arguments)
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
// -- shaders ---------------------------------------------------------------
|
|
103
|
-
createPattern(image, repetition){ return new CanvasPattern(this.canvas,
|
|
104
|
+
createPattern(image, repetition){ return new CanvasPattern(this.canvas, ...arguments) }
|
|
104
105
|
createLinearGradient(x0, y0, x1, y1){
|
|
105
106
|
return new CanvasGradient("Linear", ...arguments)
|
|
106
107
|
}
|
|
@@ -112,7 +113,7 @@ class CanvasRenderingContext2D extends RustClass{
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
createTexture(spacing, options){
|
|
115
|
-
return new CanvasTexture(
|
|
116
|
+
return new CanvasTexture(...arguments)
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
// -- fill & stroke ---------------------------------------------------------
|
|
@@ -146,7 +147,7 @@ class CanvasRenderingContext2D extends RustClass{
|
|
|
146
147
|
|
|
147
148
|
// -- line style ------------------------------------------------------------
|
|
148
149
|
getLineDash(){ return this.ƒ("getLineDash") }
|
|
149
|
-
setLineDash(segments){ this.ƒ("setLineDash",
|
|
150
|
+
setLineDash(segments){ this.ƒ("setLineDash", ...arguments) }
|
|
150
151
|
get lineCap(){ return this.prop("lineCap") }
|
|
151
152
|
set lineCap(style){ this.prop("lineCap", style) }
|
|
152
153
|
get lineDashFit(){ return this.prop("lineDashFit") }
|
|
@@ -167,14 +168,34 @@ class CanvasRenderingContext2D extends RustClass{
|
|
|
167
168
|
set imageSmoothingEnabled(flag){ this.prop("imageSmoothingEnabled", !!flag)}
|
|
168
169
|
get imageSmoothingQuality(){ return this.prop("imageSmoothingQuality")}
|
|
169
170
|
set imageSmoothingQuality(level){ this.prop("imageSmoothingQuality", level)}
|
|
170
|
-
|
|
171
|
-
createImageData(width, height, settings){
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
|
|
172
|
+
createImageData(width, height, settings){
|
|
173
|
+
argc(arguments, 2, 3)
|
|
174
|
+
return new ImageData(width, height, settings)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getImageData(x, y, width, height, {colorType='rgba', colorSpace='srgb', density=1, matte, msaa}={}){
|
|
178
|
+
argc(arguments, 4, 5)
|
|
179
|
+
|
|
180
|
+
if (typeof density!='number' || !Number.isInteger(density) || density<1){
|
|
181
|
+
throw new TypeError("Expected a non-negative integer for `density`")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (msaa===undefined || msaa===true) {
|
|
185
|
+
msaa = undefined // use the default 4x msaa
|
|
186
|
+
}else if (!isFinite(+msaa) || +msaa<0){
|
|
187
|
+
throw new TypeError("The number of MSAA samples must be an integer ≥0")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let opts = {colorType, colorSpace, density, matte, msaa},
|
|
191
|
+
buffer = this.ƒ('getImageData', x, y, width, height, opts, core(this.canvas));
|
|
192
|
+
return new ImageData(buffer, width*density, height*density, {colorType, colorSpace})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
putImageData(imageData, ...coords){
|
|
196
|
+
argc(arguments, 3, 7)
|
|
197
|
+
if (!(imageData instanceof ImageData)) throw TypeError("Expected an ImageData as 1st arg")
|
|
198
|
+
this.ƒ('putImageData', imageData, ...coords)
|
|
178
199
|
}
|
|
179
200
|
|
|
180
201
|
drawImage(image, ...coords){
|
|
@@ -182,13 +203,14 @@ class CanvasRenderingContext2D extends RustClass{
|
|
|
182
203
|
this.ƒ('drawImage', core(image.getContext('2d')), ...coords)
|
|
183
204
|
}else if (image instanceof Image){
|
|
184
205
|
if (image.complete) this.ƒ('drawImage', core(image), ...coords)
|
|
185
|
-
else throw
|
|
206
|
+
else throw Error("Image has not completed loading: listen for `load` event or await `decode()` first")
|
|
186
207
|
}else if (image instanceof ImageData){
|
|
187
208
|
this.ƒ('drawImage', image, ...coords)
|
|
188
209
|
}else if (image instanceof Promise) {
|
|
189
|
-
throw
|
|
210
|
+
throw Error("Promise has not yet resolved: `await` image loading before drawing")
|
|
190
211
|
}else{
|
|
191
|
-
|
|
212
|
+
let nonimage = inspect(image, {depth:1})
|
|
213
|
+
throw Error(`Expected an Image or a Canvas argument (got: ${nonimage})`)
|
|
192
214
|
}
|
|
193
215
|
}
|
|
194
216
|
|
|
@@ -217,21 +239,20 @@ class CanvasRenderingContext2D extends RustClass{
|
|
|
217
239
|
set wordSpacing(str){ this.prop('wordSpacing', css.spacing(str)) }
|
|
218
240
|
|
|
219
241
|
measureText(text, maxWidth){
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return new TextMetrics(metrics, lines)
|
|
242
|
+
let metrics = JSON.parse(this.ƒ('measureText', toString(text), maxWidth))
|
|
243
|
+
return new TextMetrics(metrics)
|
|
223
244
|
}
|
|
224
245
|
|
|
225
|
-
fillText(text,
|
|
226
|
-
this.ƒ('fillText', toString(text),
|
|
246
|
+
fillText(text, ...geom){
|
|
247
|
+
this.ƒ('fillText', toString(text), ...geom)
|
|
227
248
|
}
|
|
228
249
|
|
|
229
|
-
strokeText(text,
|
|
230
|
-
this.ƒ('strokeText', toString(text),
|
|
250
|
+
strokeText(text, ...geom){
|
|
251
|
+
this.ƒ('strokeText', toString(text), ...geom)
|
|
231
252
|
}
|
|
232
253
|
|
|
233
|
-
outlineText(text,
|
|
234
|
-
let path = this.ƒ('outlineText', toString(text),
|
|
254
|
+
outlineText(text, ...geom){
|
|
255
|
+
let path = this.ƒ('outlineText', toString(text), ...geom)
|
|
235
256
|
return path ? wrap(Path2D, path) : null
|
|
236
257
|
}
|
|
237
258
|
|
package/lib/classes/css.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
// https://www.w3.org/TR/css-fonts-3/#font-size-prop
|
|
10
10
|
|
|
11
11
|
var splitBy = require('string-split-by'),
|
|
12
|
-
{DOMPoint} = require('./geometry'),
|
|
13
12
|
m, cache = {font:{}, variant:{}};
|
|
14
13
|
|
|
15
14
|
const styleRE = /^(normal|italic|oblique)$/,
|
|
@@ -225,20 +224,20 @@ function parseFit(mode){
|
|
|
225
224
|
|
|
226
225
|
function parseCornerRadii(r){
|
|
227
226
|
r = [r].flat()
|
|
228
|
-
.map(n => n instanceof DOMPoint ? n : new DOMPoint(n, n))
|
|
229
227
|
.slice(0, 4)
|
|
228
|
+
.map(n => n && Object.hasOwn(n, 'x') && Object.hasOwn(n, 'y') ? n : {x:n, y:n})
|
|
230
229
|
|
|
231
230
|
if (r.some(pt => !Number.isFinite(pt.x) || !Number.isFinite(pt.y))){
|
|
232
231
|
return null // silently abort
|
|
233
232
|
}else if (r.some(pt => pt.x < 0 || pt.y < 0)){
|
|
234
|
-
throw
|
|
233
|
+
throw RangeError("Corner radius cannot be negative")
|
|
235
234
|
}
|
|
236
235
|
|
|
237
236
|
return r.length == 1 ? [r[0], r[0], r[0], r[0]]
|
|
238
237
|
: r.length == 2 ? [r[0], r[1], r[0], r[1]]
|
|
239
238
|
: r.length == 3 ? [r[0], r[1], r[2], r[1]]
|
|
240
239
|
: r.length == 4 ? [r[0], r[1], r[2], r[3]]
|
|
241
|
-
: [0, 0, 0, 0].map(n =>
|
|
240
|
+
: [0, 0, 0, 0].map(n => ({x:n, y:n}))
|
|
242
241
|
}
|
|
243
242
|
|
|
244
243
|
// -- Image Filters -----------------------------------------------------------------------
|
package/lib/classes/geometry.js
CHANGED
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
const {inspect} = require('util')
|
|
8
8
|
|
|
9
|
+
const isPlainObject = o => (
|
|
10
|
+
o !== null &&
|
|
11
|
+
typeof o === "object" &&
|
|
12
|
+
!(o instanceof DOMMatrix) &&
|
|
13
|
+
!Array.isArray(o) &&
|
|
14
|
+
!ArrayBuffer.isView(o)
|
|
15
|
+
)
|
|
16
|
+
|
|
9
17
|
/*
|
|
10
18
|
* vendored in order to fix its dependence on the window global [@samizdatco 2020/08/04]
|
|
11
19
|
* removed SVGMatrix references that were guaranteed to be undefined on node [@mpaperno 2024/10/20]
|
|
@@ -331,8 +339,10 @@ class DOMMatrix {
|
|
|
331
339
|
init.m31, init.m32, init.m33, init.m34,
|
|
332
340
|
init.m41, init.m42, init.m43, init.m44,
|
|
333
341
|
]);
|
|
334
|
-
if (DOMMatrix.isMatrix3(init))
|
|
335
|
-
|
|
342
|
+
if (DOMMatrix.isMatrix3(init) || isPlainObject(init)){
|
|
343
|
+
let {a=1, b=0, c=0, d=1, e=0, f=0} = init
|
|
344
|
+
return new DOMMatrix([a, b, c, d, e, f]);
|
|
345
|
+
}
|
|
336
346
|
throw new TypeError(`Expected DOMMatrix, got: '${init}'`);
|
|
337
347
|
}
|
|
338
348
|
|
|
@@ -378,7 +388,7 @@ class DOMMatrix {
|
|
|
378
388
|
// (Float64Array) => void
|
|
379
389
|
constructor(init) {
|
|
380
390
|
|
|
381
|
-
if (
|
|
391
|
+
if (init instanceof DOMMatrix || isPlainObject(init))
|
|
382
392
|
return DOMMatrix.fromMatrix(init);
|
|
383
393
|
|
|
384
394
|
if (arguments.length > 1)
|
|
@@ -437,7 +447,7 @@ class DOMMatrix {
|
|
|
437
447
|
setNumber3D(this, M44, init[i]);
|
|
438
448
|
}
|
|
439
449
|
else if (init !== undefined) {
|
|
440
|
-
throw new TypeError("Expected string or
|
|
450
|
+
throw new TypeError("Expected string, array, or matrix object.");
|
|
441
451
|
}
|
|
442
452
|
}
|
|
443
453
|
|
|
@@ -861,6 +871,9 @@ for (let propertyName of [
|
|
|
861
871
|
//
|
|
862
872
|
|
|
863
873
|
function toSkMatrix() {
|
|
874
|
+
if (arguments.length != 1 && arguments.length < 6){
|
|
875
|
+
throw new TypeError("not enough arguments")
|
|
876
|
+
}
|
|
864
877
|
try {
|
|
865
878
|
const m = new DOMMatrix(...arguments);
|
|
866
879
|
return [m.a, m.c, m.e, m.b, m.d, m.f, m.m14, m.m24, m.m44];
|