kittyhtml 0.2.0 → 0.3.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
@@ -2,19 +2,23 @@
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
 
9
9
  ## Install
10
10
 
11
11
  ```sh
12
- # from this directory, until published
13
- npm install
14
- npm link # exposes the `kittyhtml` binary on your PATH
12
+ npm install -g kittyhtml
15
13
  ```
16
14
 
17
- Requires Node 20+. Pulls in [`@napi-rs/canvas`](https://www.npmjs.com/package/@napi-rs/canvas) (prebuilt native binary, no compile step) and `dropflow`.
15
+ Or one-shot, no install:
16
+
17
+ ```sh
18
+ npx kittyhtml --demo
19
+ ```
20
+
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.
18
22
 
19
23
  ## Use
20
24
 
@@ -45,43 +49,24 @@ const png = await renderHtml('<h1>hello</h1>', { width: 400, scale: 2 });
45
49
  process.stdout.write(encode(png, 'kitty'));
46
50
  ```
47
51
 
48
- ## Releasing
49
-
50
- Releases publish via GitHub Actions using npm trusted publishing (OIDC, no long-lived token). To cut a release:
51
-
52
- ```sh
53
- npm version patch # or minor / major — bumps package.json and tags
54
- git push --follow-tags
55
- ```
56
-
57
- 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.
52
+ ## CSS
58
53
 
59
- ## CSS caveats
60
-
61
- 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):
62
-
63
- - Use the longhand `background-color`, not the `background` shorthand.
64
- - `max-width` / `min-width` aren't supported yet — use `width`.
65
- - `list-style` markers don't render; use `&bull;` or numbers inline.
66
- - `border-radius`, `box-shadow`, `transform`, and `position: absolute/fixed` aren't supported yet.
67
- - Body background doesn't propagate to the canvas — set the background on a wrapper element, or pass `--background <css>` to fill the canvas.
68
-
69
- 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).
70
55
 
71
56
  ## Fonts
72
57
 
73
- First run fetches Noto fonts from a CDN via DropFlow's bundled `register-noto-fonts.js`. Subsequent renders reuse what was loaded. Bundled offline fonts are on the roadmap.
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`.
74
59
 
75
60
  ## Claude Code skill
76
61
 
77
- A bundled skill lets Claude Code render output as a styled inline image when you ask for it as "kittyhtml" or "khtml":
62
+ A bundled skill lets Claude Code render output as a styled inline image when you ask for it as "kittyhtml" or "khtml". After a global install:
78
63
 
79
64
  ```sh
80
65
  mkdir -p ~/.claude/skills
81
- cp -r skill/kittyhtml ~/.claude/skills/kittyhtml
66
+ cp -r "$(npm root -g)/kittyhtml/skill/kittyhtml" ~/.claude/skills/
82
67
  ```
83
68
 
84
- 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.
85
70
 
86
71
  ## How agents should use it
87
72
 
@@ -92,3 +77,14 @@ echo "$HTML" | kittyhtml --width 700 --scale 2
92
77
  ```
93
78
 
94
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.0",
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.0",
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.0",
29
+ "kittyhtml-darwin-x64": "0.3.0",
30
+ "kittyhtml-linux-x64-gnu": "0.3.0"
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
  }
@@ -9,9 +9,9 @@ The user has asked for output as **kittyhtml** or **khtml** — they want the re
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 about — a 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 the user asked about — a styled page, card, table, summary. Keep it focused; the rendered image should fit on one screen.
15
15
 
16
16
  3. **Pipe it through the CLI**:
17
17
  ```sh
@@ -21,33 +21,52 @@ The user has asked for output as **kittyhtml** or **khtml** — they want the re
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
+ Use `--scale 2` for crisp text on retina/HiDPI. Pick width based on shape:
25
+ - `--width 700`–`800` for full pages and reports
26
+ - `--width 500` for cards and summaries
27
+ - `--width 400` for compact, just-a-snippet output
25
28
 
26
29
  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
30
 
28
- ## CSS rules DropFlow subset
31
+ ## CSS — what works
29
32
 
30
- DropFlow is a real CSS layout engine but it's not a browser. Stick to this subset:
33
+ kittyhtml v0.3+ uses Blitz (Stylo + Taffy + Parley + Vello). Most modern CSS works:
31
34
 
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.
35
+ - Flexbox (`display: flex`, `flex: 1`, `gap`, etc.)
36
+ - CSS Grid (`display: grid`, `grid-template-columns`, etc.)
37
+ - `border-radius`, `box-shadow`, `opacity`
38
+ - `max-width`, `min-width`, `width`, `height`
39
+ - `background:` shorthand (no need for `background-color` longhand)
40
+ - Native `<ul>` / `<ol>` bullets
41
+ - `<img>` tags (with absolute URLs; HTTPS works)
42
+ - `position: relative` / `absolute`
43
+ - Web fonts via `@font-face`
44
+
45
+ Inline styles only — no `<style>` blocks, no `class` selectors are honored.
46
+
47
+ ## Fonts
48
+
49
+ Two are baked in and reliable:
50
+ - `'Noto Sans'` (regular, bold, italic, bold-italic)
51
+ - `'Noto Sans Mono'` for code
52
+
53
+ Use them as the canonical `font-family`:
54
+ ```css
55
+ font-family: 'Noto Sans', sans-serif;
56
+ font-family: 'Noto Sans Mono', monospace;
57
+ ```
58
+
59
+ Other fonts will fall back to system defaults or fail to render predictably.
39
60
 
40
61
  ## Template that's known to render well
41
62
 
42
63
  ```html
43
64
  <!DOCTYPE html>
44
65
  <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>
66
+ <body style="font-family: 'Noto Sans', sans-serif; margin: 0; padding: 0; background: #f4f4f5; color: #18181b;">
67
+ <div style="max-width: 720px; margin: 0 auto; padding: 32px 24px;">
68
+ <div style="background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); padding: 28px 32px;">
69
+ <!-- content here -->
51
70
  </div>
52
71
  </div>
53
72
  </body>
package/src/cli.js CHANGED
@@ -80,6 +80,17 @@ function loadDemoHtml() {
80
80
  return readFileSync(join(here, '..', 'examples', 'demo.html'), 'utf8');
81
81
  }
82
82
 
83
+ // readFileSync(0) crashes with EAGAIN when stdin is in non-blocking mode and
84
+ // the upstream process hasn't flushed yet (common with `claude -p ... | kittyhtml`).
85
+ // Async iteration over process.stdin yields back to the event loop and waits
86
+ // for data, so it handles any pipe pacing correctly.
87
+ async function readStdin() {
88
+ process.stdin.setEncoding('utf8');
89
+ let buf = '';
90
+ for await (const chunk of process.stdin) buf += chunk;
91
+ return buf;
92
+ }
93
+
83
94
  async function main() {
84
95
  const opts = parseArgs(process.argv.slice(2));
85
96
 
@@ -89,7 +100,7 @@ async function main() {
89
100
  } else if (opts.file) {
90
101
  html = readFileSync(opts.file, 'utf8');
91
102
  } else if (!process.stdin.isTTY) {
92
- html = readFileSync(0, 'utf8');
103
+ html = await readStdin();
93
104
  } else {
94
105
  process.stdout.write(HELP);
95
106
  process.exit(1);
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