@effing/canvas 0.23.1 → 0.24.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
- Render React JSX elements and Lottie animations directly to a canvas using [@napi-rs/canvas](https://github.com/nicolo-ribaudo/napi-rs-canvas) (Rust-based Skia bindings). Includes Yoga flex layout, emoji support, font management, and frame-level Lottie rendering.
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.
8
8
 
9
9
  ## Installation
10
10
 
@@ -57,21 +57,7 @@ const png = await canvas.encode("png");
57
57
  await fs.writeFile("output.png", png);
58
58
  ```
59
59
 
60
- ## Concepts
61
-
62
- ### Rendering Pipeline
63
-
64
- ```
65
- JSX → Yoga layout → Skia canvas → PNG
66
- ```
67
-
68
- 1. **Yoga** calculates flex layout from your JSX tree (flexbox, positioning, text measurement)
69
- 2. **Skia** draws each node to a canvas context (backgrounds, borders, text, images, SVG, gradients)
70
- 3. **Encode** the canvas to PNG or JPEG via `canvas.encode()` or `canvas.encodeSync()`
71
-
72
- Canvas dimensions default to `ctx.canvas.width` / `ctx.canvas.height`, but you can override them with the `width` and `height` options. This is useful for HiDPI rendering where the canvas is larger than the logical layout size.
73
-
74
- ### Font Loading
60
+ ## Font Loading
75
61
 
76
62
  Fonts must be registered before rendering text. You can pass them via the `fonts` option (registered automatically), or register them manually:
77
63
 
@@ -92,7 +78,7 @@ registerFontFromPath("./fonts/Inter-Bold.ttf", "Inter");
92
78
  console.log(registeredFamilies()); // ["Inter", ...]
93
79
  ```
94
80
 
95
- ### Emoji Support
81
+ ## Emoji Support
96
82
 
97
83
  Emoji characters are automatically rendered as images from CDNs. Supported styles:
98
84
 
@@ -107,7 +93,24 @@ Emoji characters are automatically rendered as images from CDNs. Supported style
107
93
 
108
94
  Pass `emoji: "none"` to disable emoji image rendering.
109
95
 
110
- ### Lottie Animations
96
+ ## Fit Text
97
+
98
+ Find the largest font size that keeps text within a bounding box, using binary search over the built-in text layout engine:
99
+
100
+ ```typescript
101
+ import { findLargestUsableFontSize } from "@effing/canvas";
102
+
103
+ const fontSize = findLargestUsableFontSize({
104
+ text: "Hello World",
105
+ font,
106
+ maxWidth: 500,
107
+ maxHeight: 100,
108
+ });
109
+ ```
110
+
111
+ Supports optional `lineHeight` (`"normal"` or a numeric multiplier), `minFontSize` (default 1), and `maxFontSize` (default 1000).
112
+
113
+ ## Lottie Animations
111
114
 
112
115
  Render individual frames of Lottie animations to a canvas:
113
116
 
@@ -249,7 +252,7 @@ function createCanvas(width: number, height: number): Canvas;
249
252
  Render a React element tree to a canvas context.
250
253
 
251
254
  ```typescript
252
- function renderReactElement(
255
+ async function renderReactElement(
253
256
  ctx: SKRSContext2D,
254
257
  element: ReactNode,
255
258
  options: RenderReactElementOptions,
@@ -279,6 +282,30 @@ function renderLottieFrame(
279
282
  ): void;
280
283
  ```
281
284
 
285
+ ### `findLargestUsableFontSize(options)`
286
+
287
+ Find the largest integer font size that keeps text within the given bounds. Uses binary search with the built-in text layout engine — no external dependencies needed.
288
+
289
+ ```typescript
290
+ function findLargestUsableFontSize(
291
+ options: FindLargestUsableFontSizeOptions,
292
+ ): number;
293
+ ```
294
+
295
+ ```typescript
296
+ import { findLargestUsableFontSize } from "@effing/canvas";
297
+
298
+ const fontSize = findLargestUsableFontSize({
299
+ text: "Hello World",
300
+ font,
301
+ maxWidth: 500,
302
+ maxHeight: 100,
303
+ lineHeight: "normal", // optional (default: "normal")
304
+ minFontSize: 1, // optional (default: 1)
305
+ maxFontSize: 200, // optional (default: 1000)
306
+ });
307
+ ```
308
+
282
309
  ### Font Helpers
283
310
 
284
311
  - `registerFont(font)` — Register a font from a `FontData` buffer (idempotent)
@@ -389,27 +416,7 @@ await renderReactElement(ctx, <MyComponent />, {
389
416
  });
390
417
  ```
391
418
 
392
- ### HiDPI Rendering
393
-
394
- For sharp output on high-DPI displays, you can create a larger canvas and use `width`/`height` overrides to keep layout at the logical size:
395
-
396
- ```typescript
397
- const dpr = 2;
398
- const canvas = createCanvas(1080 * dpr, 1080 * dpr);
399
- const ctx = canvas.getContext("2d");
400
- ctx.scale(dpr, dpr);
401
-
402
- await renderReactElement(ctx, <MyComponent />, {
403
- fonts,
404
- width: 1080,
405
- height: 1080,
406
- });
407
-
408
- const png = await canvas.encode("png");
409
- ```
410
-
411
419
  ## Related Packages
412
420
 
413
421
  - [`@effing/tween`](../tween) — Step iteration and easing for frame generation
414
422
  - [`@effing/annie`](../annie) — Package rendered frames into animations
415
- - [`@effing/satori`](../satori) — Alternative JSX-to-PNG renderer (Satori + Resvg)
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ type EmojiStyle = "twemoji" | "openmoji" | "blobmoji" | "noto" | "fluent" | "flu
49
49
 
50
50
  /**
51
51
  * Font data for text rendering.
52
- * Compatible with `@effing/satori`'s FontData type.
52
+
53
53
  */
54
54
  type FontData = {
55
55
  name: string;
@@ -133,6 +133,33 @@ declare function registerFontFromPath(path: string, nameAlias?: string): void;
133
133
  */
134
134
  declare function registeredFamilies(): string[];
135
135
 
136
+ /**
137
+ * Options for {@link findLargestUsableFontSize}.
138
+ */
139
+ type FindLargestUsableFontSizeOptions = {
140
+ /** The text to fit */
141
+ text: string;
142
+ /** Font data to use for measurement */
143
+ font: FontData;
144
+ /** Maximum width in pixels */
145
+ maxWidth: number;
146
+ /** Maximum height in pixels */
147
+ maxHeight: number;
148
+ /** Line height — `"normal"` uses font metrics, numeric values are CSS multipliers */
149
+ lineHeight?: number | "normal";
150
+ /** Minimum font size to consider (default: 1) */
151
+ minFontSize?: number;
152
+ /** Maximum font size to consider (default: 1000) */
153
+ maxFontSize?: number;
154
+ };
155
+ /**
156
+ * Find the largest integer font size that keeps text within the given bounds.
157
+ *
158
+ * Uses binary search over integer font sizes, measuring with {@link layoutText}
159
+ * at each step. Returns `minFontSize` if even the smallest size overflows.
160
+ */
161
+ declare function findLargestUsableFontSize(options: FindLargestUsableFontSizeOptions): number;
162
+
136
163
  declare function createCanvas(width: number, height: number): _napi_rs_canvas.Canvas;
137
164
 
138
- export { type EmojiStyle, type FontData, type RenderReactElementOptions, createCanvas, loadLottie, registerFont, registerFontFromPath, registeredFamilies, renderLottieFrame, renderReactElement };
165
+ export { type EmojiStyle, type FindLargestUsableFontSizeOptions, type FontData, type RenderReactElementOptions, createCanvas, findLargestUsableFontSize, loadLottie, registerFont, registerFontFromPath, registeredFamilies, renderLottieFrame, renderReactElement };