kittyhtml 0.2.1 → 0.3.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,7 @@
2
2
 
3
3
  Render HTML to an image and display it inline in a graphics-capable terminal (Kitty, WezTerm, Ghostty, iTerm2).
4
4
 
5
- This is **not** a headless browser. It's a thin CLI that pipes HTML through [DropFlow](https://github.com/chearon/dropflow) (a real CSS layout engine, no JS/no Chromium) to a PNG, then emits the Kitty graphics protocol or iTerm2 inline-image protocol on stdout.
5
+ This is **not** a headless browser. It's a thin CLI that pipes HTML through [Blitz](https://github.com/DioxusLabs/blitz) a Rust HTML/CSS engine (Stylo + Taffy + Parley + Vello) to a PNG, then emits the Kitty graphics protocol or iTerm2 inline-image protocol on stdout. Headless CPU rasterization, no GPU required.
6
6
 
7
7
  Built for AI agents that have something nice to show you — a styled report, a small table, a card — without taking over your screen with a browser.
8
8
 
@@ -18,7 +18,7 @@ Or one-shot, no install:
18
18
  npx kittyhtml --demo
19
19
  ```
20
20
 
21
- Requires Node 20+. Pulls in [`@napi-rs/canvas`](https://www.npmjs.com/package/@napi-rs/canvas) (prebuilt native binary, no compile step) and `dropflow`. Two deps total, ~90 KB tarball.
21
+ Requires Node 20+. The native renderer ships as a prebuilt N-API binary per platform (macOS arm64, Linux x64). No Rust toolchain required at install time.
22
22
 
23
23
  ## Use
24
24
 
@@ -49,32 +49,13 @@ const png = await renderHtml('<h1>hello</h1>', { width: 400, scale: 2 });
49
49
  process.stdout.write(encode(png, 'kitty'));
50
50
  ```
51
51
 
52
- ## Releasing
53
-
54
- Releases publish via GitHub Actions using npm trusted publishing (OIDC, no long-lived token). To cut a release:
55
-
56
- ```sh
57
- npm version patch # or minor / major — bumps package.json and tags
58
- git push --follow-tags
59
- ```
60
-
61
- The `Publish to npm` workflow fires on the `v*` tag, exchanges a GitHub OIDC token with npm for a one-shot publish token, and publishes with `--provenance` so each release carries a Sigstore attestation linking it back to the source commit.
62
-
63
- ## CSS caveats
64
-
65
- DropFlow implements a serious subset of CSS but isn't a browser. Things to know when writing HTML for it (as of DropFlow 0.6.x):
52
+ ## CSS
66
53
 
