@effing/canvas 0.41.1 โ†’ 0.43.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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  > Part of the [**Effing**](../../README.md) family โ€” programmatic video creation with TypeScript.
6
6
 
7
- A canvas that can render JSX elements and Lottie animations, powered by Skia (via [@napi-rs/canvas](https://github.com/Brooooooklyn/canvas)) and Yoga flexbox layout. Supports emoji, font management, and CSS properties including transforms, gradients, and text effects.
7
+ A canvas that can render JSX elements and Lottie animations, powered by Skia (via [@effing/skia](https://github.com/builtbyfew/effing-skia), Effing's fork of [@napi-rs/canvas](https://github.com/Brooooooklyn/canvas)) and Yoga flexbox layout. Supports emoji, font management, and CSS properties including transforms, gradients, and text effects.
8
8
 
9
9
  ## Installation
10
10
 
@@ -12,11 +12,7 @@ A canvas that can render JSX elements and Lottie animations, powered by Skia (vi
12
12
  npm install @effing/canvas
13
13
  ```
14
14
 
15
- Requires the `@napi-rs/canvas` peer dependency (typically installed automatically though):
16
-
17
- ```bash
18
- npm install @napi-rs/canvas
19
- ```
15
+ The Skia backend, `@effing/skia`, is installed along with it. It ships prebuilt binaries for Linux x64 and arm64 (glibc and musl), macOS x64 and arm64, and Windows x64.
20
16
 
21
17
  ## Quick Start
22
18
 
@@ -91,7 +87,7 @@ Emoji characters are automatically rendered as images from CDNs. Supported style
91
87
  | `fluent` | Microsoft Fluent Emoji (color) |
92
88
  | `fluentFlat` | Microsoft Fluent Emoji (flat) |
93
89
 
94
- Pass `emoji: "none"` to disable emoji image rendering.
90
+ An emoji is an emoji character ([UTS #51](https://www.unicode.org/reports/tr51/)), with or without U+FE0F (`๐ŸŒ`, `โค`, `โค๏ธ`, `๐Ÿ•ต`), a keycap (`1๏ธโƒฃ`), a flag (`๐Ÿ‡ง๐Ÿ‡ช`, `๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ`), a skin-tone sequence (`๐Ÿ‘๐Ÿฝ`) or a ZWJ sequence of emoji (`๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง`). Emoji characters are drawn as images even when Unicode presents them as text by default, except `ยฉ`, `ยฎ` and `โ„ข`, which stay text unless U+FE0F follows them, and digits, `#` and `*`, which stay text unless a keycap follows them, as browsers draw them; U+FE0E keeps any emoji character text. For text-default emoji in the Basic Multilingual Plane, such as `โค` or `โ˜Ž`, this differs from browsers, which draw them as monochrome text through the system's fallback fonts: canvas has no such fallback, so an image beats a missing glyph. Text-default emoji outside it, such as `๐Ÿ•ต` or `๐Ÿณ`, are colour emoji in browsers too. Characters with no emoji form, such as `โœ“` or `โ—`, are text. Each emoji takes an inline box of 1em on its line, with its bottom 0.1em below the baseline, about where browsers draw an emoji glyph. Lines break around it as browsers break them around an emoji, so it stays with the punctuation next to it (`๐ŸŽ‰!`, `(๐ŸŽ‰)`). Pass `emoji: "none"` to disable emoji image rendering. An emoji drawn without an image โ€” with `emoji: "none"`, one the style has no image for (the Fluent styles have no flags), or one whose image failed to load โ€” is drawn as text, so it needs a font that covers it among the text's font families (every font given to `renderReactElement` is one), as do text symbols.
95
91
 
96
92
  ## Fit Text
97
93
 
@@ -108,7 +104,7 @@ const fontSize = findLargestUsableFontSize({
108
104
  });
109
105
  ```
110
106
 
111
- By default the text wraps to `maxWidth` and is fit into the `maxWidth` ร— `maxHeight` box. Pass `whiteSpace: "nowrap"` to fit the text on one line (per newline-separated paragraph) instead, where `maxWidth` constrains the full line width:
107
+ By default the text wraps to `maxWidth` and is fit into the `maxWidth` ร— `maxHeight` box. Pass `whiteSpace: "nowrap"` to fit the text on one line instead (or `"pre"` for one line per newline-separated paragraph), where `maxWidth` constrains the full line width:
112
108
 
113
109
  ```typescript
114
110
  const fontSize = findLargestUsableFontSize({
@@ -120,7 +116,7 @@ const fontSize = findLargestUsableFontSize({
120
116
  });
121
117
  ```
122
118
 
123
- Supports optional `lineHeight` (`"normal"` or a numeric multiplier), `whiteSpace` (`"normal"` by default; `"nowrap"`/`"pre"` for one line per paragraph), `minFontSize` (default 1), and `maxFontSize` (default 1000).
119
+ Supports optional `lineHeight` (`"normal"` or a numeric multiplier), `whiteSpace` (`"normal"` by default; `"nowrap"` for one line, `"pre"` for one line per paragraph), `minFontSize` (default 1), and `maxFontSize` (default 1000).
124
120
 
125
121
  ## Inline SVG
126
122
 
@@ -249,27 +245,34 @@ const png = canvas.encodeSync("png");
249
245
 
250
246
  ### Typography
251
247
 
252
- | Property | Values / Notes |
253
- | ----------------------- | ------------------------------------------------------------- |
254
- | `fontFamily` | Font name (inherited) |
255
- | `fontSize` | Number or CSS length (inherited) |
256
- | `fontWeight` | Numeric weight (inherited) |
257
- | `fontStyle` | `normal`, `italic` (inherited) |
258
- | `textAlign` | `left`, `center`, `right`, `justify` (inherited) |
259
- | `textDecoration` | String (inherited) |
260
- | `textTransform` | `none`, `uppercase`, `lowercase`, `capitalize` (inherited) |
261
- | `lineHeight` | Number or string (inherited) |
262
- | `letterSpacing` | Number or CSS length (inherited) |
263
- | `whiteSpace` | `normal`, `nowrap`, `pre`, `pre-wrap`, `pre-line` (inherited) |
264
- | `wordBreak` | `normal`, `break-all`, `break-word`, `keep-all` (inherited) |
265
- | `textOverflow` | `clip`, `ellipsis` (inherited) |
266
- | `lineClamp` | Number โ€” max visible lines (adds ellipsis) |
267
- | `textBox` | Shorthand for `textBoxTrim` and `textBoxEdge` |
268
- | `textBoxTrim` | `none`, `trim-start`, `trim-end`, `trim-both` (inherited) |
269
- | `textBoxEdge` | e.g. `"cap alphabetic"` (inherited) |
270
- | `WebkitTextStroke` | Shorthand, e.g. `"2px red"` (inherited) |
271
- | `WebkitTextStrokeWidth` | CSS length (inherited) |
272
- | `WebkitTextStrokeColor` | Any CSS color (inherited) |
248
+ | Property | Values / Notes |
249
+ | ----------------------- | -------------------------------------------------------------- |
250
+ | `fontFamily` | Font name (inherited) |
251
+ | `fontSize` | Number or CSS length (inherited) |
252
+ | `fontWeight` | Numeric weight (inherited) |
253
+ | `fontStyle` | `normal`, `italic` (inherited) |
254
+ | `textAlign` | `left`, `center`, `right`, `justify` (inherited) |
255
+ | `textDecoration` | String (inherited) |
256
+ | `textTransform` | `none`, `uppercase`, `lowercase`, `capitalize` (inherited) |
257
+ | `lineHeight` | Number or string (inherited) |
258
+ | `letterSpacing` | Number or CSS length (inherited) |
259
+ | `whiteSpace` | `normal`, `nowrap`, `pre`, `pre-wrap`, `pre-line` (inherited)ยน |
260
+ | `wordBreak` | `normal`, `break-all`, `keep-all`, `break-word`ยฒ (inherited) |
261
+ | `overflowWrap` | `normal`, `break-word`, `anywhere`ยฒ (inherited) |
262
+ | `textOverflow` | `clip`, `ellipsis` (inherited) |
263
+ | `lineClamp` | Number โ€” max visible lines (adds ellipsis)ยณ |
264
+ | `textBox` | Shorthand for `textBoxTrim` and `textBoxEdge` |
265
+ | `textBoxTrim` | `none`, `trim-start`, `trim-end`, `trim-both` (inherited) |
266
+ | `textBoxEdge` | e.g. `"cap alphabetic"` (inherited) |
267
+ | `WebkitTextStroke` | Shorthand, e.g. `"2px red"` (inherited) |
268
+ | `WebkitTextStrokeWidth` | CSS length (inherited) |
269
+ | `WebkitTextStrokeColor` | Any CSS color (inherited) |
270
+
271
+ ยน White space is processed as in CSS: under `normal`, `nowrap` and `pre-line`, a run of spaces and tabs collapses to one space, the spaces at the start and end of the text and around a newline are removed, and a newline becomes a space (`pre-line` keeps it as a line break). Adjacent strings, as `Hello {name}` gives, are one run of text, and each run of text between elements is laid out as a flex item of its own, as browsers lay out text in a flex container: a run of nothing but white space, such as `{" "}` between two elements, isn't rendered, and an element whose only text is empty or white space has no line box. Fragments, arrays and function components are unwrapped first, as the DOM has no trace of them. A `<br />` is an item of its own, not a forced break inside a run of text: in a column it puts the text after it on the next line, but in a row (the default `flexDirection`) the text either side of it sits side by side, where browsers stack it. A line or paragraph separator (U+2028, U+2029) is a space with a break opportunity after it, as in browsers, but with `letterSpacing` it gets the spacing twice where browsers add it once. `pre` and `pre-wrap` keep the spaces before a line break in the line's width and alignment, as browsers do; a tab is one space wide. A CRLF counts as one newline, and `pre` and `pre-wrap` drop a lone CR, which browsers draw as nothing.
272
+
273
+ ยฒ A word wider than its line overflows it, as in CSS, unless `overflowWrap` is `break-word` or `anywhere` (or `wordBreak` the deprecated `break-word`): the word then starts a line of its own and is broken between grapheme clusters where that line is full. `anywhere` breaks as `break-word` does.
274
+
275
+ ยณ The last line shown is its own text with an ellipsis after it, cut to fit, as in browsers. Under `whiteSpace: "pre"`, a clamped line that ends at a newline gets no ellipsis, where browsers add one. Canvas aligns the clamped line with its ellipsis, where browsers align its text alone and let the ellipsis overflow (hidden by `overflow: hidden`): right-aligned, `"ab\ncd"` clamped to one line is "abโ€ฆ" at the right edge, where browsers show "ab" there.
273
276
 
274
277
  ### Effects
275
278
 
@@ -289,6 +292,17 @@ const png = canvas.encodeSync("png");
289
292
  | ----------- | ------------------------------------------------ |
290
293
  | `objectFit` | `contain`, `cover`, `fill`, `none`, `scale-down` |
291
294
 
295
+ ### Opacity and filters
296
+
297
+ `opacity` and `filter` apply to an element and its descendants as one group,
298
+ as in CSS: overlapping children fade together instead of showing through each
299
+ other, and a `drop-shadow()` is cast once by the whole group.
300
+
301
+ A filter covers the element as painted, so `blur()` softens its edge too,
302
+ rounded corners included. To keep the edge sharp, clip after the filter: give
303
+ the element a `clipPath` (for example `inset(0px round 8px)`), or wrap it in an
304
+ element with `overflow: hidden`.
305
+
292
306
  ### Clip paths
293
307
 
294
308
  `clipPath` clips the element's entire rendering โ€” background, borders,
@@ -347,10 +361,9 @@ pixels of the element and follow its `transform`, including non-uniform
347
361
  scales and skews. At the canvas edge the backdrop is extended outward, like a
348
362
  browser clamps it at the viewport edge, so a blur stays uniform there.
349
363
 
350
- One difference from browsers: `opacity` is applied per drawing operation, not
351
- to the element's subtree as a group. Under an ancestor with `opacity` below 1
352
- the filtered backdrop is painted back with that opacity, so it blends with the
353
- unfiltered backdrop instead of the ancestor forming its own backdrop root.
364
+ As in browsers, an ancestor with `opacity` below 1 or a `filter` is a backdrop
365
+ root: an element inside it sees only what that ancestor has painted so far,
366
+ not what lies behind the ancestor.
354
367
 
355
368
  ### Units
356
369
 
@@ -360,7 +373,7 @@ unfiltered backdrop instead of the ancestor forming its own backdrop root.
360
373
 
361
374
  ### `createCanvas(width, height)`
362
375
 
363
- Create a new canvas. Re-exported from `@napi-rs/canvas`.
376
+ Create a new canvas. Re-exported from `@effing/skia`.
364
377
 
365
378
  ```typescript
366
379
  function createCanvas(width: number, height: number): Canvas;
@@ -402,7 +415,7 @@ for (let frame = 0; frame < frameCount; frame++) {
402
415
 
403
416
  ### `loadImage(source, options?)`
404
417
 
405
- Load an image from a path, Buffer, data URI, or remote URL. Remote `http`/`https` URLs are fetched via the global `fetch()` โ€” the same path `<img>` sources take in `renderReactElement` โ€” so a global dispatcher / proxy (e.g. undici's `setGlobalDispatcher`) and the `userAgent` option are honored. All other sources delegate to `@napi-rs/canvas`'s native loader.
418
+ Load an image from a path, Buffer, data URI, or remote URL. Remote `http`/`https` URLs are fetched via the global `fetch()` โ€” the same path `<img>` sources take in `renderReactElement` โ€” so a global dispatcher / proxy (e.g. undici's `setGlobalDispatcher`) and the `userAgent` option are honored. All other sources delegate to `@effing/skia`'s native loader.
406
419
 
407
420
  ```typescript
408
421
  function loadImage(
@@ -504,7 +517,7 @@ type EmojiStyle =
504
517
 
505
518
  ### Canvas Primitives
506
519
 
507
- The building blocks you need alongside a context are re-exported from `@napi-rs/canvas`, so you never need to import (or declare a dependency on) `@napi-rs/canvas` directly: `Canvas`, `SKRSContext2D`, `GlobalFonts`, `Image`, `ImageData`, `Path2D`, `DOMMatrix`, `DOMPoint`, `DOMRect`, `PathOp`, `FillType`, `StrokeJoin`, `StrokeCap`, and `LottieAnimation`. (`loadImage` is wrapped โ€” see above โ€” not re-exported directly.)
520
+ The building blocks you need alongside a context are re-exported from `@effing/skia`, so you never need to import (or declare a dependency on) `@effing/skia` directly: `Canvas`, `SKRSContext2D`, `GlobalFonts`, `Image`, `ImageData`, `Path2D`, `DOMMatrix`, `DOMPoint`, `DOMRect`, `PathOp`, `FillType`, `StrokeJoin`, `StrokeCap`, and `LottieAnimation`. (`loadImage` is wrapped โ€” see above โ€” not re-exported directly.)
508
521
 
509
522
  ```typescript
510
523
  import { createCanvas, Path2D } from "@effing/canvas";
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import * as _napi_rs_canvas from '@napi-rs/canvas';
2
- import { Image, loadImage as loadImage$1, LottieAnimation, SKRSContext2D } from '@napi-rs/canvas';
3
- export { Canvas, DOMMatrix, DOMPoint, DOMRect, FillType, GlobalFonts, Image, ImageData, LottieAnimation, Path2D, PathOp, SKRSContext2D, StrokeCap, StrokeJoin } from '@napi-rs/canvas';
1
+ import * as _effing_skia from '@effing/skia';
2
+ import { Image, loadImage as loadImage$1, LottieAnimation, SKRSContext2D } from '@effing/skia';
3
+ export { Canvas, DOMMatrix, DOMPoint, DOMRect, FillType, GlobalFonts, Image, ImageData, LottieAnimation, Path2D, PathOp, SKRSContext2D, StrokeCap, StrokeJoin } from '@effing/skia';
4
4
  import { ReactNode } from 'react';
5
5
 
6
- /** Source types accepted by {@link loadImage}, mirroring @napi-rs/canvas. */
6
+ /** Source types accepted by {@link loadImage}, mirroring @effing/skia. */
7
7
  type LoadImageSource = Parameters<typeof loadImage$1>[0];
8
8
  /** Options for {@link loadImage}. */
9
9
  interface LoadImageOptions {
@@ -23,11 +23,11 @@ interface LoadImageOptions {
23
23
  * native loader as bytes โ€” the same path `<img>` sources take in
24
24
  * `renderReactElement`. This keeps `loadImage(url)` and `<img src={url}>`
25
25
  * consistent: both honor a global dispatcher / proxy (undici's
26
- * setGlobalDispatcher) and the `userAgent` option. @napi-rs/canvas's own URL
26
+ * setGlobalDispatcher) and the `userAgent` option. @effing/skia's own URL
27
27
  * loader uses Node's raw http modules, which bypass any dispatcher, so it is
28
28
  * only used for non-remote sources here.
29
29
  *
30
- * Note: @napi-rs/canvas's `maxRedirects` / `requestOptions` load options are
30
+ * Note: @effing/skia's `maxRedirects` / `requestOptions` load options are
31
31
  * intentionally not exposed โ€” they only configure its built-in URL loader,
32
32
  * which this wrapper bypasses. Control remote fetches via fetch/undici instead.
33
33
  */
@@ -277,6 +277,7 @@ type ComputedStyle = {
277
277
  letterSpacing?: number | string;
278
278
  whiteSpace?: "normal" | "nowrap" | "pre" | "pre-wrap" | "pre-line";
279
279
  wordBreak?: "normal" | "break-all" | "break-word" | "keep-all";
280
+ overflowWrap?: "normal" | "break-word" | "anywhere";
280
281
  textOverflow?: "clip" | "ellipsis";
281
282
  lineClamp?: number;
282
283
  boxShadow?: string;
@@ -308,9 +309,10 @@ type FindLargestUsableFontSizeOptions = {
308
309
  /** Line height โ€” `"normal"` uses font metrics, numeric values are CSS multipliers */
309
310
  lineHeight?: number | "normal";
310
311
  /**
311
- * Whitespace handling, mirroring CSS. Use `"nowrap"` (or `"pre"`) to fit text
312
- * on one line (per newline-separated paragraph) instead of wrapping to
313
- * `maxWidth` (default: `"normal"`).
312
+ * Whitespace handling, as in CSS (default: `"normal"`). Use `"nowrap"` to
313
+ * fit the text on one line instead of wrapping to `maxWidth`, its newlines
314
+ * collapsing to spaces, or `"pre"` to fit each newline-separated paragraph
315
+ * on a line of its own.
314
316
  */
315
317
  whiteSpace?: ComputedStyle["whiteSpace"];
316
318
  /** Minimum font size to consider (default: 1) */
@@ -325,12 +327,12 @@ type FindLargestUsableFontSizeOptions = {
325
327
  * at each step. Returns `minFontSize` if even the smallest size overflows.
326
328
  *
327
329
  * By default text wraps to `maxWidth` and is fit into the `maxWidth` ร— `maxHeight`
328
- * box. Set `whiteSpace: "nowrap"` to fit the text on one line (per
329
- * newline-separated paragraph) instead, in which case `maxWidth` constrains the
330
- * full line width.
330
+ * box. Set `whiteSpace: "nowrap"` to fit the text on one line instead (or
331
+ * `"pre"` for one line per newline-separated paragraph), in which case
332
+ * `maxWidth` constrains the full line width.
331
333
  */
332
334
  declare function findLargestUsableFontSize(options: FindLargestUsableFontSizeOptions): number;
333
335
 
334
- declare function createCanvas(width: number, height: number): _napi_rs_canvas.Canvas;
336
+ declare function createCanvas(width: number, height: number): _effing_skia.Canvas;
335
337
 
336
338
  export { type EmojiStyle, type FindLargestUsableFontSizeOptions, type FontData, type ImageCache, type LoadImageOptions, type LoadImageSource, type RenderReactElementOptions, createCanvas, findLargestUsableFontSize, loadImage, loadLottie, registerFont, registerFontFromPath, registeredFamilies, renderLottieFrame, renderReactElement };