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 +17 -25
- 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 +41 -21
- package/src/cli.js +16 -2
- 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,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 [
|
|
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+.
|
|
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
|
-
##
|
|
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
|
-
|
|
68
|
-
- `max-width` / `min-width` aren't supported yet — use `width`.
|
|
69
|
-
- `list-style` markers don't render; use `•` 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)
|
|
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
|
|
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;
|
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.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
|
-
"
|
|
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.1",
|
|
29
|
+
"kittyhtml-darwin-x64": "0.3.1",
|
|
30
|
+
"kittyhtml-linux-x64-gnu": "0.3.1"
|
|
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
|
@@ -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
|
|
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
|
|
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
|
|
14
|
+
2. **Generate HTML** that represents the content. Be liberal with CSS — Blitz 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
|
|
16
|
+
3. **Pipe through the CLI**:
|
|
17
17
|
```sh
|
|
18
|
-
cat <<'HTML' | kittyhtml --
|
|
18
|
+
cat <<'HTML' | kittyhtml --scale 2
|
|
19
19
|
<!DOCTYPE html>
|
|
20
20
|
<html><body>...</body></html>
|
|
21
21
|
HTML
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
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
|
|
28
|
+
## CSS — what works
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
kittyhtml v0.3+ uses Blitz (Stylo + Taffy + Parley + Vello CPU). Treat it like a modern browser:
|
|
31
31
|
|
|
32
|
-
- **
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|