67
- - Use the longhand `background-color`, not the `background` shorthand.
68
- - `max-width` / `min-width` aren't supported yet — use `width`.
69
- - `list-style` markers don't render; use `&bull;` or numbers inline.
70
- - `border-radius`, `box-shadow`, `transform`, and `position: absolute/fixed` aren't supported yet.
71
- - Body background doesn't propagate to the canvas — set the background on a wrapper element, or pass `--background <css>` to fill the canvas.
72
-
73
- See the [DropFlow README](https://github.com/chearon/dropflow#supported-css-rules) for the full support matrix.
54
+ Blitz implements a serious subset of CSS — flexbox, grid, `border-radius`, `box-shadow`, web fonts, `<img>` tags, `background:` shorthand, `max-width`, native `<ul>` bullets, the things you'd expect. It's pre-alpha for general embedding but works well for our render-once use case. The full status matrix is at [the Blitz repo](https://github.com/DioxusLabs/blitz).
74
55
 
75
56
  ## Fonts
76
57
 
77
- `Noto Sans` (regular, bold, italic, bold-italic) and `Noto Sans Mono` (regular, bold) ship inside the package as latin-subset TTFs (~160 KB total). No CDN fetch on first run; works offline. Reference them in HTML with `font-family: 'Noto Sans', sans-serif` and `font-family: 'Noto Sans Mono', monospace`.
58
+ `Noto Sans` (regular, bold, italic, bold-italic) and `Noto Sans Mono` (regular, bold) are baked into the native binary as latin-subset TTFs. No system font dependency; renders identically across macOS and Linux. Reference them in HTML with `font-family: 'Noto Sans', sans-serif` and `font-family: 'Noto Sans Mono', monospace`.
78
59
 
79
60
  ## Claude Code skill
80
61
 
@@ -85,7 +66,7 @@ mkdir -p ~/.claude/skills
85
66
  cp -r "$(npm root -g)/kittyhtml/skill/kittyhtml" ~/.claude/skills/
86
67
  ```
87
68
 
88
- Then in any Claude Code session: *"give me this report as kittyhtml"* — the agent will generate DropFlow-compatible HTML and pipe it through this CLI. The skill is narrow on purpose; it only triggers on those keywords.
69
+ Then in any Claude Code session: *"give me this report as kittyhtml"* — the agent will generate HTML and pipe it through this CLI. The skill is narrow on purpose; it only triggers on those keywords.
89
70
 
90
71
  ## How agents should use it
91
72
 
@@ -96,3 +77,14 @@ echo "$HTML" | kittyhtml --width 700 --scale 2
96
77
  ```
97
78
 
98
79
  The image is one frame in the scrollback — no popups, no new windows.
80
+
81
+ ## Releasing
82
+
83
+ Releases publish via GitHub Actions using npm trusted publishing (OIDC, no long-lived token). The native binary is cross-compiled per platform and published as `@kittyhtml/native-*` packages, then the umbrella `kittyhtml` package selects the right one at install time.
84
+
85
+ ```sh
86
+ npm version patch # or minor / major — bumps package.json and tags
87
+ git push --follow-tags
88
+ ```
89
+
90
+ The `Publish to npm` workflow fires on the `v*` tag, builds the native binaries on the matrix, and publishes everything with `--provenance`.
@@ -0,0 +1,44 @@
1
+ // Platform-specific loader for the kittyhtml native renderer.
2
+ // Selects the right prebuilt .node file based on process.platform and process.arch.
3
+ //
4
+ // When packaged for npm, the .node files live in platform-specific subpackages
5
+ // (npm/darwin-arm64, npm/linux-x64-gnu, etc.) and are resolved via optionalDependencies.
6
+ // During local development, the build sits next to this file.
7
+
8
+ const { existsSync } = require('node:fs');
9
+ const { join } = require('node:path');
10
+
11
+ const platform = process.platform;
12
+ const arch = process.arch;
13
+ const libc = (() => {
14
+ if (platform !== 'linux') return '';
15
+ // Best-effort: assume gnu unless musl is hinted. CI builds will use named
16
+ // subpackages so this guess only matters during local dev on Linux.
17
+ try {
18
+ const { familySync, GLIBC, MUSL } = require('detect-libc');
19
+ const fam = familySync();
20
+ return fam === MUSL ? '-musl' : '-gnu';
21
+ } catch {
22
+ return '-gnu';
23
+ }
24
+ })();
25
+
26
+ const target = `${platform}-${arch}${libc}`;
27
+ const localBinary = join(__dirname, `kittyhtml-native.${target}.node`);
28
+
29
+ let mod;
30
+ if (existsSync(localBinary)) {
31
+ mod = require(localBinary);
32
+ } else {
33
+ try {
34
+ mod = require(`kittyhtml-${target}`);
35
+ } catch (err) {
36
+ throw new Error(
37
+ `kittyhtml: no prebuilt native binary for ${target}. ` +
38
+ `Tried ${localBinary} and the kittyhtml-${target} package. ` +
39
+ `If you're on an unsupported platform, please file an issue.`,
40
+ );
41
+ }
42
+ }
43
+
44
+ module.exports = mod;
@@ -1,35 +1,48 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
- <body style="font-family: 'Noto Sans', sans-serif; margin: 0; padding: 0; background-color: #f4f4f5; color: #18181b;">
4
- <div style="background-color: #f4f4f5; padding: 32px 0;">
5
- <div style="width: 720px; margin: 0 auto;">
3
+ <body style="font-family: 'Noto Sans', sans-serif; margin: 0; padding: 0; background: #f4f4f5; color: #18181b;">
4
+ <div style="max-width: 720px; margin: 0 auto; padding: 32px 24px;">
6
5
 
7
- <div style="background-color: #ffffff; border: 1px solid #e4e4e7; padding: 28px 32px;">
8
- <div style="font-size: 11px; color: #71717a; font-weight: 700;">KITTYHTML &middot; DEMO</div>
9
- <div style="font-size: 26px; font-weight: 700; padding-top: 6px;">HTML, in your terminal.</div>
10
- <div style="font-size: 14px; color: #52525b; padding-top: 4px;">Rendered by DropFlow, displayed via the Kitty graphics protocol.</div>
6
+ <div style="background: #ffffff; border-radius: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); padding: 32px;">
7
+ <div style="font-size: 11px; color: #71717a; font-weight: 700; letter-spacing: 1.2px;">KITTYHTML &middot; v0.3</div>
8
+ <h1 style="font-size: 28px; margin: 6px 0 4px; font-weight: 700;">HTML, in your terminal.</h1>
9
+ <p style="margin: 0; font-size: 14px; color: #52525b;">Rendered by Blitz, displayed via the Kitty graphics protocol.</p>
11
10
 
12
- <div style="padding-top: 18px; font-size: 14px; line-height: 1.6;">
13
- No Chromium, no headless browser. Just CSS layout to a PNG, base64-streamed into your terminal as one inline image. Useful when an AI agent has something to show you that reads better as a page than as plain text.
14
- </div>
11
+ <p style="font-size: 14px; line-height: 1.6; margin: 20px 0 0;">
12
+ No Chromium, no headless browser. Just a Rust + Vello renderer producing a PNG, base64-streamed into your terminal as one inline image. Useful when an AI agent has something to show you that reads better as a page than as plain text.
13
+ </p>
14
+
15
+ <h2 style="font-size: 15px; margin: 24px 0 8px;">What this is good for</h2>
16
+ <ul style="font-size: 14px; line-height: 1.7; margin: 0; padding-left: 22px;">
17
+ <li>Inline reports, tables, and summaries from agents.</li>
18
+ <li>Quick previews of email or marketing copy.</li>
19
+ <li>Lightweight diagrams that compose better in HTML than ASCII.</li>
20
+ </ul>
15
21
 
16
- <div style="font-size: 14px; font-weight: 700; padding-top: 22px; padding-bottom: 6px;">What this is good for</div>
17
- <div style="font-size: 14px; line-height: 1.7; padding-left: 4px;">
18
- <div>&bull;&nbsp;&nbsp;Inline reports, tables, and summaries from agents.</div>
19
- <div>&bull;&nbsp;&nbsp;Quick previews of email or marketing copy.</div>
20
- <div>&bull;&nbsp;&nbsp;Lightweight diagrams that compose better in HTML than in ASCII.</div>
22
+ <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; margin-top: 22px;">
23
+ <div style="background: #dbeafe; border-radius: 8px; padding: 12px 14px; font-size: 13px;">
24
+ <div style="font-weight: 700; font-size: 11px; color: #1e40af; letter-spacing: 0.8px;">FLEXBOX</div>
25
+ <div style="margin-top: 2px;">supported</div>
26
+ </div>
27
+ <div style="background: #fef3c7; border-radius: 8px; padding: 12px 14px; font-size: 13px;">
28
+ <div style="font-weight: 700; font-size: 11px; color: #92400e; letter-spacing: 0.8px;">GRID</div>
29
+ <div style="margin-top: 2px;">supported</div>
30
+ </div>
31
+ <div style="background: #d1fae5; border-radius: 8px; padding: 12px 14px; font-size: 13px;">
32
+ <div style="font-weight: 700; font-size: 11px; color: #065f46; letter-spacing: 0.8px;">RADIUS</div>
33
+ <div style="margin-top: 2px;">supported</div>
34
+ </div>
21
35
  </div>
22
36
 
23
- <div style="margin-top: 20px; background-color: #18181b; color: #e4e4e7; padding: 14px 16px; font-family: 'Noto Sans Mono', monospace; font-size: 13px;">
37
+ <div style="margin-top: 22px; background: #18181b; color: #e4e4e7; padding: 14px 18px; border-radius: 10px; font-family: 'Noto Sans Mono', monospace; font-size: 13px;">
24
38
  $ echo '&lt;h1&gt;hi&lt;/h1&gt;' | kittyhtml
25
39
  </div>
26
40
 
27
- <div style="font-size: 12px; color: #71717a; padding-top: 18px;">
41
+ <p style="font-size: 12px; color: #71717a; margin: 18px 0 0;">
28
42
  Tip: try <span style="font-family: 'Noto Sans Mono', monospace; color: #18181b;">--scale 2</span> for sharper text on high-DPI displays.
29
- </div>
43
+ </p>
30
44
  </div>
31
45
 
32
46
  </div>
33
- </div>
34
47
  </body>
35
48
  </html>
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kittyhtml",
3
- "version": "0.2.1",
4
- "description": "Render HTML to an image and display it inline in Kitty/iTerm2-capable terminals. No browser — CSS layout via DropFlow.",
3
+ "version": "0.3.1",
4
+ "description": "Render HTML to an image and display it inline in Kitty/iTerm2-capable terminals. No browser — Rust + Blitz layout, headless CPU rasterization.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "kittyhtml": "./src/cli.js"
@@ -14,20 +14,24 @@
14
14
  },
15
15
  "files": [
16
16
  "src",
17
- "assets/fonts",
17
+ "crates/renderer/index.cjs",
18
18
  "skill",
19
19
  "examples/demo.html",
20
- "README.md"
20
+ "examples/demo.png",
21
+ "README.md",
22
+ "LICENSE"
21
23
  ],
22
24
  "engines": {
23
25
  "node": ">=20"
24
26
  },
25
- "scripts": {
26
- "demo": "node src/cli.js --demo"
27
+ "optionalDependencies": {
28
+ "kittyhtml-darwin-arm64": "0.3.1",
29
+ "kittyhtml-darwin-x64": "0.3.1",
30
+ "kittyhtml-linux-x64-gnu": "0.3.1"
27
31
  },
28
- "dependencies": {
29
- "@napi-rs/canvas": "^1.0.0",
30
- "dropflow": "^0.6.1"
32
+ "scripts": {
33
+ "demo": "node src/cli.js --demo",
34
+ "build": "napi build --release --platform --manifest-path crates/renderer/Cargo.toml --package-json-path package.json --output-dir crates/renderer"
31
35
  },
32
36
  "keywords": [
33
37
  "kitty",
@@ -37,7 +41,8 @@
37
41
  "html",
38
42
  "css",
39
43
  "render",
40
- "dropflow",
44
+ "blitz",
45
+ "vello",
41
46
  "image",
42
47
  "cli",
43
48
  "ai-agent",
@@ -52,5 +57,16 @@
52
57
  "bugs": {
53
58
  "url": "https://github.com/kkukshtel/kittyhtml/issues"
54
59
  },
55
- "homepage": "https://github.com/kkukshtel/kittyhtml#readme"
60
+ "homepage": "https://github.com/kkukshtel/kittyhtml#readme",
61
+ "devDependencies": {
62
+ "@napi-rs/cli": "^3.6.2"
63
+ },
64
+ "napi": {
65
+ "binaryName": "kittyhtml-native",
66
+ "targets": [
67
+ "x86_64-apple-darwin",
68
+ "aarch64-apple-darwin",
69
+ "x86_64-unknown-linux-gnu"
70
+ ]
71
+ }
56
72
  }
@@ -5,49 +5,69 @@ description: Render output as an inline terminal image using the `kittyhtml` CLI
5
5
 
6
6
  # kittyhtml output format
7
7
 
8
- The user has asked for output as **kittyhtml** or **khtml** — they want the result rendered as a styled HTML page and displayed inline in their terminal as an image, via the `kittyhtml` CLI.
8
+ The user has asked for output as **kittyhtml** or **khtml** — they want the result rendered as a styled HTML page and displayed inline in their terminal as an image.
9
9
 
10
10
  ## What to do
11
11
 
12
- 1. **Verify the tool is installed**: `command -v kittyhtml` (one-time check per session). If missing, tell the user to run `npm install -g kittyhtml` and stop.
12
+ 1. **Verify the tool is installed**: `command -v kittyhtml`. If missing, tell the user `npm install -g kittyhtml` and stop.
13
13
 
14
- 2. **Generate HTML** that represents the content the user asked abouta styled page, card, table, summary, or whatever fits the request. Keep it focused; the rendered image should fit on one screen.
14
+ 2. **Generate HTML** that represents the content. Be liberal with CSSBlitz handles flexbox, grid, `<style>` blocks, class selectors, web fonts, and `<img>` tags. Treat it like a real browser with the caveats listed below.
15
15
 
16
- 3. **Pipe it through the CLI**:
16
+ 3. **Pipe through the CLI**:
17
17
  ```sh
18
- cat <<'HTML' | kittyhtml --width 700 --scale 2
18
+ cat <<'HTML' | kittyhtml --scale 2
19
19
  <!DOCTYPE html>
20
20
  <html><body>...</body></html>
21
21
  HTML
22
22
  ```
23
23
 
24
- Use `--scale 2` for crisp text on retina/HiDPI. Use `--width 600`–`800` for content that should feel "page-sized." Use a smaller width (`--width 400`) for compact card-like output.
24
+ Don't pass `--width` unless the user asks for a specific size the default adapts to their terminal width. Always pass `--scale 2` for sharper text on HiDPI displays.
25
25
 
26
26
  4. After piping, write a one-line confirmation (e.g. "Rendered above."). The image is the deliverable; don't restate its contents in text.
27
27
 
28
- ## CSS rules DropFlow subset
28
+ ## CSS — what works
29
29
 
30
- DropFlow is a real CSS layout engine but it's not a browser. Stick to this subset:
30
+ kittyhtml v0.3+ uses Blitz (Stylo + Taffy + Parley + Vello CPU). Treat it like a modern browser:
31
31
 
32
- - **Use `background-color`, NOT the `background` shorthand.** The shorthand is silently dropped.
33
- - **Use `width: Npx`, NOT `max-width`.** `max-width` / `min-width` aren't implemented.
34
- - **No `list-style` markers.** Don't use `<ul><li>` and expect bullets. Use `<div>&bull;&nbsp;&nbsp;item</div>` or numbered prefixes.
35
- - **No `border-radius`, `box-shadow`, `transform`, `position: absolute/fixed`.** Square corners only. Use `border: 1px solid #color;` for definition.
36
- - **`<body>` background does NOT propagate to the canvas.** Wrap content in an outer `<div style="background-color: #fff; padding: ...">` to fill the image.
37
- - **Only inline `style` attributes work** — no `<style>` blocks, no classes.
38
- - **Fonts available**: `Noto Sans` (default), `Noto Sans Mono` for code.
32
+ - **Full `<style>` blocks and class selectors work.** Use them.
33
+ - Flexbox, CSS Grid, `border-radius`, `box-shadow`, `opacity`, web fonts via `@font-face`, `<img>` tags with HTTPS URLs.
34
+ - `max-width`, `min-width`, `width`, `height` all work in any units.
35
+ - `<ul>` / `<ol>` render native bullets/numbers.
36
+ - `position: relative` and (limited) `absolute`.
37
+
38
+ ## Caveats (Blitz pre-alpha)
39
+
40
+ - **`<thead>` rows render the background but lose text content.** Workaround: put header rows in `<tbody>` and style the row with `font-weight: 700` instead of wrapping in `<thead>`, or apply inline `style="font-weight:700"` directly on each `<th>`.
41
+ - **Fixed widths bigger than `--width` overflow the canvas.** Stick to percentages (`width: 100%`) or `max-width` on tables and large containers. The canvas dimension is `--width * --scale` pixels.
42
+
43
+ ## Fonts
44
+
45
+ Two are baked into the binary and reliable:
46
+ - `'Noto Sans'` (regular, bold, italic, bold-italic)
47
+ - `'Noto Sans Mono'` for code
48
+
49
+ Use them as the canonical `font-family`:
50
+ ```css
51
+ font-family: 'Noto Sans', sans-serif;
52
+ font-family: 'Noto Sans Mono', monospace;
53
+ ```
54
+
55
+ Other fonts will fall back to system defaults or fail to render predictably.
39
56
 
40
57
  ## Template that's known to render well
41
58
 
42
59
  ```html
43
60
  <!DOCTYPE html>
44
61
  <html>
45
- <body style="font-family: 'Noto Sans', sans-serif; margin: 0; padding: 0; color: #18181b;">
46
- <div style="background-color: #f4f4f5; padding: 24px 0;">
47
- <div style="width: 640px; margin: 0 auto;">
48
- <div style="background-color: #ffffff; border: 1px solid #e4e4e7; padding: 24px 28px;">
49
- <!-- content here -->
50
- </div>
62
+ <head><style>
63
+ body { font-family: 'Noto Sans', sans-serif; margin: 0; padding: 0; background: #f4f4f5; color: #18181b; }
64
+ .wrap { max-width: 720px; margin: 0 auto; padding: 32px 24px; }
65
+ .card { background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); padding: 28px 32px; }
66
+ </style></head>
67
+ <body>
68
+ <div class="wrap">
69
+ <div class="card">
70
+ <!-- content here -->
51
71
  </div>
52
72
  </div>
53
73
  </body>
package/src/cli.js CHANGED
@@ -13,7 +13,8 @@ USAGE
13
13
  kittyhtml --demo
14
14
 
15
15
  OPTIONS
16
- --width N Viewport width in CSS px (default 800).
16
+ --width N Viewport width in CSS px (default: terminal width when
17
+ rendered to a TTY, else 1200).
17
18
  --height N Fixed canvas height; omit to auto-fit content.
18
19
  --scale N Pixel ratio for crisper output (default 1; try 2).
19
20
  --background CSS Fill canvas background before painting (e.g. "#fff").
@@ -28,9 +29,22 @@ EXAMPLES
28
29
  kittyhtml report.html --scale 2 -o report.png
29
30
  `;
30
31
 
32
+ // When stdout is a terminal, estimate its visible width in pixels so the
33
+ // rendered image fits the terminal at 1:1 instead of looking small. The
34
+ // estimate is `columns * 9` — a rough cell-width assumption that's close
35
+ // enough on macOS terminals at default zoom. Clamped so we don't render
36
+ // a wallpaper for someone with a 400-column tmux pane.
37
+ function defaultWidth() {
38
+ if (process.stdout.isTTY && typeof process.stdout.columns === 'number') {
39
+ const cols = process.stdout.columns;
40
+ return Math.max(400, Math.min(2400, cols * 9));
41
+ }
42
+ return 1200;
43
+ }
44
+
31
45
  function parseArgs(argv) {
32
46
  const opts = {
33
- width: 800,
47
+ width: defaultWidth(),
34
48
  height: null,
35
49
  scale: 1,
36
50
  background: null,
package/src/render.js CHANGED
@@ -1,77 +1,30 @@
1
- import * as flow from 'dropflow';
2
- import parse from 'dropflow/parse.js';
3
- import { createCanvas, GlobalFonts, loadImage } from '@napi-rs/canvas';
4
-
5
- const FONTS_DIR = new URL('../assets/fonts/', import.meta.url);
6
- const BUNDLED_FONTS = [
7
- 'NotoSans-Regular.ttf',
8
- 'NotoSans-Bold.ttf',
9
- 'NotoSans-Italic.ttf',
10
- 'NotoSans-BoldItalic.ttf',
11
- 'NotoSansMono-Regular.ttf',
12
- 'NotoSansMono-Bold.ttf',
13
- ];
14
-
15
- let envReady = false;
16
- function ensureEnv() {
17
- if (envReady) return;
18
-
19
- // Tell DropFlow how to register a font and decode an image into the
20
- // @napi-rs/canvas backend (default wiring in environment-node.js targets the
21
- // legacy `canvas` package).
22
- flow.environment.registerFont = face => {
23
- const key = GlobalFonts.register(face.getBuffer(), face.uniqueFamily);
24
- if (key) return () => GlobalFonts.remove(key);
25
- };
26
- flow.environment.createDecodedImage = async image => {
27
- return await loadImage(Buffer.from(image.buffer));
28
- };
29
-
30
- for (const file of BUNDLED_FONTS) {
31
- flow.fonts.add(flow.createFaceFromTablesSync(new URL(file, FONTS_DIR)));
32
- }
33
-
34
- envReady = true;
1
+ import { createRequire } from 'node:module';
2
+
3
+ const require = createRequire(import.meta.url);
4
+ // Loaded lazily so importing this module doesn't pay the .node init cost
5
+ // until someone actually renders.
6
+ let _native = null;
7
+ function native() {
8
+ if (_native == null) _native = require('../crates/renderer/index.cjs');
9
+ return _native;
35
10
  }
36
11
 
37
12
  /**
38
- * Render an HTML string to a PNG buffer using DropFlow.
13
+ * Render an HTML string to a PNG buffer via Blitz + Vello-CPU.
39
14
  *
40
15
  * @param {string} html
41
16
  * @param {object} [opts]
42
17
  * @param {number} [opts.width=800] Viewport width in CSS px before scaling.
43
18
  * @param {number|null} [opts.height] Fixed canvas height; if null, auto-fit to content.
44
19
  * @param {number} [opts.scale=1] Pixel ratio (2 = retina-sharp).
45
- * @param {string|null} [opts.background] Optional canvas background fill (CSS color).
46
20
  * @returns {Promise<Buffer>} PNG image bytes.
47
21
  */
48
22
  export async function renderHtml(html, opts = {}) {
49
- const { width = 800, height = null, scale = 1, background = null } = opts;
50
- ensureEnv();
51
-
52
- const root = parse(html);
53
- await flow.load(root);
54
-
55
- const pxWidth = Math.max(1, Math.round(width * scale));
56
- const layout = flow.generate(root);
57
-
58
- let pxHeight;
59
- if (height != null) {
60
- pxHeight = Math.max(1, Math.round(height * scale));
61
- flow.layout(layout, pxWidth, pxHeight);
62
- } else {
63
- flow.layout(layout, pxWidth, 1_000_000);
64
- const measured = layout.getBorderArea().height;
65
- pxHeight = Math.max(1, Math.ceil(measured));
66
- flow.layout(layout, pxWidth, pxHeight);
67
- }
68
-
69
- const canvas = createCanvas(pxWidth, pxHeight);
70
- const ctx = canvas.getContext('2d');
71
- if (background) {
72
- ctx.fillStyle = background;
73
- ctx.fillRect(0, 0, pxWidth, pxHeight);
74
- }
75
- flow.paintToCanvas(layout, ctx);
76
- return await canvas.encode('png');
23
+ const { width = 800, height = null, scale = 1 } = opts;
24
+ const nativeOpts = {
25
+ width: Math.max(1, Math.round(width)),
26
+ scale: Math.max(0.01, scale),
27
+ };
28
+ if (height != null) nativeOpts.height = Math.max(1, Math.round(height));
29
+ return await native().renderHtml(html, nativeOpts);
77
30
  }
Binary file
Binary file
Binary file
Binary file