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 +26 -30
- package/crates/renderer/index.cjs +44 -0
- package/examples/demo.html +32 -19
- package/examples/demo.png +0 -0
- package/package.json +27 -11
- package/skill/kittyhtml/SKILL.md +37 -18
- package/src/cli.js +12 -1
- package/src/render.js +17 -64
- package/assets/fonts/NotoSans-Bold.ttf +0 -0
- package/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
- package/assets/fonts/NotoSans-Italic.ttf +0 -0
- package/assets/fonts/NotoSans-Regular.ttf +0 -0
- package/assets/fonts/NotoSansMono-Bold.ttf +0 -0
- package/assets/fonts/NotoSansMono-Regular.ttf +0 -0
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 [
|
|
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
|
-
|
|
13
|
-
npm install
|
|
14
|
-
npm link # exposes the `kittyhtml` binary on your PATH
|
|
12
|
+
npm install -g kittyhtml
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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 `•` 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
|
-
|
|
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/
|
|
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
|
|
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;
|
package/examples/demo.html
CHANGED
|
@@ -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
|
|
4
|
-
<div style="
|
|
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
|
|
8
|
-
<div style="font-size: 11px; color: #71717a; font-weight: 700;">KITTYHTML ·
|
|
9
|
-
<
|
|
10
|
-
<
|
|
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 · 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
|
-
<
|
|
13
|
-
No Chromium, no headless browser. Just
|
|
14
|
-
</
|
|
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="
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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:
|
|
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 '<h1>hi</h1>' | kittyhtml
|
|
25
39
|
</div>
|
|
26
40
|
|
|
27
|
-
<
|
|
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
|
-
</
|
|
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.
|
|
4
|
-
"description": "Render HTML to an image and display it inline in Kitty/iTerm2-capable terminals. No browser —
|
|
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
|
-
"
|
|
17
|
+
"crates/renderer/index.cjs",
|
|
18
18
|
"skill",
|
|
19
19
|
"examples/demo.html",
|
|
20
|
-
"
|
|
20
|
+
"examples/demo.png",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
21
23
|
],
|
|
22
24
|
"engines": {
|
|
23
25
|
"node": ">=20"
|
|
24
26
|
},
|
|
25
|
-
"
|
|
26
|
-
"
|
|
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
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
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
|
-
"
|
|
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
|
}
|
package/skill/kittyhtml/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
|
31
|
+
## CSS — what works
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
kittyhtml v0.3+ uses Blitz (Stylo + Taffy + Parley + Vello). Most modern CSS works:
|
|
31
34
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
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="
|
|
47
|
-
<div style="
|
|
48
|
-
|
|
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 =
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'
|
|
9
|
-
|
|
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
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
Binary file
|
|
Binary file
|