takumi-paint 0.0.0 → 0.1.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
@@ -1,3 +1,92 @@
1
+ <div align="center">
2
+ <img src="https://takumi.kane.tw/logo.svg" alt="Takumi" width="64" />
3
+
1
4
  # takumi-paint
2
5
 
3
- The paint tree package for [Takumi](https://github.com/kane50613/takumi). Not released yet.
6
+ **Lay out JSX, HTML, and CSS. Get back what to draw instead of pixels.**
7
+
8
+ Every box, gradient, shadow, text run, and image, in paint order, with each CSS value resolved.
9
+
10
+ [Documentation](https://takumi.kane.tw/docs/paint) · [Reference](https://takumi.kane.tw/docs/paint/reference)
11
+
12
+ </div>
13
+
14
+ ## What it is for
15
+
16
+ Takumi's other packages end in a PNG, an SVG, or a PDF. Use `takumi-paint` when you own the output:
17
+
18
+ - Write slides where text stays editable, such as PPTX.
19
+ - Draw onto Canvas, Skia, or a native drawing API.
20
+ - Read where layout put each box and line of text.
21
+
22
+ It runs the same layout as the image, SVG, and PDF renderers, in WebAssembly. Where they draw, it records the **paint tree**.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install takumi-paint
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```tsx
33
+ import { paint } from "takumi-paint";
34
+
35
+ const tree = await paint(
36
+ <div id="card" style={{ width: 240, padding: 16, background: "#F7F3EC" }}>
37
+ Hello <b>world</b>
38
+ </div>,
39
+ { width: 320 },
40
+ );
41
+ ```
42
+
43
+ `tree.nodes` lists every node. Each node lists what it draws:
44
+
45
+ ```tsx
46
+ const card = tree.nodes.find((node) => node.type === "box" && node.element?.id === "card");
47
+ console.log(card?.drawables[0]); // { type: "fill", role: "background", shape: { type: "rect", … }, … }
48
+
49
+ for (const node of tree.nodes) {
50
+ if (node.type !== "text") continue;
51
+ for (const run of node.runs) console.log(run.text, run.font.family, run.fontSize);
52
+ }
53
+ ```
54
+
55
+ `tree.steps` is the draw order. Replay it from the bottom up:
56
+
57
+ ```ts
58
+ for (const step of tree.steps) {
59
+ if (step.type !== "draw") continue; // begin-group, end-group, begin-clip, and end-clip wrap draws
60
+ ctx.setTransform(...step.node.transform);
61
+ for (const drawable of step.drawables) draw(ctx, drawable);
62
+ }
63
+ ```
64
+
65
+ `paint()` also takes an HTML string or a Takumi node tree.
66
+
67
+ ## What the tree holds
68
+
69
+ | Part | Contents |
70
+ | --------- | --------------------------------------------------------------------------- |
71
+ | Nodes | `box`, `text`, and `image`, each placed on the canvas by its `transform` |
72
+ | Drawables | Fills, strokes, shadows, glyph runs, and images, tagged with their CSS role |
73
+ | Paints | Colors, gradients, images, and tiled patterns |
74
+ | Text runs | Text, font, size, line height, and baseline, plus glyphs and outlines |
75
+ | Steps | Draws, clips, and groups for opacity, blending, filters, and masks |
76
+
77
+ Lengths are device pixels.
78
+
79
+ ## Learn more
80
+
81
+ | Need | Read |
82
+ | ------------------------------ | -------------------------------------------------------- |
83
+ | See the tree, step by step | [Paint](https://takumi.kane.tw/docs/paint) |
84
+ | Draw nodes in order | [Drawing](https://takumi.kane.tw/docs/paint/drawing) |
85
+ | Place text or lay it out again | [Text](https://takumi.kane.tw/docs/paint/text) |
86
+ | Check options and field types | [Reference](https://takumi.kane.tw/docs/paint/reference) |
87
+
88
+ ## License
89
+
90
+ MIT or Apache-2.0
91
+
92
+ Bundled third-party code is listed in the [third-party notices](https://github.com/kane50613/takumi/blob/master/THIRD-PARTY-NOTICES.md).
@@ -0,0 +1,7 @@
1
+ import * as wasm from "../dist/export.mjs";
2
+ import url from "./wasm-url.mjs";
3
+
4
+ wasm.initSync({ module: await fetch(url).then((response) => response.arrayBuffer()) });
5
+
6
+ export * from "../dist/export.mjs";
7
+ export default wasm.default;
@@ -0,0 +1,9 @@
1
+ import * as wasm from "../dist/export.mjs";
2
+ import wasmPath from "../pkg/takumi_paint_wasm_bg.wasm";
3
+
4
+ const wasmBytes = await Bun.file(new URL(wasmPath, import.meta.url)).arrayBuffer();
5
+
6
+ wasm.initSync({ module: wasmBytes });
7
+
8
+ export * from "../dist/export.mjs";
9
+ export default wasm.default;
@@ -0,0 +1,11 @@
1
+ import module from "../pkg/takumi_paint_wasm_bg.wasm?module";
2
+ import * as wasm from "../dist/export.mjs";
3
+
4
+ // typeof module says its Module { } but instanceof WebAssembly.Module is false
5
+ // Have to force override the prototype to be WebAssembly.Module
6
+ Object.setPrototypeOf(module, WebAssembly.Module.prototype);
7
+
8
+ wasm.initSync({ module });
9
+
10
+ export * from "../dist/export.mjs";
11
+ export default wasm.default;
@@ -0,0 +1,10 @@
1
+ const wasm = require("../dist/export.cjs");
2
+ const { readFileSync } = require("node:fs");
3
+ const { join } = require("node:path");
4
+
5
+ const wasmPath = join(__dirname, "../pkg/takumi_paint_wasm_bg.wasm");
6
+ const wasmBytes = readFileSync(wasmPath);
7
+
8
+ wasm.initSync({ module: wasmBytes });
9
+
10
+ module.exports = wasm;
@@ -0,0 +1,3 @@
1
+ import * as paint from "../dist/export.mjs";
2
+
3
+ export = paint;
@@ -0,0 +1,2 @@
1
+ export * from "../dist/export.mjs";
2
+ export { default } from "../dist/export.mjs";
@@ -0,0 +1,9 @@
1
+ import { readFileSync } from "node:fs";
2
+ import * as wasm from "../dist/export.mjs";
3
+
4
+ const wasmBytes = readFileSync(new URL("../pkg/takumi_paint_wasm_bg.wasm", import.meta.url));
5
+
6
+ wasm.initSync({ module: wasmBytes });
7
+
8
+ export * from "../dist/export.mjs";
9
+ export default wasm.default;
@@ -0,0 +1,46 @@
1
+ import url from "../pkg/takumi_paint_wasm_bg.wasm?url";
2
+ import * as wasm from "../dist/export.mjs";
3
+
4
+ // `?url` is a browser path; map it to a file for server reads. Vite emits the asset next to the
5
+ // importing server chunk, so resolve relative to import.meta.url, not the framework output dir.
6
+ async function resolveWasm() {
7
+ if (typeof process !== "undefined" && process.versions?.node != null) {
8
+ const { readFile } = process.getBuiltinModule("node:fs/promises");
9
+ const path = decodeURIComponent(url.replace(/[?#].*$/, ""));
10
+
11
+ // Dev SSR serves an `/@fs/<abs-path>` URL.
12
+ if (path.startsWith("/@fs/")) {
13
+ return readFile(path.slice("/@fs".length));
14
+ }
15
+
16
+ if (!path.startsWith("/")) {
17
+ return readFile(new URL(path, import.meta.url));
18
+ }
19
+
20
+ const basename = path.slice(path.lastIndexOf("/") + 1);
21
+ const candidates = [`./${basename}`, `.${path}`, `../client${path}`, `../../client${path}`];
22
+
23
+ let lastError;
24
+ for (const candidate of candidates) {
25
+ try {
26
+ return await readFile(new URL(candidate, import.meta.url));
27
+ } catch (error) {
28
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
29
+ lastError = error;
30
+ continue;
31
+ }
32
+
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ throw new Error(`Unable to locate Takumi WASM asset for SSR: ${url}`, { cause: lastError });
38
+ }
39
+
40
+ return fetch(new URL(url, import.meta.url)).then((response) => response.arrayBuffer());
41
+ }
42
+
43
+ wasm.initSync({ module: await resolveWasm() });
44
+
45
+ export * from "../dist/export.mjs";
46
+ export default wasm.default;
@@ -0,0 +1,4 @@
1
+ const { join } = require("node:path");
2
+ const { pathToFileURL } = require("node:url");
3
+
4
+ module.exports = pathToFileURL(join(__dirname, "../pkg/takumi_paint_wasm_bg.wasm"));
@@ -0,0 +1,3 @@
1
+ declare const url: URL;
2
+
3
+ export = url;
@@ -0,0 +1,3 @@
1
+ declare const url: URL;
2
+
3
+ export default url;
@@ -0,0 +1 @@
1
+ export default new URL("../pkg/takumi_paint_wasm_bg.wasm", import.meta.url);
@@ -0,0 +1,7 @@
1
+ import module from "../pkg/takumi_paint_wasm_bg.wasm";
2
+ import * as wasm from "../dist/export.mjs";
3
+
4
+ wasm.initSync({ module });
5
+
6
+ export * from "../dist/export.mjs";
7
+ export default wasm.default;