skia-canvas 0.9.27 → 0.9.30

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/CHANGELOG.md ADDED
@@ -0,0 +1,243 @@
1
+ # Changelog
2
+
3
+ <!-- ## 🥚 ⟩ [Unreleased] -->
4
+
5
+ ## 📦 ⟩ [v0.9.30] ⟩ Jun 7, 2022
6
+
7
+ ### New Features
8
+ - Enhacements to the shared **FontLibrary** object:
9
+ - Added a [`reset()`][FontLibrary.reset] method to FontLibrary which uninstalls any fonts that had been dynamically installed via `FontLibrary.use()`
10
+ - The [`use()`][FontLibrary.use] method now checks for previously installed fonts with the same family name (or alias) and will replace them with the newly added font
11
+ - Added pre-compiled binaries for Alpine Linux on arm64
12
+
13
+ ### Bugfixes
14
+ - Calling `clip` with an empty path (or one that does not intersect the current clipping mask) will now prevent drawing altogether
15
+ - Transformation (`translate`, `rotate`, etc.) and line-drawing methods (`moveTo`, `lineTo`, `ellipse`, etc.) are now silently ignored if called with `NaN`, `Infinity`, or non-**Number** values in the arguments rather than throwing an error
16
+ - applies to both the Context and Path2D versions of the drawing methods
17
+ - a **TypeError** is thrown only if the number of arguments is too low (mirroring browser behavior)
18
+ - [`conicCurveTo()`][conicCurveTo] now correctly reflects the canvas's transform state
19
+ - The browser-based version of [`loadImage()`][loadImage] now returns a **Promise** that correctly resolves to an **Image** object
20
+ - SVG exports no longer have an invisible, canvas-sized `<rect/>` as their first element
21
+ - Fixed an incompatibility on Alpine between the version of libstdc++ present on the `node:alpine` docker images and the version used when building the precompiled binaries
22
+
23
+ ### Misc. Improvements
24
+ - Upgraded Skia to milestone 101
25
+
26
+ [conicCurveTo]: https://github.com/samizdatco/skia-canvas#coniccurvetocpx-cpy-x-y-weight
27
+ [FontLibrary.reset]: https://github.com/samizdatco/skia-canvas#reset
28
+ [FontLibrary.use]: https://github.com/samizdatco/skia-canvas#usefamilyname-fontpaths
29
+ [loadImage]: https://github.com/samizdatco/skia-canvas/#loadimage
30
+
31
+ ## 📦 ⟩ [v0.9.29] ⟩ Feb 7, 2022
32
+
33
+ ### New Features
34
+ - PDF exports now support the optional [`matte`][matte] argument.
35
+
36
+ ### Breaking Changes
37
+ - When the [`drawImage()`][mdn_drawImage] function is passed a **Canvas** object as its image source it will now rasterize the canvas before drawing. The prior behavior (in which it is drawn as a vector graphic) can now be accessed through the new [`drawCanvas()`][drawCanvas] method which supports the same numerical arguments as `drawImage` but requires that its first argument be a **Canvas**.
38
+
39
+ ### Bugfixes
40
+ - Regions erased using [`clearRect()`][mdn_clearRect] are now properly antialiased
41
+ - The [`clip()`][mdn_clip] method now interprets the current translate/scale/rotate state correctly when combining clipping masks
42
+
43
+ ### Misc. Improvements
44
+ - Upgraded Skia to milestone 97
45
+
46
+ [drawCanvas]: https://github.com/samizdatco/skia-canvas#drawcanvascanvas-x-y-
47
+ [mdn_clip]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clip
48
+ [mdn_clearRect]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clearRect
49
+
50
+ ## 📦 ⟩ [v0.9.28] ⟩ Jan 12, 2022
51
+
52
+ ### New Features
53
+ - Added TypeScript definitions for extensions to the DOM spec (contributed by [@cprecioso](https://github.com/cprecioso))
54
+ - Added 3D-perspective transformations via the new [createProjection()](https://github.com/samizdatco/skia-canvas#createprojectionquad-basis) context method
55
+ - Colors can now use the [hwb()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb()) model
56
+
57
+ ### Breaking Changes
58
+ - The **Canvas** [`.async`](https://github.com/samizdatco/skia-canvas#async) property has been **deprecated** and will be removed in a future release.
59
+ - The `saveAs`, `toBuffer`, and `toDataURL` methods will now be async-only (likewise the [shorthand properties](https://github.com/samizdatco/skia-canvas#pdf-svg-jpg-and-png)).
60
+ - Use their synchronous counterparts (`saveAsSync`, `toBufferSync`, and `toDataURLSync`) if you want to block execution while exporting images.
61
+ - The [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData/ImageData) constructor now orders its arguments properly: the optional buffer/array argument now comes first
62
+
63
+ ### Bugfixes
64
+ - Fixed a stack overflow that was occurring when images became too deeply nested for the default deallocator to handle (primarily due to many thousands of image exports from the same canvas)
65
+ - The `source-in`, `source-out`, `destination-atop`, and `copy` composite operations now work correctly for paths rather than rendering shapes without color (contributed by [@meihuanyu](https://github.com/meihuanyu))
66
+ - Shape primitives now behave consistently with browsers when being added to a non-empty path:
67
+ - `rect()` now issues an initial `moveTo` rather than extending the path, then leaves the ‘current’ point in its upper left corner
68
+ - `ellipse()` extends the current path rather than implicitly closing it (contributed by [@meihuanyu](https://github.com/meihuanyu))
69
+ - `arc()` also extends the current path rather than closing it
70
+
71
+ ### Misc. Improvements
72
+ - Upgraded Skia to milestone 96
73
+ - Added workflow for creating docker build environments
74
+
75
+
76
+ ## 📦 ⟩ [v0.9.27] ⟩ Oct 23, 2021
77
+
78
+ ### New Features
79
+ - Added pre-compiled binaries for Alpine Linux using the [musl](https://musl.libc.org) C library
80
+
81
+
82
+ ## 📦 ⟩ [v0.9.26] ⟩ Oct 18, 2021
83
+
84
+ ### New Features
85
+ - Added pre-compiled binaries for 32-bit and 64-bit ARM on Linux (a.k.a. Raspberry Pi)
86
+
87
+ ### Bugfixes
88
+ - Windows text rendering has been restored after failing due to changes involving the `icudtl.dat` file
89
+ - `FontLibrary.use` now reports an error if the specified font file doesn't exist
90
+ - Fixed a crash that could result from calling `measureText` with various unicode escapes
91
+
92
+ ### Misc. Improvements
93
+ - Upgraded Skia to milestone 94
94
+ - Now embedding a more recent version of the FreeType library on Linux with support for more font formats
95
+
96
+
97
+ ## 📦 ⟩ [v0.9.25] ⟩ Aug 22, 2021
98
+
99
+ ### Bugfixes
100
+ - Improved image scaling when a larger image is being shrunk down to a smaller size via [`drawImage()`][mdn_drawImage]
101
+ - modified [`imageSmoothingQuality`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality) settings to provide a more meaningful range across `low`, `medium`, and `high`
102
+ - [`measureText()`](https://github.com/samizdatco/skia-canvas#measuretextstr-width) now returns correct metrics regardless of current `textAlign` setting
103
+ - Rolled back `icudtl.dat` changes on Windows (which suppressed the misleading warning message but required running as Administrator)
104
+
105
+ ### Misc. Improvements
106
+ - Now using [Neon](https://github.com/neon-bindings/neon) v0.9 (with enhanced async event scheduling)
107
+
108
+ [mdn_drawImage]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
109
+
110
+ ## 📦 ⟩ [v0.9.24] ⟩ Aug 18, 2021
111
+
112
+ ### New Features
113
+ - **Path2D** objects now have a read/write [`d`](https://github.com/samizdatco/skia-canvas/#d) property with an [SVG representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#path_commands) of the path’s contours and an [`unwind()`](https://github.com/samizdatco/skia-canvas/#unwind) method for converting from even-odd to non-zero winding rules
114
+ - The [`createTexture()`](https://github.com/samizdatco/skia-canvas#createtexturespacing-path-line-color-angle-offset0) context method returns **CanvasTexture** objects which can be assigned to `fillStyle` or `strokeStyle`
115
+ - Textures draw either a parallel-lines pattern or one derived from the provided **Path2D** object and positioning parameters
116
+ - The marker used when `setLineDash` is active can now be customized by assigning a **Path2D** to the context’s [`lineDashMarker`](https://github.com/samizdatco/skia-canvas#linedashmarker) property (default dashing can be restored by assigning `null`)
117
+ - The marker’s orientation & shape relative to the path being stroked can be controlled by the [`lineDashFit`](https://github.com/samizdatco/skia-canvas#linedashfit) property which defaults to `"turn"` but can be set to `"move"` (which preserves orientation) or `"follow"` (which distorts the marker’s shape to match the contour)
118
+
119
+ ### Bugfixes
120
+
121
+ - Removed use of the `??` operator which is unavailable prior to Node 14
122
+ - Prevented a spurious warning on windows incorrectly claiming that the `icudtl.dat` file could not be found
123
+
124
+ ### Misc. Improvements
125
+
126
+ - The **Path2D** [`simplify()`](https://github.com/samizdatco/skia-canvas/#simplifyrulenonzero) method now takes an optional fill-rule argument
127
+ - Added support for versions of macOS starting with 10.13 (High Sierra)
128
+
129
+
130
+ ## 📦 ⟩ [v0.9.23] ⟩ Jul 12, 2021
131
+
132
+ ### New Features
133
+
134
+ - [Conic béziers][conic_bezier] can now be drawn to the context or a Path2D with the [`conicCurveTo()`][conic_curveto] method
135
+ - Text can be converted to a Path2D using the context’s new [`outlineText()`][outline_text] method
136
+ - Path2D objects can now report back on their internal geometry with:
137
+ - the [`edges`][edges] property which contains an array of line-drawing commands describing the path’s individual contours
138
+ - the [`contains()`][contains] method which tests whether a given point is on/within the path
139
+ - the [`points()`][points] method which returns an array of `[x, y]` pairs at the requested spacing along the curve’s periphery
140
+ - A modified copy of a source Path2D can now be created using:
141
+ - [`offset()`][offset] or [`transform()`][transform] to shift position or apply a DOMMatrix respectively
142
+ - [`jitter()`][jitter] to break the path into smaller sections and apply random noise to the segments’ positions
143
+ - [`round()`][round] to round off every sharp corner in a path to a particular radius
144
+ - [`trim()`][trim] to select a percentage-based subsection of the path
145
+ - Two similar paths can be ‘tweened’ into a proportional combination of their coordinates using the [`interpolate()`][interpolate] method
146
+
147
+ ### Bugfixes
148
+
149
+ - Passing a Path2D argument to the `fill()` or `stroke()` method no longer disturbs the context’s ‘current’ path (if one has been created using `beginPath()`)
150
+ - The `filter` property will now accept percentage values greater than 999%
151
+
152
+ ### Misc. Improvements
153
+
154
+ - The `newPage()` and `saveAs()` methods now work in the browser, including the ability to save image sequences to a zip archive. The browser’s canvas is still doing all the drawing however, so file export formats will be limited to PNG and JPEG and none of the other Skia-specific extensions will be available.
155
+ - The file-export methods now accept a [`matte`][matte] value in their options object which can be used to set the background color for any portions of the canvas that were left semi-transparent
156
+ - Canvas dimensions are no longer rounded-off to integer values (at least until a bitmap needs to be generated for export)
157
+ - Linux builds will now run on some older systems going back to glibc 2.24
158
+
159
+ [conic_bezier]: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/curves/beziers#the-conic-bézier-curve
160
+ [conic_curveto]: https://github.com/samizdatco/skia-canvas#coniccurvetocpx-cpy-x-y-weight
161
+ [outline_text]: https://github.com/samizdatco/skia-canvas#outlinetextstr
162
+ [matte]: https://github.com/samizdatco/skia-canvas#matte
163
+
164
+ [edges]: https://github.com/samizdatco/skia-canvas#edges
165
+ [contains]: https://github.com/samizdatco/skia-canvas#containsx-y
166
+ [points]: https://github.com/samizdatco/skia-canvas#pointsstep1
167
+ [offset]: https://github.com/samizdatco/skia-canvas#offsetdx-dy
168
+ [transform]: https://github.com/samizdatco/skia-canvas#transformmatrix-or-transforma-b-c-d-e-f
169
+
170
+ [interpolate]: https://github.com/samizdatco/skia-canvas#interpolateotherpath-weight
171
+ [jitter]: https://github.com/samizdatco/skia-canvas#jittersegmentlength-amount-seed0
172
+ [round]: https://github.com/samizdatco/skia-canvas#roundradius
173
+ [simplify]: https://github.com/samizdatco/skia-canvas#simplify
174
+ [trim]: https://github.com/samizdatco/skia-canvas#trimstart-end-inverted
175
+
176
+
177
+ ## 📦 ⟩ [v0.9.22] ⟩ Jun 09, 2021
178
+
179
+ ### New Features
180
+
181
+ - Rasterization and file i/o are now handled asynchronously in a background thread. See the discussion of Canvas’s new [`async`](https://github.com/samizdatco/skia-canvas#async) property for details.
182
+ - Output files can now be generated at pixel-ratios > 1 for High-DPI screens. `SaveAs` and the other canvas output functions all accept an optional [`density`](https://github.com/samizdatco/skia-canvas#density) argument which is an integer ≥1 and will upscale the image accordingly. The density can also be passed using the `filename` argument by ending the name with an ‘@’ suffix like `some-image@2x.png`.
183
+ - SVG exports can optionally convert text to paths by setting the [`outline`](https://github.com/samizdatco/skia-canvas#outline) argument to `true`.
184
+
185
+ ### Breaking Changes
186
+
187
+ - The canvas functions dealing with rasterization (`toBuffer`, `toDataURL`, `png`, `jpg`, `pdf`, and `svg`) and file i/o (`saveAs`) are now asynchronous and return `Promise` objects. The old, synchronous behavior is still available on a canvas-by-canvas basis by setting its `async` property to `false`.
188
+ - The optional `quality` argument accepted by the output methods is now a float in the range 0–1 rather than an integer from 0–100. This is consistent with the [encoderOptions](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL) arg in the spec. Quality now defaults to 0.92 (again, as per the spec) rather than lossless.
189
+
190
+ ### Bugfixes
191
+
192
+ - `measureText` was reporting zero when asked to measure a string that was entirely made of whitespace. This is still the case for ‘blank‘ lines when `textWrap` is set to `true` but in the default, single-line mode the metrics will now report the width of the whitespace.
193
+ - Changed the way text rendering was staged so that SVG exports didn’t *entirely omit(!)* text from their output. As a result, `Context2D`s now use an external `Typesetter` struct to manage layout and rendering.
194
+
195
+
196
+ ## 📦 ⟩ [v0.9.21] ⟩ May 22, 2021
197
+
198
+ ### New Features
199
+ - Now runs on Windows and Apple Silicon Macs.
200
+ - Precompiled binaries support Node 10, 12, 14+.
201
+ - Image objects can be initialized from PNG, JPEG, GIF, BMP, or ICO data.
202
+ - Path2D objects can now be combined using [boolean operators](https://github.com/samizdatco/skia-canvas/#complement-difference-intersect-union-and-xor) and can measure their own [bounding boxes](https://github.com/samizdatco/skia-canvas/#bounds).
203
+ - Context objects now support [`createConicGradient()`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createConicGradient).
204
+ - Image objects now return a promise from their [`decode()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) method allowing for async loading without the [`loadImage`](https://github.com/samizdatco/skia-canvas/#loadimage) helper.
205
+
206
+ ### Bugfixes
207
+ - Calling `drawImage` with a `Canvas` object as the argument now uses a Skia `Pict` rather than a `Drawable` as the interchange format, meaning it can actually respect the canvas's current `globalAlpha` and `globalCompositeOperation` state (fixed #6).
208
+ - Improved some spurious error messages when trying to generate a graphics file from a canvas whose width and/or height was set to zero (fixed #5).
209
+ - `CanvasPattern`s now respect the `imageSmoothingEnabled` setting
210
+ - The `counterclockwise` arg to `ellipse` and `arc` is now correctly treated as optional.
211
+
212
+ ### Misc. Improvements
213
+ - Made the `console.log` representations of the canvas-related objects friendlier.
214
+ - Added new test suites for `Path2D`, `Image`, and `Canvas`’s format support.
215
+ - Created [workflows](https://github.com/samizdatco/skia-canvas/tree/master/.github/workflows) to automate precompiled binary builds, testing, and npm package updating.
216
+
217
+
218
+ ## 📦 ⟩ [v0.9.20] ⟩ Mar 27, 2021
219
+
220
+ ### Bugfixes
221
+ - The `loadImage` helper can now handle `Buffer` arguments
222
+
223
+ ### Misc. Improvements
224
+ - Improved documentation of compilation steps and use of line height with `ctx.font`
225
+
226
+
227
+ ## 📦 ⟩ [v0.9.19] ⟩ Aug 30, 2020
228
+
229
+ **Initial public release** 🎉
230
+
231
+ [unreleased]: https://github.com/samizdatco/skia-canvas/compare/v0.9.30...HEAD
232
+ [v0.9.30]: https://github.com/samizdatco/skia-canvas/compare/v0.9.29...v0.9.30
233
+ [v0.9.29]: https://github.com/samizdatco/skia-canvas/compare/v0.9.28...v0.9.29
234
+ [v0.9.28]: https://github.com/samizdatco/skia-canvas/compare/v0.9.27...v0.9.28
235
+ [v0.9.27]: https://github.com/samizdatco/skia-canvas/compare/v0.9.26...v0.9.27
236
+ [v0.9.26]: https://github.com/samizdatco/skia-canvas/compare/v0.9.25...v0.9.26
237
+ [v0.9.25]: https://github.com/samizdatco/skia-canvas/compare/v0.9.24...v0.9.25
238
+ [v0.9.24]: https://github.com/samizdatco/skia-canvas/compare/v0.9.23...v0.9.24
239
+ [v0.9.23]: https://github.com/samizdatco/skia-canvas/compare/v0.9.22...v0.9.23
240
+ [v0.9.22]: https://github.com/samizdatco/skia-canvas/compare/v0.9.21...v0.9.22
241
+ [v0.9.21]: https://github.com/samizdatco/skia-canvas/compare/v0.9.20...v0.9.21
242
+ [v0.9.20]: https://github.com/samizdatco/skia-canvas/compare/v0.9.19...v0.9.20
243
+ [v0.9.19]: https://github.com/samizdatco/skia-canvas/compare/v0.9.15...v0.9.19
package/README.md CHANGED
@@ -9,9 +9,10 @@ In particular, Skia Canvas:
9
9
  - is fast and compact since all the heavy lifting is done by native code written in Rust and C++
10
10
  - can generate output in both raster (JPEG & PNG) and vector (PDF & SVG) image formats
11
11
  - can save images to [files][saveAs], return them as [Buffers][toBuffer], or encode [dataURL][toDataURL_ext] strings
12
- - uses native threads and [channels](https://docs.rs/neon/0.9.0/neon/event/struct.Channel.html) for asynchronous rendering and file I/O
12
+ - uses native threads and the Node [worker pool](https://github.com/neon-bindings/rfcs/pull/35) for asynchronous rendering and file I/O
13
13
  - can create [multiple ‘pages’][newPage] on a given canvas and then [output][saveAs] them as a single, multi-page PDF or an image-sequence saved to multiple files
14
14
  - can [simplify][p2d_simplify], [blunt][p2d_round], [combine][bool-ops], [excerpt][p2d_trim], and [atomize][p2d_points] bézier paths using [efficient](https://www.youtube.com/watch?v=OmfliNQsk88) boolean operations or point-by-point [interpolation][p2d_interpolate]
15
+ - can apply [3D perspective][createProjection()] transformations in addition to [scaling][scale()], [rotation][rotate()], and [translation][translate()]
15
16
  - can fill shapes with vector-based [Textures][createTexture()] in addition to bitmap-based [Patterns][createPattern()] and supports line-drawing with custom [markers][lineDashMarker]
16
17
  - fully supports the [CSS filter effects][filter] image processing operators
17
18
  - offers rich typographic control including:
@@ -51,9 +52,9 @@ Nearly everything you need is statically linked into the library. A notable exce
51
52
 
52
53
  ### Running in Docker
53
54
 
54
- The library is compatible with Linux systems using [glibc](https://www.gnu.org/software/libc/) 2.24 or later as well as Alpine Linux (x64) and the [musl](https://musl.libc.org) C library it favors. In both cases, Fontconfig must be installed on the system for `skia-canvas` to operate correctly.
55
+ The library is compatible with Linux systems using [glibc](https://www.gnu.org/software/libc/) 2.24 or later as well as Alpine Linux (x64 & arm64) and the [musl](https://musl.libc.org) C library it favors. In both cases, Fontconfig must be installed on the system for `skia-canvas` to operate correctly.
55
56
 
56
- If you are setting up a [Dockerfile](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/) that uses [`node`](https://hub.docker.com/_/node) as its basis, the simplest approach is to set your `FROM` image to one of the (Debian-derived) defaults like `node:16`, `node:14`, `node:12`, `node:buster`, `node:stretch`, or simply:
57
+ If you are setting up a [Dockerfile](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/) that uses [`node`](https://hub.docker.com/_/node) as its basis, the simplest approach is to set your `FROM` image to one of the (Debian-derived) defaults like `node:16`, `node:14`, `node:12`, `node:bullseye`, `node:buster`, or simply:
57
58
  ```dockerfile
58
59
  FROM node
59
60
  ```
@@ -122,15 +123,14 @@ async function render(){
122
123
  // save the graphic...
123
124
  await canvas.saveAs("pilcrow.png")
124
125
  // ...or use a shorthand for canvas.toBuffer("png")
125
- fs.writeFileSync("pilcrow.png", await canvas.png)
126
+ let pngData = await canvas.png
126
127
  // ...or embed it in a string
127
128
  console.log(`<img src="${await canvas.toDataURL("png")}">`)
128
129
  }
129
130
  render()
130
131
 
131
- // ...or switch into synchronous mode and save from the main thread
132
- canvas.async = false
133
- canvas.saveAs("pilcrow.png")
132
+ // ...or save the file synchronously from the main thread
133
+ canvas.saveAsSync("pilcrow.png")
134
134
  ```
135
135
 
136
136
 
@@ -163,22 +163,22 @@ The Canvas object is a stand-in for the HTML `<canvas>` element. It defines imag
163
163
 
164
164
  | Image Dimensions | Rendering Contexts | Output |
165
165
  | -- | -- | -- |
166
- | [**width**][canvas_width] | [**pages**][canvas_pages] ⚡ | [**async**][canvas_async] ⚡ |
166
+ | [**width**][canvas_width] | [**pages**][canvas_pages] ⚡ | ~~[**async**][canvas_async]~~ ⚡ |
167
167
  | [**height**][canvas_height] | [getContext()][getContext] | [**pdf**, **png**, **svg**, **jpg**][shorthands] ⚡ |
168
- | | [newPage()][newPage] ⚡ | [saveAs()][saveAs] ⚡ |
169
- | | | [toBuffer()][toBuffer] ⚡ |
170
- | | | [toDataURL()][toDataURL_mdn] [⚡][toDataURL_ext] |
168
+ | | [newPage()][newPage] ⚡ | [saveAs()][saveAs] / [saveAsSync()][saveAs] ⚡ |
169
+ | | | [toBuffer()][toBuffer] / [toBufferSync()][toBuffer] ⚡ |
170
+ | | | [toDataURL()][toDataURL_ext] / [toDataURLSync()][toDataURL_ext] ⚡ |
171
171
 
172
172
  [canvas_width]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/width
173
173
  [canvas_height]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/height
174
174
  [canvas_async]: #async
175
175
  [canvas_pages]: #pages
176
176
  [getContext]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
177
- [saveAs]: #saveasfilename-page-format-density1-quality092-outlinefalse
178
- [toBuffer]: #tobufferformat-page-density-quality-outline
177
+ [saveAs]: #saveasfilename-page-format-matte-density1-quality092-outlinefalse
178
+ [toBuffer]: #tobufferformat-page-matte-density-quality-outline
179
179
  [newPage]: #newpagewidth-height
180
180
  [toDataURL_mdn]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
181
- [toDataURL_ext]: #todataurlformat-page-density-quality-outline
181
+ [toDataURL_ext]: #todataurlformat-page-matte-density-quality-outline
182
182
  [shorthands]: #pdf-svg-jpg-and-png
183
183
 
184
184
  #### Creating new `Canvas` objects
@@ -190,21 +190,24 @@ let defaultCanvas = new Canvas() // without arguments, defaults to 300 × 150 px
190
190
  let squareCanvas = new Canvas(512, 512) // creates a 512 px square
191
191
  ```
192
192
 
193
- ##### PROPERTIES
194
-
195
- #### `.async`
193
+ #### Saving graphics to files, buffers, and strings
196
194
 
197
- When the canvas renders images and writes them to disk, it does so in a background thread so as not to block execution within your script. As a result you’ll generally want to deal with the canvas from within an `async` function and be sure to use the `await` keyword when accessing any of its output methods or shorthand properties:
195
+ When the canvas renders images and writes them to disk, it does so in a background thread so as not to block execution within your script. As a result you’ll generally want to deal with the canvas from within an `async` function and be sure to use the `await` keyword when accessing any of its output methods or shorthand properties (all of which return Promises):
198
196
  - [`saveAs()`][saveAs]
199
197
  - [`toBuffer()`][toBuffer]
200
198
  - [`toDataURL()`][toDataURL_ext]
201
199
  - [`.pdf`, `.svg`, `.jpg`, and `.png`][shorthands]
202
200
 
203
- In cases where this is not the desired behavior, you can switch these methods into a synchronous mode for a particular canvas by setting its `async` property to `false`. For instance, both of the example functions below will generate PNG & PDF from the canvas, though the first will be more efficient (particularly for parallel contexts like request-handlers in an HTTP server or batch exports):
204
- ```js
205
201
 
202
+ In cases where this is not the desired behavior, you can use the synchronous equivalents for the primary export functions. They accept identical arguments to their async versions but block execution and return their values synchronously rather than wrapped in Promises. Also note that the [shorthand properties][shorthands] do not have synchronous versions:
203
+ - [`saveAsSync()`][saveAs]
204
+ - [`toBufferSync()`][toBuffer]
205
+ - [`toDataURLSync()`][toDataURL_ext]
206
+
207
+ For instance, both of the example functions below will generate PNG & PDF from the canvas, though the first will be more efficient (particularly for parallel contexts like request-handlers in an HTTP server or batch exports):
208
+
209
+ ```js
206
210
  let canvas = new Canvas()
207
- console.log(canvas.async) // -> true by default
208
211
 
209
212
  async function normal(){
210
213
  let pngURL = await canvas.toDataURL("png")
@@ -212,14 +215,16 @@ async function normal(){
212
215
  }
213
216
 
214
217
  function synchronous(){
215
- canvas.async = false // switch into synchronous mode
216
- let pngURL = canvas.toDataURL("png")
217
- let pdfBuffer = canvas.pdf
218
+ let pngURL = canvas.toDataURLSync("png")
219
+ let pdfBuffer = canvas.toBufferSync("pdf")
218
220
  }
219
221
  ```
220
222
 
223
+ ##### PROPERTIES
221
224
 
225
+ #### ~~`.async`~~
222
226
 
227
+ **The async property has been deprecated** and will be removed in a future release. Use the [`saveAsSync()`][saveAs], [`toBufferSync()`][toBuffer], and [`toDataURLSync()`][toDataURL_ext] methods if the default, asynchronous versions aren't to your liking.
223
228
 
224
229
  #### `.pages`
225
230
 
@@ -248,6 +253,10 @@ An integer can optionally be placed between the braces to indicate the number of
248
253
  ##### page
249
254
  The optional `page` argument accepts an integer that allows for the individual selection of pages in a multi-page canvas. Note that page indexing starts with page 1 **not** 0. The page value can also be negative, counting from the end of the canvas’s `.pages` array. For instance, `.saveAs("currentPage.png", {page:-1})` is equivalent to omitting `page` since they both yield the canvas’s most recently added page.
250
255
 
256
+ ##### format
257
+
258
+ The image format to generate, specified either as a mime-type string or file extension. The `format` argument will take precedence over the type specified through the `filename` argument’s extension, but is primarily useful when generating a file whose name cannot end with an extension for other reasons.
259
+
251
260
  ##### matte
252
261
  The optional `matte` argument accepts a color-string specifying the background that should be drawn *behind* the canvas in the exported image. Any transparent portions of the image will be filled with the matte color.
253
262
 
@@ -265,11 +274,11 @@ The `quality` option is a number between 0 and 1.0 that controls the level of JP
265
274
  ##### outline
266
275
  When generating SVG output containing text, you have two options for how to handle the fonts that were used. By default, SVG files will contain `<text>` elements that refer to the fonts by name in the embedded stylesheet. This requires that viewers of the SVG have the same fonts available on their system (or accessible as webfonts). Setting the optional `outline` argument to `true` will trace all the letterforms and ‘burn’ them into the file as bézier paths. This will result in a much larger file (and one in which the original text strings will be unrecoverable), but it will be viewable regardless of the specifics of the system it’s displayed on.
267
276
 
268
- #### `toBuffer(format, {page, density, quality, outline})`
277
+ #### `toBuffer(format, {page, matte, density, quality, outline})`
269
278
 
270
279
  Node [`Buffer`][Buffer] objects containing various image formats can be created by passing either a format string like `"svg"` or a mime-type like `"image/svg+xml"`. An ‘@’ suffix can be added to the format string to specify a pixel-density (for instance, `"jpg@2x"`). The optional arguments behave the same as in the `saveAs` method.
271
280
 
272
- #### `toDataURL(format, {page, density, quality, outline})`
281
+ #### `toDataURL(format, {page, matte, density, quality, outline})`
273
282
 
274
283
  This method accepts the same arguments and behaves similarly to `.toBuffer`. However instead of returning a Buffer, it returns a string of the form `"data:<mime-type>;base64,<image-data>"` which can be used as a `src` attribute in `<img>` tags, embedded into CSS, etc.
275
284
 
@@ -278,33 +287,18 @@ This method accepts the same arguments and behaves similarly to `.toBuffer`. How
278
287
 
279
288
  Most of your interaction with the canvas will actually be directed toward its ‘rendering context’, a supporting object you can acquire by calling the canvas’s [getContext()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext) and [newPage()][newPage] methods.
280
289
 
281
-
282
- | Canvas State | Drawing | Pattern & Color | Line Style | Transform |
283
- |------------------------------------------|----------------------------------------------|---------------------------------------------------|-----------------------------------------|------------------------------------------|
284
- | [**canvas**][canvas_attr] ⧸[⚡](#canvas) | [clearRect()][clearRect()] | [**fillStyle**][fillStyle] | [**lineCap**][lineCap] | [**currentTransform**][currentTransform] |
285
- | [beginPath()][beginPath()] | [fillRect()][fillRect()] | [**strokeStyle**][strokeStyle] | [**lineDashFit** ⚡][lineDashFit] | [getTransform()][getTransform()] |
286
- | [isPointInPath()][isPointInPath()] | [strokeRect()][strokeRect()] | [createConicGradient()][createConicGradient()] | [**lineDashMarker** ⚡][lineDashMarker] | [setTransform()][setTransform()] |
287
- | [isPointInStroke()][isPointInStroke()] | [fillText()][fillText()] ⧸[⚡][drawText] | [createLinearGradient()][createLinearGradient()] | [**lineDashOffset**][lineDashOffset] | [resetTransform()][resetTransform()] |
288
- | [save()][save()] | [strokeText()][strokeText()] ⧸[⚡][drawText] | [createRadialGradient()][createRadialGradient()] | [**lineJoin**][lineJoin] | [transform()][transform()] |
289
- | [restore()][restore()] | [fill()][fill()] | [createPattern()][createPattern()] | [**lineWidth**][lineWidth] | [translate()][translate()] |
290
- | [clip()][clip()] | [stroke()][stroke()] | [createTexture() ⚡][createTexture()] | [**miterLimit**][miterLimit] | [rotate()][rotate()] |
291
- | | | | [getLineDash()][getLineDash()] | [scale()][scale()] |
292
- | | | | [setLineDash()][setLineDash()] | |
293
-
294
-
295
- | Bezier Paths | Typography | Images | Compositing Effects |
296
- |------------------------------------------|-------------------------------------------------------------|----------------------------------------------------|----------------------------------------------------------|
297
- | [moveTo()][moveTo()] | [**direction**][direction] | [**imageSmoothingEnabled**][imageSmoothingEnabled] | [**filter**][filter] |
298
- | [lineTo()][lineTo()] | [**font**][font] ⧸[⚡](#font) | [**imageSmoothingQuality**][imageSmoothingQuality] | [**globalAlpha**][globalAlpha] |
299
- | [arcTo()][arcTo()] | [**fontVariant** ⚡](#fontvariant) | [createImageData()][createImageData()] | [**globalCompositeOperation**][globalCompositeOperation] |
300
- | [bezierCurveTo()][bezierCurveTo()] | [**textAlign**][textAlign] | [getImageData()][getImageData()] | [**shadowBlur**][shadowBlur] |
301
- | [conicCurveTo() ⚡][conicCurveTo] | [**textBaseline**][textBaseline] | [putImageData()][putImageData()] | [**shadowColor**][shadowColor] |
302
- | [quadraticCurveTo()][quadraticCurveTo()] | [**textTracking** ⚡](#texttracking) | [drawImage()][drawImage()] | [**shadowOffsetX**][shadowOffsetX] |
303
- | [closePath()][closePath()] | [**textWrap** ⚡](#textwrap) | | [**shadowOffsetY**][shadowOffsetY] |
304
- | [arc()][arc()] | [measureText()][measureText()] ⧸[⚡](#measuretextstr-width) | | |
305
- | [ellipse()][ellipse()] | [outlineText() ⚡][outlineText()] | | |
306
- | [rect()][rect()] | | |
307
-
290
+ | Canvas State | Drawing | Pattern & Color | Line Style | Transform | Bezier Paths | Typography | Images | Compositing Effects |
291
+ |-----------------------------------------------|---------------------------------------------------|---------------------------------------------------|----------------------------------------------|--------------------------------------------------|------------------------------------------|------------------------------------------------------------------|----------------------------------------------------|----------------------------------------------------------|
292
+ | [**canvas**][canvas_attr] ⧸[⚡](#canvas) | [clearRect()][clearRect()] | [**fillStyle**][fillStyle] | [**lineCap**][lineCap] | [**currentTransform**][currentTransform] | [moveTo()][moveTo()] | [**direction**][direction] | [**imageSmoothingEnabled**][imageSmoothingEnabled] | [**filter**][filter] |
293
+ | [beginPath()][beginPath()] | [fillRect()][fillRect()] | [**strokeStyle**][strokeStyle] | [**lineDashFit** ⚡][lineDashFit] | [createProjection() ⚡][createProjection()] | [lineTo()][lineTo()] | [**font**][font] ⧸[⚡](#font) | [**imageSmoothingQuality**][imageSmoothingQuality] | [**globalAlpha**][globalAlpha] |
294
+ | [isPointInPath()][isPointInPath()] | [strokeRect()][strokeRect()] | [createConicGradient()][createConicGradient()] | [**lineDashMarker** ⚡][lineDashMarker] | [getTransform()][getTransform()] | [arcTo()][arcTo()] | [**fontVariant** ⚡](#fontvariant) | [createImageData()][createImageData()] | [**globalCompositeOperation**][globalCompositeOperation] |
295
+ | [isPointInStroke()][isPointInStroke()] | [fillText()][fillText()] ⧸[⚡][drawText] | [createLinearGradient()][createLinearGradient()] | [**lineDashOffset**][lineDashOffset] | [setTransform()][setTransform()] | [bezierCurveTo()][bezierCurveTo()] | [**textAlign**][textAlign] | [getImageData()][getImageData()] | [**shadowBlur**][shadowBlur] |
296
+ | [save()][save()] | [strokeText()][strokeText()] ⧸[⚡][drawText] | [createRadialGradient()][createRadialGradient()] | [**lineJoin**][lineJoin] | [resetTransform()][resetTransform()] | [conicCurveTo() ⚡][conicCurveTo] | [**textBaseline**][textBaseline] | [putImageData()][putImageData()] | [**shadowColor**][shadowColor] |
297
+ | [restore()][restore()] | [fill()][fill()] | [createPattern()][createPattern()] | [**lineWidth**][lineWidth] | [transform()][transform()] | [quadraticCurveTo()][quadraticCurveTo()] | [**textTracking** ⚡](#texttracking) | [drawCanvas() ⚡](#drawcanvascanvas-x-y-) | [**shadowOffsetX**][shadowOffsetX] |
298
+ | [clip()][clip()] | [stroke()][stroke()] | [createTexture() ⚡][createTexture()] | [**miterLimit**][miterLimit] | [translate()][translate()] | [closePath()][closePath()] | [**textWrap** ⚡](#textwrap) | [drawImage()][drawImage()] | [**shadowOffsetY**][shadowOffsetY] |
299
+ | | | | [getLineDash()][getLineDash()] | [rotate()][rotate()] | [arc()][arc()] | [measureText()][measureText()] ⧸[⚡](#measuretextstr-width) | | |
300
+ | | | | [setLineDash()][setLineDash()] | [scale()][scale()] | [ellipse()][ellipse()] | [outlineText() ⚡][outlineText()] | | |
301
+ | | | | | | [rect()][rect()] | | |
308
302
 
309
303
  ##### PROPERTIES
310
304
 
@@ -389,6 +383,71 @@ The `lineDashFit` attribute can be set to `"move"`, `"turn"`, or `"follow"` and
389
383
 
390
384
  Adds a line segment connecting the current point to (*x, y*) but curving toward the control point (*cpx, cpy*) along the way. The `weight` argument controls how close the curve will come to the control point. If the weight is `0`, the result will be a straight line from the current point to (*x, y*). With a weight of `1.0`, the function is equivalent to calling `quadraticCurveTo()`. Weights greater than `1.0` will pull the line segment ever closer to the control point.
391
385
 
386
+ #### `createProjection(quad, [basis])`
387
+
388
+ This method returns a [DOMMatrix][DOMMatrix] object which can be used to simulate perspective effects or other distortions in which the four corners of the canvas are mapped to an arbitrary quadrilateral (four sided polygon). The matrix must be passed to the context's [setTransform][setTransform()] method for it take effect.
389
+
390
+ ##### `quad`
391
+
392
+ The `quad` argument defines the **target** of the transformation. It specifies four points that establish where the four corners of the source coordinate space will be positioned within the viewport. If these points form a polygon other than a rectangle, lines drawn along the x & y axes of the source space will no longer be perpendicular—trapezoids allow for ‘vanishing point’ effects and parallelograms create ‘skew’.
393
+
394
+ The geometry of the quadrilateral should be described as an Array of either 8 or 4 numbers specifying an arbitrary polygon or rectangle respectively:
395
+
396
+ ```js
397
+ [x1, y1, x2, y2, x3, y3, x4, y4] // four corner points
398
+ [left, top, right, bottom] // four edges of a rectangle
399
+
400
+ // internal arrays for grouping are also allowed
401
+ [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
402
+ ```
403
+
404
+ ##### `basis`
405
+
406
+ The optional `basis` argument defines the **source** quadrilateral whose corners will be mapped to the positions defined by `quad`. If no `basis` is specified, the canvas's bounding box will be used (i.e., the rectangle from ⟨`0`, `0`⟩ to ⟨`canvas.width`, `canvas.height`⟩). Note that drawing commands that go outside of the `basis` region may well be visible—it only establishes the geometry of the projection, not the [clipping][clip()] path.
407
+
408
+ The `basis` polygon can be described using 2, 4, or 8 numbers, using the canvas dimensions to fill in the unspecified coordinates:
409
+ ```js
410
+ [width, height] // rectangle from ⟨0, 0⟩ to ⟨width, height⟩
411
+ [left, top, right, bottom] // four edges of a rectangle
412
+ [x1, y1, x2, y2, x3, y3, x4, y4] // four corner points
413
+ ```
414
+
415
+ ----
416
+
417
+ The projection matrix will apply to all types of drawing: shapes, images, and text. This example transforms a white box and red `"@"` character into a trapezoid bounded by the vertical midline of the canvas and its left and right edges. Since no `basis` argument is provided, it will default to using the current canvas bounds as the rectangle to be mapped onto that trapezoid.
418
+
419
+ ```js
420
+ let canvas = new Canvas(512, 512),
421
+ ctx = canvas.getContext("2d"),
422
+ {width:w, height:h} = canvas;
423
+ ctx.font = '900 480px Times'
424
+ ctx.textAlign = 'center'
425
+ ctx.fillStyle = '#aaa'
426
+ ctx.fillRect(0, 0, w, h)
427
+
428
+ let quad = [
429
+ w*.33, h/2, // upper left
430
+ w*.66, h/2, // upper right
431
+ w, h*.9, // bottom right
432
+ 0, h*.9, // bottom left
433
+ ]
434
+
435
+ let matrix = ctx.createProjection(quad) // use default basis
436
+ ctx.setTransform(matrix)
437
+
438
+ ctx.fillStyle = 'white'
439
+ ctx.fillRect(10, 10, w-20, h-20)
440
+
441
+ ctx.fillStyle = '#900'
442
+ ctx.fillText("@", w/2, h-40)
443
+
444
+ ```
445
+
446
+ The results below show the image generated when the `createProjection()` call is omitted entirely, called (as above) with just a `quad` argument, or called with two different values for the optional `basis` argument:
447
+
448
+ ![Paths and text with a perspective transform](/test/assets/path/projection@2x.png)
449
+
450
+
392
451
  #### `createTexture(spacing, {path, line, color, angle, offset=0})`
393
452
 
394
453
  The `createTexture()` method returns a `CanvasTexture` object that can be assigned to the context’s `strokeStyle` or `fillStyle` property. Similar to a `CanvasPattern`, a `CanvasTexture` defines a repeating pattern that will be drawn instead of a flat color, but textures define their content using *vectors* rather than bitmaps.
@@ -416,6 +475,21 @@ The rectangle defined by the `spacing` argument will be aligned with the canvas
416
475
  ##### `offset`
417
476
  As with `CanvasPattern` objects, textures are positioned globally relative to the upper left corner of the canvas—not the corner of the object currently being filled or stroked. To fine-tune the texture’s alignment with individual objects, set the `offset` argument to an `[x, y]` array with two numbers that will shift the texture relative to its origin.
418
477
 
478
+ #### `drawCanvas(canvas, x, y, …)`
479
+ This method behaves identically to the standard [`drawImage()`][drawImage()] function with one key difference: if the first argument is a canvas, it will not be converted to a bitmap before being drawn. Instead its contents will be added to the canvas as resolution-independent vector graphics. This is especially useful when scaling or rotating since it preserves the fidelity of text, patterns, and gradients from the source canvas.
480
+
481
+ ```js
482
+ let src = new Canvas(10, 10),
483
+ srcCtx = src.getContext("2d");
484
+ srcCtx.font = 'italic 10px Times'
485
+ srcCtx.fillText('¶', 2, 8)
486
+
487
+ let dst = new Canvas(350, 150),
488
+ dstCtx = dst.getContext("2d");
489
+ dstCtx.drawImage(src, 0, 0, 150, 150)
490
+ dstCtx.drawCanvas(src, 200, 0, 150, 150)
491
+ ```
492
+ ![drawCanvas preserves resolution-independence](/test/assets/image/drawCanvas@2x.png)
419
493
 
420
494
  #### `fillText(str, x, y, [width])` & `strokeText(str, x, y, [width])`
421
495
 
@@ -714,6 +788,13 @@ img.onload = function(){
714
788
  img.src = 'https://example.com/icon.png'
715
789
  ```
716
790
 
791
+ ```js
792
+ let img = new Image()
793
+ img.src = 'https://example.com/icon.png'
794
+ await img.decode()
795
+ ctx.drawImage(img, 100, 100)
796
+ ```
797
+
717
798
  ```js
718
799
  let img = await loadImage('https://example.com/icon.png')
719
800
  ctx.drawImage(img, 100, 100)
@@ -747,6 +828,10 @@ Asking for details about an unknown family will return `undefined`.
747
828
 
748
829
  Returns `true` if the family is installed on the system or has been added via `FontLibrary.use()`.
749
830
 
831
+ ##### `reset()`
832
+
833
+ Uninstalls any dynamically loaded fonts that had been added via `FontLibrary.use()`.
834
+
750
835
  ##### `use(familyName, [...fontPaths])`
751
836
 
752
837
  The `FontLibrary.use()` method allows you to dynamically load local font files and use them with your canvases. By default it will use whatever family name is in the font metadata, but this can be overridden by an alias you provide. Since font-wrangling can be messy, `use` can be called in a number of different ways:
@@ -770,6 +855,8 @@ FontLibrary.use("Grizwald", [
770
855
 
771
856
  ###### with a list of ‘glob’ patterns
772
857
 
858
+ > Note to Windows users: Due to recent changes to the [glob][glob] module, you must write paths using unix-style forward slashes. Backslashes are now used solely for escaping wildcard characters.
859
+
773
860
  ```js
774
861
  // with default family name
775
862
  FontLibrary.use(['fonts/Crimson_Pro/*.ttf'])
@@ -830,6 +917,7 @@ Many thanks to the [`node-canvas`](https://github.com/Automattic/node-canvas) de
830
917
  [conicCurveTo]: #coniccurvetocpx-cpy-x-y-weight
831
918
  [outlineText()]: #outlinetextstr
832
919
  [createTexture()]: #createtexturespacing-path-line-color-angle-offset0
920
+ [createProjection()]: #createprojectionquad-basis
833
921
  [lineDashMarker]: #linedashmarker
834
922
  [lineDashFit]: #linedashfit
835
923
 
@@ -915,4 +1003,6 @@ Many thanks to the [`node-canvas`](https://github.com/Automattic/node-canvas) de
915
1003
  [translate()]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate
916
1004
 
917
1005
  [nonzero]: https://en.wikipedia.org/wiki/Nonzero-rule
918
- [evenodd]: https://en.wikipedia.org/wiki/Even–odd_rule
1006
+ [evenodd]: https://en.wikipedia.org/wiki/Even–odd_rule
1007
+
1008
+ [glob]: https://github.com/isaacs/node-glob/blob/main/changelog.md#80
package/lib/browser.js CHANGED
@@ -9,9 +9,10 @@ const {asBuffer, asDownload, asZipDownload, atScale, options} = require('./io')
9
9
 
10
10
  const _toURL_ = Symbol.for("toDataURL")
11
11
 
12
- const loadImage = src => new Promise((onload, onerror) =>
13
- Object.assign(new Image(), {crossOrigin:'Anonymous', onload, onerror, src})
14
- )
12
+ const loadImage = src => {
13
+ let img = Object.assign(new Image(), {crossOrigin:'Anonymous', src})
14
+ return img.decode().then(() => img)
15
+ }
15
16
 
16
17
  class Canvas{
17
18
  constructor(width, height){
package/lib/index.d.ts ADDED
@@ -0,0 +1,236 @@
1
+ /// <reference lib="dom"/>
2
+ /// <reference types="node" />
3
+
4
+ export function loadImage(src: string | Buffer): Promise<Image>
5
+ export class DOMMatrix extends globalThis.DOMMatrix {}
6
+ export class DOMPoint extends globalThis.DOMPoint {}
7
+ export class DOMRect extends globalThis.DOMRect {}
8
+ export class Image extends globalThis.Image {}
9
+ export class ImageData extends globalThis.ImageData {}
10
+ export class CanvasGradient extends globalThis.CanvasGradient {}
11
+ export class CanvasPattern extends globalThis.CanvasPattern {}
12
+ export class CanvasTexture {}
13
+
14
+ //
15
+ // Canvas
16
+ //
17
+
18
+ export type ExportFormat = "png" | "jpg" | "jpeg" | "pdf" | "svg";
19
+
20
+ export interface RenderOptions {
21
+ /** Page to export: Defaults to 1 (i.e., first page) */
22
+ page?: number
23
+
24
+ /** Background color to draw beneath transparent parts of the canvas */
25
+ matte?: string
26
+
27
+ /** Number of pixels per grid ‘point’ (defaults to 1) */
28
+ density?: number
29
+
30
+ /** Quality for lossy encodings like JPEG (0.0–1.0) */
31
+ quality?: number
32
+
33
+ /** Convert text to paths for SVG exports */
34
+ outline?: boolean
35
+ }
36
+
37
+ export interface SaveOptions extends RenderOptions {
38
+ /** Image format to use */
39
+ format?: ExportFormat
40
+ }
41
+
42
+ export class Canvas {
43
+ /** @internal */
44
+ constructor(width?: number, height?: number)
45
+ static contexts: WeakMap<Canvas, readonly CanvasRenderingContext2D[]>
46
+
47
+ /**
48
+ * @deprecated Use the saveAsSync, toBufferSync, and toDataURLSync methods
49
+ * instead of setting the async property to false
50
+ */
51
+ async: boolean
52
+ width: number
53
+ height: number
54
+
55
+ getContext(type?: "2d"): CanvasRenderingContext2D
56
+ newPage(width?: number, height?: number): CanvasRenderingContext2D
57
+ readonly pages: CanvasRenderingContext2D[]
58
+
59
+ saveAs(filename: string, options?: SaveOptions): Promise<void>
60
+ toBuffer(format: ExportFormat, options?: RenderOptions): Promise<Buffer>
61
+ toDataURL(format: ExportFormat, options?: RenderOptions): Promise<string>
62
+
63
+ saveAsSync(filename: string, options?: SaveOptions): void
64
+ toBufferSync(format: ExportFormat, options?: RenderOptions): Buffer
65
+ toDataURLSync(format: ExportFormat, options?: RenderOptions): string
66
+
67
+ get pdf(): Promise<Buffer>
68
+ get svg(): Promise<Buffer>
69
+ get jpg(): Promise<Buffer>
70
+ get png(): Promise<Buffer>
71
+ }
72
+
73
+ //
74
+ // Context
75
+ //
76
+
77
+ type Offset = [x: number, y: number] | number
78
+
79
+ export interface CreateTextureOptions {
80
+ /** The 2D shape to be drawn in a repeating grid with the specified spacing (if omitted, parallel lines will be used) */
81
+ path?: Path2D
82
+
83
+ /** The lineWidth with which to stroke the path (if omitted, the path will be filled instead) */
84
+ line?: number
85
+
86
+ /** The color to use for stroking/filling the path */
87
+ color?: string
88
+
89
+ /** The orientation of the pattern grid in radians */
90
+ angle?: number
91
+
92
+ /** The amount by which to shift the pattern relative to the canvas origin */
93
+ offset?: Offset
94
+ }
95
+
96
+ export type CanvasImageSource = Canvas | Image;
97
+
98
+ interface CanvasDrawImage {
99
+ drawImage(image: CanvasImageSource, dx: number, dy: number): void;
100
+ drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;
101
+ drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
102
+ drawCanvas(image: Canvas, dx: number, dy: number): void;
103
+ drawCanvas(image: Canvas, dx: number, dy: number, dw: number, dh: number): void;
104
+ drawCanvas(image: Canvas, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
105
+ }
106
+
107
+ interface CanvasFillStrokeStyles {
108
+ fillStyle: string | CanvasGradient | CanvasPattern | CanvasTexture;
109
+ strokeStyle: string | CanvasGradient | CanvasPattern | CanvasTexture;
110
+ createConicGradient(startAngle: number, x: number, y: number): CanvasGradient;
111
+ createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient;
112
+ createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient;
113
+ createPattern(image: CanvasImageSource, repetition: string | null): CanvasPattern | null;
114
+ createTexture(spacing: Offset, options?: CreateTextureOptions): CanvasTexture
115
+ }
116
+
117
+ type QuadOrRect = [x1:number, y1:number, x2:number, y2:number, x3:number, y3:number, x4:number, y4:number] |
118
+ [left:number, top:number, right:number, bottom:number] | [width:number, height:number]
119
+
120
+ export interface CanvasRenderingContext2D extends CanvasCompositing, CanvasDrawImage, CanvasDrawPath, CanvasFillStrokeStyles, CanvasFilters, CanvasImageData, CanvasImageSmoothing, CanvasPath, CanvasPathDrawingStyles, CanvasRect, CanvasShadowStyles, CanvasState, CanvasText, CanvasTextDrawingStyles, CanvasTransform, CanvasUserInterface {
121
+ readonly canvas: Canvas;
122
+ fontVariant: string;
123
+ textTracking: number;
124
+ textWrap: boolean;
125
+ lineDashMarker: Path2D | null;
126
+ lineDashFit: "move" | "turn" | "follow";
127
+
128
+ get currentTransform(): DOMMatrix
129
+ set currentTransform(matrix: DOMMatrix)
130
+ createProjection(quad: QuadOrRect, basis?: QuadOrRect): DOMMatrix
131
+
132
+ conicCurveTo(cpx: number, cpy: number, x: number, y: number, weight: number): void
133
+ // getContextAttributes(): CanvasRenderingContext2DSettings;
134
+
135
+ fillText(text: string, x: number, y:number, maxWidth?: number): void
136
+ strokeText(text: string, x: number, y:number, maxWidth?: number): void
137
+ measureText(text: string, maxWidth?: number): TextMetrics
138
+ outlineText(text: string): Path2D
139
+ }
140
+
141
+ //
142
+ // Bézier Paths
143
+ //
144
+
145
+ export interface Path2DBounds {
146
+ readonly top: number
147
+ readonly left: number
148
+ readonly bottom: number
149
+ readonly right: number
150
+ readonly width: number
151
+ readonly height: number
152
+ }
153
+
154
+ export type Path2DEdge = [verb: string, ...args: number[]]
155
+
156
+ export class Path2D extends globalThis.Path2D {
157
+ d: string
158
+ readonly bounds: Path2DBounds
159
+ readonly edges: readonly Path2DEdge[]
160
+
161
+ contains(x: number, y: number): boolean
162
+ conicCurveTo(
163
+ cpx: number,
164
+ cpy: number,
165
+ x: number,
166
+ y: number,
167
+ weight: number
168
+ ): void
169
+
170
+ complement(otherPath: Path2D): Path2D
171
+ difference(otherPath: Path2D): Path2D
172
+ intersect(otherPath: Path2D): Path2D
173
+ union(otherPath: Path2D): Path2D
174
+ xor(otherPath: Path2D): Path2D
175
+ interpolate(otherPath: Path2D, weight: number): Path2D
176
+
177
+ jitter(segmentLength: number, amount: number, seed?: number): Path2D
178
+ offset(dx: number, dy: number): Path2D
179
+ points(step?: number): readonly [x: number, y: number][]
180
+ round(radius: number): Path2D
181
+ simplify(rule?: "nonzero" | "evenodd"): Path2D
182
+ transform(...args: [matrix: DOMMatrix] | [a: number, b: number, c: number, d: number, e: number, f: number]): Path2D;
183
+ trim(start: number, end: number, inverted?: boolean): Path2D;
184
+ trim(start: number, inverted?: boolean): Path2D;
185
+
186
+ unwind(): Path2D
187
+ }
188
+
189
+ //
190
+ // Typography
191
+ //
192
+
193
+ export interface TextMetrics extends globalThis.TextMetrics {
194
+ lines: TextMetricsLine[]
195
+ }
196
+
197
+ export interface TextMetricsLine {
198
+ readonly x: number
199
+ readonly y: number
200
+ readonly width: number
201
+ readonly height: number
202
+ readonly baseline: number
203
+ readonly startIndex: number
204
+ readonly endIndex: number
205
+ }
206
+
207
+ export interface FontFamily {
208
+ family: string
209
+ weights: number[]
210
+ widths: string[]
211
+ styles: string[]
212
+ }
213
+
214
+ export interface Font {
215
+ family: string
216
+ weight: number
217
+ style: string
218
+ width: string
219
+ file: string
220
+ }
221
+
222
+ export interface FontLibrary {
223
+ families: readonly string[]
224
+ family(name: string): FontFamily | undefined
225
+ has(familyName: string): boolean
226
+
227
+ use(familyName: string, fontPaths?: string | readonly string[]): Font[]
228
+ use(fontPaths: readonly string[]): Font[]
229
+ use(
230
+ families: Record<string, readonly string[] | string>
231
+ ): Record<string, Font[] | Font>
232
+
233
+ reset(): void
234
+ }
235
+
236
+ export const FontLibrary: FontLibrary
package/lib/index.js CHANGED
@@ -51,7 +51,12 @@ class RustClass{
51
51
  }
52
52
 
53
53
  ƒ(fn, ...args){
54
- return this.native[fn](this[ø], ...args)
54
+ try{
55
+ return this.native[fn](this[ø], ...args)
56
+ }catch(error){
57
+ Error.captureStackTrace(error, this.ƒ)
58
+ throw error
59
+ }
55
60
  }
56
61
  }
57
62
 
@@ -76,18 +81,23 @@ const toString = val => typeof val=='string' ? val : new String(val).toString()
76
81
  //
77
82
 
78
83
  function toSkMatrix(jsMatrix){
79
- if (Array.isArray(jsMatrix)){
80
- var [a, b, c, d, e, f] = jsMatrix
81
- }else{
82
- var {a, b, c, d, e, f} = jsMatrix
84
+ if (Array.isArray(jsMatrix) && jsMatrix.length==6){
85
+ var [a, b, c, d, e, f, m14, m24, m44] = jsMatrix.concat(0, 0, 1)
86
+ }else if (jsMatrix instanceof geometry.DOMMatrix){
87
+ var {a, b, c, d, e, f, m14, m24, m44} = jsMatrix
83
88
  }
84
- return [a, c, e, b, d, f]
89
+ return [a, c, e, b, d, f, m14, m24, m44]
85
90
  }
86
91
 
87
92
  function fromSkMatrix(skMatrix){
88
- // TBD: how/if to map the perspective terms
89
- let [a, c, e, b, d, f, p0, p1, p2] = skMatrix
90
- return new geometry.DOMMatrix([a, b, c, d, e, f])
93
+ let [a, b, c, d, e, f, p0, p1, p2] = skMatrix
94
+ return new geometry.DOMMatrix([
95
+ a, d, 0, p0,
96
+ b, e, 0, p1,
97
+ 0, 0, 1, 0,
98
+ c, f, 0, p2
99
+ ])
100
+
91
101
  }
92
102
 
93
103
 
@@ -141,44 +151,60 @@ class Canvas extends RustClass{
141
151
  get svg(){ return this.toBuffer("svg") }
142
152
 
143
153
  get async(){ return this.prop('async') }
144
- set async(flag){ this.prop('async', flag) }
154
+ set async(flag){
155
+ if (!flag){
156
+ process.emitWarning("Use the saveAsSync, toBufferSync, and toDataURLSync methods instead of setting the Canvas `async` property to false", "DeprecationWarning")
157
+ }
158
+ this.prop('async', flag)
159
+ }
145
160
 
146
161
  saveAs(filename, opts={}){
162
+ if (!this.async) return this.saveAsSync(...arguments) // support while deprecated
163
+
147
164
  opts = typeof opts=='number' ? {quality:opts} : opts
148
165
  let {format, quality, pages, padding, pattern, density, outline, matte} = io.options(this.pages, {filename, ...opts}),
149
- args = [pages.map(core), pattern, padding, format, quality, density, outline, matte];
166
+ args = [pages.map(core), pattern, padding, format, quality, density, outline, matte]
167
+ return this.ƒ("save", ...args)
168
+ }
150
169
 
151
- if (this.async){
152
- let worker = new EventEmitter()
153
- this.ƒ("save", (result, msg) => worker.emit(result, msg), ...args)
154
- return new Promise((res, rej) => worker.once('ok', res).once('err', msg => rej(new Error(msg))) )
155
- }else{
156
- this.ƒ("saveSync", ...args)
157
- }
170
+ saveAsSync(filename, opts={}){
171
+ opts = typeof opts=='number' ? {quality:opts} : opts
172
+ let {format, quality, pages, padding, pattern, density, outline, matte} = io.options(this.pages, {filename, ...opts})
173
+ this.ƒ("saveSync", pages.map(core), pattern, padding, format, quality, density, outline, matte)
158
174
  }
159
175
 
160
176
  toBuffer(extension="png", opts={}){
177
+ if (!this.async) return this.toBufferSync(...arguments) // support while deprecated
178
+
161
179
  opts = typeof opts=='number' ? {quality:opts} : opts
162
180
  let {format, quality, pages, density, outline, matte} = io.options(this.pages, {extension, ...opts}),
163
181
  args = [pages.map(core), format, quality, density, outline, matte];
182
+ return this.ƒ("toBuffer", ...args)
183
+ }
164
184
 
165
- if (this.async){
166
- let worker = new EventEmitter()
167
- this.ƒ("toBuffer", (result, msg) => worker.emit(result, msg), ...args)
168
- return new Promise((res, rej) => worker.once('ok', res).once('err', msg => rej(new Error(msg))) )
169
- }else{
170
- return this.ƒ("toBufferSync", ...args)
171
- }
185
+ toBufferSync(extension="png", opts={}){
186
+ opts = typeof opts=='number' ? {quality:opts} : opts
187
+ let {format, quality, pages, density, outline, matte} = io.options(this.pages, {extension, ...opts})
188
+ return this.ƒ("toBufferSync", pages.map(core), format, quality, density, outline, matte)
172
189
  }
173
190
 
174
191
  toDataURL(extension="png", opts={}){
192
+ if (!this.async) return this.toDataURLSync(...arguments) // support while deprecated
193
+
175
194
  opts = typeof opts=='number' ? {quality:opts} : opts
176
195
  let {mime} = io.options(this.pages, {extension, ...opts}),
177
- urlify = data => `data:${mime};base64,${data.toString('base64')}`,
178
196
  buffer = this.toBuffer(extension, opts);
179
- return this.async ? buffer.then(urlify) : urlify(buffer)
197
+ return buffer.then(data => `data:${mime};base64,${data.toString('base64')}`)
180
198
  }
181
199
 
200
+ toDataURLSync(extension="png", opts={}){
201
+ opts = typeof opts=='number' ? {quality:opts} : opts
202
+ let {mime} = io.options(this.pages, {extension, ...opts}),
203
+ buffer = this.toBufferSync(extension, opts);
204
+ return `data:${mime};base64,${buffer.toString('base64')}`
205
+ }
206
+
207
+
182
208
  [REPR](depth, options) {
183
209
  let {width, height, async, pages} = this
184
210
  return `Canvas ${inspect({width, height, async, pages}, options)}`
@@ -261,30 +287,35 @@ class CanvasRenderingContext2D extends RustClass{
261
287
  get currentTransform(){ return fromSkMatrix( this.prop('currentTransform') ) }
262
288
  set currentTransform(matrix){ this.prop('currentTransform', toSkMatrix(matrix) ) }
263
289
 
290
+ resetTransform(){ this.ƒ('resetTransform')}
264
291
  getTransform(){ return this.currentTransform }
265
292
  setTransform(matrix){
266
293
  this.currentTransform = arguments.length > 1 ? [...arguments] : matrix
267
294
  }
268
- transform(...terms){ this.ƒ('transform', ...terms)}
269
- translate(x, y){ this.ƒ('translate', x, y)}
270
- scale(x, y){ this.ƒ('scale', x, y)}
271
- rotate(angle){ this.ƒ('rotate', angle)}
272
- resetTransform(){ this.ƒ('resetTransform')}
295
+
296
+ transform(a, b, c, d, e, f){ this.ƒ('transform', ...arguments)}
297
+ translate(x, y){ this.ƒ('translate', ...arguments)}
298
+ scale(x, y){ this.ƒ('scale', ...arguments)}
299
+ rotate(angle){ this.ƒ('rotate', ...arguments)}
300
+
301
+ createProjection(quad, basis){
302
+ return fromSkMatrix(this.ƒ("createProjection", [quad].flat(), [basis].flat()))
303
+ }
273
304
 
274
305
  // -- bézier paths ----------------------------------------------------------
275
306
  beginPath(){ this.ƒ('beginPath') }
276
307
  rect(x, y, width, height){ this.ƒ('rect', ...arguments) }
277
308
  arc(x, y, radius, startAngle, endAngle, isCCW){ this.ƒ('arc', ...arguments) }
278
309
  ellipse(x, y, xRadius, yRadius, rotation, startAngle, endAngle, isCCW){ this.ƒ('ellipse', ...arguments) }
279
- moveTo(x, y){ this.ƒ('moveTo', x, y) }
280
- lineTo(x, y){ this.ƒ('lineTo', x, y) }
310
+ moveTo(x, y){ this.ƒ('moveTo', ...arguments) }
311
+ lineTo(x, y){ this.ƒ('lineTo', ...arguments) }
281
312
  arcTo(x1, y1, x2, y2, radius){ this.ƒ('arcTo', ...arguments) }
282
313
  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y){ this.ƒ('bezierCurveTo', ...arguments) }
283
314
  quadraticCurveTo(cpx, cpy, x, y){ this.ƒ('quadraticCurveTo', ...arguments) }
284
315
  conicCurveTo(cpx, cpy, x, y, weight){ this.ƒ("conicCurveTo", ...arguments) }
285
316
  closePath(){ this.ƒ('closePath') }
286
- isPointInPath(x, y){ return this.ƒ('isPointInPath', x, y) }
287
- isPointInStroke(x, y){ return this.ƒ('isPointInStroke', x, y) }
317
+ isPointInPath(x, y){ return this.ƒ('isPointInPath', ...arguments) }
318
+ isPointInStroke(x, y){ return this.ƒ('isPointInStroke', ...arguments) }
288
319
 
289
320
  // -- using paths -----------------------------------------------------------
290
321
  fill(path, rule){
@@ -377,19 +408,27 @@ class CanvasRenderingContext2D extends RustClass{
377
408
  let w = Math.floor(width),
378
409
  h = Math.floor(height),
379
410
  buffer = this.ƒ('getImageData', x, y, w, h);
380
- return new ImageData(w, h, buffer)
411
+ return new ImageData(buffer, w, h)
381
412
  }
382
413
 
383
414
  drawImage(image, ...coords){
384
415
  if (image instanceof Canvas){
385
- this.ƒ('drawCanvas', core(image.getContext('2d')), ...coords)
416
+ this.ƒ('drawImage', core(image.getContext('2d')), ...coords)
386
417
  }else if (image instanceof Image){
387
- this.ƒ('drawRaster', core(image), ...coords)
418
+ this.ƒ('drawImage', core(image), ...coords)
388
419
  }else{
389
420
  throw new Error("Expected an Image or a Canvas argument")
390
421
  }
391
422
  }
392
423
 
424
+ drawCanvas(image, ...coords){
425
+ if (image instanceof Canvas){
426
+ this.ƒ('drawCanvas', core(image.getContext('2d')), ...coords)
427
+ }else{
428
+ this.drawImage(image, ...coords)
429
+ }
430
+ }
431
+
393
432
  // -- typography ------------------------------------------------------------
394
433
  get font(){ return this.prop('font') }
395
434
  set font(str){ this.prop('font', css.font(str)) }
@@ -489,6 +528,8 @@ class FontLibrary extends RustClass {
489
528
  throw new Error("Expected an array of file paths or an object mapping family names to font files")
490
529
  }
491
530
  }
531
+
532
+ reset(){ return this.ƒ('reset') }
492
533
  }
493
534
 
494
535
  class Image extends RustClass {
@@ -562,9 +603,17 @@ class Image extends RustClass {
562
603
  }
563
604
 
564
605
  class ImageData{
565
- constructor(width, height, data){
566
- if (arguments[0] instanceof ImageData){
567
- var {width, height, data} = arguments[0]
606
+ constructor(...args){
607
+ if (args[0] instanceof ImageData){
608
+ var {data, width, height} = args[0]
609
+ }else if (args[0] instanceof Uint8ClampedArray || args[0] instanceof Buffer){
610
+ var [data, width, height] = args
611
+ height = height || data.length / width / 4
612
+ if (data.length / 4 != width * height){
613
+ throw new Error("ImageData dimensions must match buffer length")
614
+ }
615
+ }else{
616
+ var [width, height] = args
568
617
  }
569
618
 
570
619
  if (!Number.isInteger(width) || !Number.isInteger(height) || width < 0 || height < 0){
@@ -624,8 +673,8 @@ class Path2D extends RustClass{
624
673
  }
625
674
 
626
675
  // line segments
627
- moveTo(x, y){ this.ƒ("moveTo", x, y) }
628
- lineTo(x, y){ this.ƒ("lineTo", x, y) }
676
+ moveTo(x, y){ this.ƒ("moveTo", ...arguments) }
677
+ lineTo(x, y){ this.ƒ("lineTo", ...arguments) }
629
678
  closePath(){ this.ƒ("closePath") }
630
679
  arcTo(x1, y1, x2, y2, radius){ this.ƒ("arcTo", ...arguments) }
631
680
  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y){ this.ƒ("bezierCurveTo", ...arguments) }
@@ -699,9 +748,7 @@ class TextMetrics{
699
748
  }
700
749
  }
701
750
 
702
- const loadImage = src => new Promise((onload, onerror) =>
703
- Object.assign(new Image(), {onload, onerror, src})
704
- )
751
+ const loadImage = src => Object.assign(new Image(), {src}).decode()
705
752
 
706
753
  module.exports = {
707
754
  Canvas, CanvasGradient, CanvasPattern, CanvasRenderingContext2D, CanvasTexture,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skia-canvas",
3
- "version": "0.9.27",
3
+ "version": "0.9.30",
4
4
  "description": "A canvas environment for Node",
5
5
  "author": "Christian Swinehart <drafting@samizdat.co>",
6
6
  "license": "MIT",
@@ -25,22 +25,24 @@
25
25
  "test": "jest"
26
26
  },
27
27
  "dependencies": {
28
- "@mapbox/node-pre-gyp": "^1.0.6",
29
- "glob": "^7.2.0",
28
+ "@mapbox/node-pre-gyp": "^1.0.9",
29
+ "cargo-cp-artifact": "^0.1",
30
+ "glob": "^8.0.3",
30
31
  "path-browserify": "^1.0.1",
31
- "simple-get": "^4.0.0",
32
+ "simple-get": "^4.0.1",
32
33
  "string-split-by": "^1.0.0"
33
34
  },
34
35
  "devDependencies": {
35
- "aws-sdk": "^2.1013.0",
36
- "cargo-cp-artifact": "^0.1",
37
- "express": "^4.17.1",
38
- "jest": "^27.3.1",
36
+ "@types/jest": "^27.5.1",
37
+ "@types/node": "^17.0.38",
38
+ "aws-sdk": "^2.1146.0",
39
+ "express": "^4.18.1",
40
+ "jest": "^28.1.0",
39
41
  "lodash": "^4.17.21",
40
- "nodemon": "^2.0.14",
42
+ "nodemon": "^2.0.16",
41
43
  "tmp": "^0.2.1"
42
44
  },
43
- "files":[
45
+ "files": [
44
46
  "lib"
45
47
  ],
46
48
  "binary": {