@napi-rs/canvas 1.0.13 → 1.1.1

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 CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![CI](https://github.com/Brooooooklyn/canvas/actions/workflows/CI.yaml/badge.svg)](https://github.com/Brooooooklyn/canvas/actions/workflows/CI.yaml)
4
4
  ![Skia Version](https://img.shields.io/badge/Skia-chrome%2Fm157-hotpink)
5
- [![install size](https://packagephobia.com/badge?p=@napi-rs/canvas)](https://packagephobia.com/result?p=@napi-rs/canvas)
6
5
  [![Downloads](https://img.shields.io/npm/dm/@napi-rs/canvas.svg?sanitize=true)](https://npmcharts.com/compare/@napi-rs/canvas?minimal=true)
7
6
 
8
7
  > 🚀 Help me to become a full-time open-source developer by [sponsoring me on Github](https://github.com/sponsors/Brooooooklyn)
@@ -36,6 +35,10 @@ All Apple M chips on **macOS**.
36
35
 
37
36
  Since Skia relies on the [glibc](https://www.gnu.org/software/libc/) 2.18 API, you need to have at least glibc version >= 2.18 on your system.
38
37
 
38
+ Note: the armv7 (`linux-arm-gnueabihf`) packages from 0.1.66 to 1.1.0 need
39
+ a newer glibc (0.1.66 needs 2.28, 1.1.0 needs 2.30). Later versions need
40
+ glibc 2.18, like the other targets.
41
+
39
42
  ## AWS Lambda usage
40
43
 
41
44
  To use this library on Lambda you will need to use a Lambda layer.
@@ -120,6 +123,71 @@ const jpeg = await canvas.encode('jpeg', { quality: 90, resolution: 300, copyrig
120
123
 
121
124
  WebP, AVIF and GIF output ignore these options. `toDataURL`, `toDataURLAsync`, `toBlob`, `convertToBlob`, `encodeStream` and `savePng` never write metadata. To save a file with metadata, write the Buffer from `toBuffer` or `encode`. With an options object, `toBuffer('image/jpeg', ...)` encodes at quality 92; use `encode` or `encodeSync` for another quality.
122
125
 
126
+ ## Text rendering
127
+
128
+ By default text is drawn the same way on every OS. Every font is drawn with FreeType, with Skia's default text gamma, no LCD (subpixel) text and one font family table (the one Linux uses).
129
+
130
+ Text gives the same pixels and the same `measureText` results on macOS, Linux and Windows when all of these are true:
131
+
132
+ 1. `ctx.font` names only fonts you registered. The canvas default `10px sans-serif`, the other generic families and names like `Arial` use installed fonts, unless you registered a font under that name.
133
+ 2. Your registered fonts have every character you draw. A character they lack is taken from an installed font.
134
+ 3. You set `ctx.lang`, for example to `'en'`. The default `'inherit'` follows the host language.
135
+ 4. You use the default `'portable'` renderer, not `setTextRenderer('system')`.
136
+ 5. The `FREETYPE_PROPERTIES` env var is not set.
137
+
138
+ x64 and arm64 builds can still differ by 1 color level on some edge pixels.
139
+
140
+ ```js
141
+ const { GlobalFonts } = require('@napi-rs/canvas')
142
+
143
+ // A name no OS installs
144
+ GlobalFonts.registerFromPath('./fonts/Lato-Regular.ttf', 'MyLato')
145
+ ctx.font = '20px MyLato'
146
+ ctx.lang = 'en'
147
+ ```
148
+
149
+ A name registered over an installed family (for example `Helvetica`) works like a CSS `@font-face` in Chrome: only font lists that name it use it, for every style (bold and italic are made from the faces you registered when you registered no bold or italic face). The generic families, such as `sans-serif`, keep the installed family. To change a generic family, register the font under the keyword itself:
150
+
151
+ ```js
152
+ GlobalFonts.registerFromPath('./fonts/Lato-Regular.ttf', 'sans-serif')
153
+ ```
154
+
155
+ These still depend on the OS and the machine:
156
+
157
+ - Installed fonts. Names like `Arial` or `Helvetica`, and the generic families (`sans-serif`, `serif`, `monospace`, `system-ui`, the canvas default `10px sans-serif`) resolve to whatever is installed.
158
+ - Characters your registered fonts lack. They are taken from an installed font (the ones `loadSystemFonts` and `loadFontsFromDir` load, or the OS font folder) that has them, else they draw as boxes. A font you `register` or `registerFromPath` is never used for a character another font lacks, as in Chrome: to give a host without fonts a fallback font, load it with `loadFontsFromDir`.
159
+ - The `ex`, `ch`, `cap` and line-height units inside SVG images use an installed default font.
160
+ - Lottie (Skottie) text uses installed fonts.
161
+ - PDF font embedding depends on the build target.
162
+ - x64 and arm64 builds can differ by 1 color level on some edge pixels.
163
+ - A `FREETYPE_PROPERTIES` env var changes FreeType's hinting.
164
+ - The host language. The default `ctx.lang` (`'inherit'`) follows the OS UI language (the macOS language list, the Windows user locale, or `LANGUAGE` / `LC_ALL` / `LC_MESSAGES` / `LANG` elsewhere), as Chrome does. It changes `locl` glyphs, case mapping (`small-caps` under `tr`) and CJK punctuation spacing. Set `ctx.lang` (for example `'en'`) to pin it. The installed font picked for a Han character still follows the host language unless `ctx.lang` names a CJK language (for example `'ja'` or `'zh-Hans'`). On macOS with `setTextRenderer('system')`, the generic families also follow a `ja`, `ko`, `zh-CN`, `zh-TW`, `ar`, `he` or `fa` UI language whatever `ctx.lang` is, as in Chrome (`serif` is Hiragino Mincho ProN under a Japanese UI).
165
+
166
+ Set `DISABLE_SYSTEM_FONTS_LOAD=1` to keep installed fonts out of `GlobalFonts.families`. By default they can still be found by name, on every OS.
167
+
168
+ ### `setTextRenderer()`
169
+
170
+ `setTextRenderer('system')` draws text the way Chrome does on each OS: CoreText on macOS (with CoreText's installed fonts and fallback), Fontations on Linux and FreeType on Windows, plus each OS's text gamma, LCD text on opaque macOS canvases, Chrome's per-OS font families and the macOS metric rules. This was the default in 1.0.12 and 1.0.13. `'portable'` is the default.
171
+
172
+ ```js
173
+ const { setTextRenderer, getTextRenderer } = require('@napi-rs/canvas')
174
+
175
+ setTextRenderer('system')
176
+ getTextRenderer() // 'system'
177
+ ```
178
+
179
+ It applies to the whole process. Call it once at startup, before you create canvases. `setTextRenderer('portable')` switches back. Any other value throws a `TypeError`. A canvas created before the switch keeps its old text gamma until you set its width or height. A `ctx.font` value set before the switch keeps its parsed form (sizes in `ex`, `ch` and other font-relative units, and on macOS the `BlinkMacSystemFont` to `system-ui` rewrite), so set `ctx.font` again after the switch.
180
+
181
+ > **Behavior change** ([#1402](https://github.com/Brooooooklyn/canvas/issues/1402)): text on macOS and Linux is drawn the way Windows draws it, so pixels and `measureText` results differ from 1.0.12 and 1.0.13. On macOS, the installed fonts are FreeType faces again (as in 1.0.11), read from `/System/Library/Fonts`, `/Library/Fonts` and `~/Library/Fonts`; CoreText's per-character fallback is off, and `sans-serif`, `serif` and `monospace` map to Arial, Times New Roman and the Linux monospace list. On macOS, `DISABLE_SYSTEM_FONTS_LOAD` no longer hides installed fonts from name lookup, as on Linux and Windows and before 1.0.12; it only keeps them out of `GlobalFonts.families`. Call `setTextRenderer('system')` to draw with each OS's own font renderer and installed fonts again, as 1.0.12 and 1.0.13 did. Both renderers now shape text the way Chrome does, so the output is not the same as in 1.0.12 and 1.0.13.
182
+
183
+ > **Behavior change**: a character no font of the font list has is taken from an installed font, as Chrome's font cache takes it from the platform's fonts. This applies to the portable renderer on every OS and to the system renderer off macOS (on macOS, CoreText still picks first). The font is chosen the way Chrome on Windows chooses it: a bold or italic list first tries its first family at the normal style, then the font Windows names for the character's script (on macOS, the font Chrome's CoreText fallback names for it), then the first installed family, by name, that has the character. Before, such a character drew as a box (the first font's `.notdef`). Private use characters still draw as boxes.
184
+
185
+ > **Behavior change**: SVG text (SVG images, the SVG images nested in them, and `convertSVGTextToPath`) takes a character its first font lacks from the next family of its `font-family` list, then from the system's fonts, as canvas text does. Chrome does the same. Before, the whole text used the first font, and a character it lacks drew as a box. Text with no such character draws the same as before. `convertSVGTextToPath` now reads the document the way an SVG image does (`<style>` sheets, `var()`, `<use>` and the other CSS rules), so its output matches what the SVG image draws. A document that is not well-formed UTF-8 XML is read as before, with only the first family of each `font-family` list.
186
+
187
+ > **Behavior change**: the default `ctx.lang`, `'inherit'`, follows the host language, as Chrome does. Before, it meant no language. Text whose font has language-specific glyphs or spacing can draw and measure differently on hosts with different UI languages; set `ctx.lang` to pin it.
188
+
189
+ > **Behavior change**: SVG text whose family lacks the bold or italic face it asks for draws a synthetic bold or italic, with the rules canvas text uses: a registered font is bolded at `font-weight` 600 and up and slanted for `italic` and `oblique` unless it has that face (or a `wght` axis above 400, or a `slnt` axis); an installed font follows its own faces. Chrome does the same. The advances do not change, so the text does not move; the glyphs are heavier or slanted, and `convertSVGTextToPath` outlines grow to match. Before, SVG text drew the regular face for every style.
190
+
123
191
  ## Emoji text
124
192
 
125
193
  ```js
package/index.d.ts CHANGED
@@ -3,6 +3,44 @@ import { ReadableStream } from 'node:stream/web'
3
3
  // Clear all type of caches in Skia
4
4
  export function clearAllCache(): void
5
5
 
6
+ export type TextRenderer = 'portable' | 'system'
7
+
8
+ /**
9
+ * Selects how text is drawn.
10
+ *
11
+ * `'portable'` (the default) draws text the same way on every OS: FreeType,
12
+ * Skia's default text gamma, no LCD text and one font family table. Text gives
13
+ * the same pixels and TextMetrics on macOS, Linux and Windows when:
14
+ * - `ctx.font` names only fonts you registered (the default `10px sans-serif`
15
+ * and names like `Arial` use installed fonts, unless you registered a font
16
+ * under that name);
17
+ * - your registered fonts have every character you draw (a missing character
18
+ * comes from an installed font);
19
+ * - you set `ctx.lang` (for example `'en'`), because the default `'inherit'`
20
+ * follows the host language;
21
+ * - `FREETYPE_PROPERTIES` is not set.
22
+ * x64 and arm64 builds can still differ by 1 color level on some edge pixels.
23
+ *
24
+ * `'system'` draws text with each OS's own font renderer, as Chrome does:
25
+ * CoreText on macOS (Fontations for some color and variable fonts) with
26
+ * CoreText's installed fonts and fallback, Fontations on Linux, FreeType
27
+ * elsewhere; plus each OS's text gamma and contrast, LCD text on opaque macOS
28
+ * canvases, Chrome's per-OS font family settings and macOS metric rules.
29
+ *
30
+ * Applies to the whole process, worker threads included. Call it once at
31
+ * startup, before creating canvases. Setting the mode in use again changes
32
+ * nothing. Any other value throws a TypeError. Registered fonts keep their
33
+ * FontKey. A canvas created before the switch keeps its text gamma until its
34
+ * width or height is set. A `ctx.font` value set before the switch keeps its
35
+ * parsed form: sizes in `ex`/`ch`/... units and, on macOS, the
36
+ * BlinkMacSystemFont to system-ui rewrite. Set `ctx.font` again after the
37
+ * switch.
38
+ */
39
+ export function setTextRenderer(renderer: TextRenderer): void
40
+
41
+ /** The text renderer in use, set by {@link setTextRenderer}. */
42
+ export function getTextRenderer(): TextRenderer
43
+
6
44
  interface CanvasRenderingContext2D
7
45
  extends CanvasCompositing,
8
46
  CanvasDrawPath,
@@ -708,6 +746,48 @@ export interface SKRSContext2D extends CanvasRenderingContext2D {
708
746
 
709
747
  letterSpacing: string
710
748
  wordSpacing: string
749
+ /**
750
+ * Non-standard: Chrome's CanvasRenderingContext2D has no such attribute.
751
+ * Behaves like the CSS `font-feature-settings` property in Chrome.
752
+ * Invalid values are ignored. Default `"normal"`.
753
+ * @experimental
754
+ */
755
+ fontFeatureSettings: string
756
+ /**
757
+ * Non-standard: Chrome's CanvasRenderingContext2D has no such attribute.
758
+ * Behaves like the CSS `font-variant-ligatures` property in Chrome.
759
+ * Invalid values are ignored. Default `"normal"`.
760
+ * @experimental
761
+ */
762
+ fontVariantLigatures: string
763
+ /**
764
+ * Non-standard: Chrome's CanvasRenderingContext2D has no such attribute.
765
+ * Behaves like the CSS `font-variant-numeric` property in Chrome.
766
+ * Invalid values are ignored. Default `"normal"`.
767
+ * @experimental
768
+ */
769
+ fontVariantNumeric: string
770
+ /**
771
+ * Non-standard: Chrome's CanvasRenderingContext2D has no such attribute.
772
+ * Behaves like the CSS `font-variant-position` property in Chrome.
773
+ * Invalid values are ignored. Default `"normal"`.
774
+ * @experimental
775
+ */
776
+ fontVariantPosition: 'normal' | 'sub' | 'super'
777
+ /**
778
+ * Non-standard: Chrome's CanvasRenderingContext2D has no such attribute.
779
+ * Behaves like the CSS `font-size-adjust` property in Chrome.
780
+ * Invalid values are ignored. Default `"none"`.
781
+ * @experimental
782
+ */
783
+ fontSizeAdjust: string
784
+ /**
785
+ * Non-standard: Chrome's CanvasRenderingContext2D has no such attribute.
786
+ * Behaves like the CSS `font-optical-sizing` property in Chrome.
787
+ * Invalid values are ignored. Default `"auto"`.
788
+ * @experimental
789
+ */
790
+ fontOpticalSizing: 'auto' | 'none'
711
791
  }
712
792
 
713
793
  export type ColorSpace = 'srgb' | 'display-p3'
package/index.js CHANGED
@@ -26,6 +26,8 @@ const {
26
26
  _argumentCounts,
27
27
  _imageDataConstructor,
28
28
  _toNumber: toNumber,
29
+ setTextRenderer,
30
+ getTextRenderer,
29
31
  } = require('./js-binding')
30
32
 
31
33
  const { DOMPoint, DOMMatrix, DOMRect } = require('./geometry')
@@ -702,24 +704,24 @@ class Canvas {
702
704
  }
703
705
 
704
706
  if (!process.env.DISABLE_SYSTEM_FONTS_LOAD) {
705
- // macOS: CoreText's installed fonts, user fonts included, listed at the
706
- // first font lookup.
707
- GlobalFonts.enableSystemFonts()
708
- const platformName = platform()
707
+ // Installed fonts and the per-user font folders: FreeType by default;
708
+ // CoreText's on macOS after setTextRenderer('system'). On macOS that is
709
+ // /Library/Fonts (all users) and ~/Library/Fonts, after
710
+ // /System/Library/Fonts, as CoreText lists them.
709
711
  const homedirPath = homedir()
710
- switch (platformName) {
711
- case 'win32':
712
- GlobalFonts.loadFontsFromDir(join(homedirPath, 'AppData', 'Local', 'Microsoft', 'Windows', 'Fonts'))
713
- break
714
- case 'linux':
715
- GlobalFonts.loadFontsFromDir(join('usr', 'local', 'share', 'fonts'))
716
- GlobalFonts.loadFontsFromDir(join(homedirPath, '.fonts'))
717
- break
718
- }
712
+ const userFontDirs =
713
+ {
714
+ win32: [join(homedirPath, 'AppData', 'Local', 'Microsoft', 'Windows', 'Fonts')],
715
+ darwin: ['/Library/Fonts', join(homedirPath, 'Library', 'Fonts')],
716
+ linux: [join('usr', 'local', 'share', 'fonts'), join(homedirPath, '.fonts')],
717
+ }[platform()] ?? []
718
+ GlobalFonts.enableSystemFonts(userFontDirs)
719
719
  }
720
720
 
721
721
  module.exports = {
722
722
  clearAllCache,
723
+ setTextRenderer,
724
+ getTextRenderer,
723
725
  Canvas,
724
726
  createCanvas,
725
727
  Path2D,
package/js-binding.js CHANGED
@@ -455,6 +455,8 @@ module.exports.SVGCanvas = nativeBinding.SVGCanvas
455
455
  module.exports.ChromaSubsampling = nativeBinding.ChromaSubsampling
456
456
  module.exports.clearAllCache = nativeBinding.clearAllCache
457
457
  module.exports.convertSVGTextToPath = nativeBinding.convertSVGTextToPath
458
+ module.exports.setTextRenderer = nativeBinding.setTextRenderer
459
+ module.exports.getTextRenderer = nativeBinding.getTextRenderer
458
460
  module.exports.FillType = nativeBinding.FillType
459
461
  module.exports.PathOp = nativeBinding.PathOp
460
462
  module.exports.StrokeCap = nativeBinding.StrokeCap
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napi-rs/canvas",
3
- "version": "1.0.13",
3
+ "version": "1.1.1",
4
4
  "description": "Canvas for Node.js with skia backend",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -71,6 +71,7 @@
71
71
  "test:ci": "NODE_OPTIONS='--import=core-js/proposals/iterator-helpers.js' ava -c 1",
72
72
  "test": "NODE_OPTIONS='--import=core-js/proposals/iterator-helpers.js' ava",
73
73
  "e2e": "yarn workspace @napi-rs/canvas-e2e-webpack test",
74
+ "e2e:electron": "yarn workspace @napi-rs/canvas-e2e-electron test",
74
75
  "version": "napi version && conventional-changelog -p angular -i CHANGELOG.md -s && git add ."
75
76
  },
76
77
  "devDependencies": {
@@ -82,11 +83,11 @@
82
83
  "@napi-rs/webcodecs": "^1.1.1",
83
84
  "@node-rs/crc32": "^1.10.6",
84
85
  "@octokit/rest": "^22.0.1",
85
- "@oxc-node/cli": "^0.1.0",
86
- "@oxc-node/core": "^0.1.0",
86
+ "@oxc-node/cli": "^1.0.0",
87
+ "@oxc-node/core": "^1.0.0",
87
88
  "@taplo/cli": "^0.7.0",
88
89
  "@types/lodash": "^4.17.21",
89
- "@types/node": "^25.0.3",
90
+ "@types/node": "^26.0.0",
90
91
  "@types/semver": "^7",
91
92
  "ava": "^8.0.0",
92
93
  "canvas": "^3.2.0",
@@ -95,14 +96,14 @@
95
96
  "conventional-changelog-cli": "^5.0.0",
96
97
  "core-js": "^3.47.0",
97
98
  "echarts": "^6.0.0",
98
- "electron": "^44.0.0",
99
+ "electron": "^45.0.0",
99
100
  "husky": "^9.1.7",
100
101
  "lint-staged": "^17.0.0",
101
102
  "lodash": "^4.17.21",
102
103
  "npm-run-all2": "^9.0.0",
103
104
  "oxlint": "^1.34.0",
104
105
  "oxlint-tsgolint": "^7.0.0",
105
- "pdfjs-dist": "6.4.299",
106
+ "pdfjs-dist": "6.5.438",
106
107
  "pinst": "^3.0.0",
107
108
  "png.js": "^0.2.1",
108
109
  "prettier": "^3.7.4",
@@ -161,16 +162,16 @@
161
162
  "@napi-rs/canvas": "workspace:"
162
163
  },
163
164
  "optionalDependencies": {
164
- "@napi-rs/canvas-linux-x64-gnu": "1.0.13",
165
- "@napi-rs/canvas-darwin-x64": "1.0.13",
166
- "@napi-rs/canvas-win32-x64-msvc": "1.0.13",
167
- "@napi-rs/canvas-win32-arm64-msvc": "1.0.13",
168
- "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.13",
169
- "@napi-rs/canvas-linux-x64-musl": "1.0.13",
170
- "@napi-rs/canvas-linux-arm64-gnu": "1.0.13",
171
- "@napi-rs/canvas-linux-arm64-musl": "1.0.13",
172
- "@napi-rs/canvas-darwin-arm64": "1.0.13",
173
- "@napi-rs/canvas-android-arm64": "1.0.13",
174
- "@napi-rs/canvas-linux-riscv64-gnu": "1.0.13"
165
+ "@napi-rs/canvas-linux-x64-gnu": "1.1.1",
166
+ "@napi-rs/canvas-darwin-x64": "1.1.1",
167
+ "@napi-rs/canvas-win32-x64-msvc": "1.1.1",
168
+ "@napi-rs/canvas-win32-arm64-msvc": "1.1.1",
169
+ "@napi-rs/canvas-linux-arm-gnueabihf": "1.1.1",
170
+ "@napi-rs/canvas-linux-x64-musl": "1.1.1",
171
+ "@napi-rs/canvas-linux-arm64-gnu": "1.1.1",
172
+ "@napi-rs/canvas-linux-arm64-musl": "1.1.1",
173
+ "@napi-rs/canvas-darwin-arm64": "1.1.1",
174
+ "@napi-rs/canvas-android-arm64": "1.1.1",
175
+ "@napi-rs/canvas-linux-riscv64-gnu": "1.1.1"
175
176
  }
176
- }
177
+ }