tegaki 0.19.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## tegaki@0.20.0
2
+
3
+ ### Add `TegakiEngine.toSVG()` and a `canvas` accessor
4
+
5
+ The engine can now serialize its current text to a standalone SVG string via `toSVG({ animated, loop })` — either self-drawing (SMIL mask reveal, variable stroke width) or looping (CSS keyframes, constant width), with the viewBox cropped to the ink. The backing `<canvas>` is also exposed through a `canvas` getter so export tooling can read pixels without reaching through the DOM.
6
+
7
+ ### Add a `tegaki` CLI and a `textToSvg()` export
8
+
9
+ `npx tegaki` renders text to an animated handwriting SVG straight from the command line. The same output is available programmatically through the new `textToSvg()` export, along with its `TextToSvgMode` and `TextToSvgOptions` types.
10
+
1
11
  # tegaki
2
12
 
3
13
  ## 0.19.0
package/README.md CHANGED
@@ -6,12 +6,12 @@ Tegaki (手書き) turns any font into animated handwriting.
6
6
  No manual path authoring. No native dependencies. Just pick a font.
7
7
 
8
8
  [![npm](https://img.shields.io/npm/v/tegaki)](https://www.npmjs.com/package/tegaki)
9
- [![license](https://img.shields.io/npm/l/tegaki)](https://github.com/KurtGokhan/tegaki/blob/main/LICENSE)
9
+ [![license](https://img.shields.io/npm/l/tegaki)](https://github.com/gkurt/tegaki/blob/main/LICENSE)
10
10
 
11
11
  <br clear="both" />
12
12
 
13
13
  <p align="center">
14
- <img src="media/hello-world.svg" alt="Hello World handwriting animation" width="500" />
14
+ <img src="media/hello-world.svg" alt="Tegaki is awesome handwriting animation" width="560" />
15
15
  </p>
16
16
 
17
17
  ---
@@ -41,6 +41,23 @@ function App() {
41
41
 
42
42
  That's it. The text draws itself stroke by stroke with natural timing.
43
43
 
44
+ ## Command Line
45
+
46
+ Don't want to wire up a component? Generate an animated handwriting SVG straight from your terminal — nothing to install:
47
+
48
+ ```bash
49
+ npx tegaki "Tegaki is awesome"
50
+ ```
51
+
52
+ This writes a self-drawing, looping `tegaki-is-awesome.svg` — drop it into a README, a slide, or any page. Pick a font, mode, size, or color:
53
+
54
+ ```bash
55
+ npx tegaki "Hello World" --font tangerine --mode once -o hello.svg
56
+ npx tegaki "ABC" --stagger 80% --size 140 --color "#222"
57
+ ```
58
+
59
+ `--mode` is `loop` (repeats forever, the default), `once` (draws itself a single time), or `static` (finished artwork). Run `npx tegaki --help` for every option and `--list-fonts` for the bundled fonts. The CLI emits SVG only — for PNG, GIF, or WebM use the [interactive studio](https://gkurt.com/tegaki/studio/).
60
+
44
61
  ## Framework Support
45
62
 
46
63
  Tegaki works with all major frameworks:
@@ -77,7 +94,7 @@ Several handwriting fonts are bundled and ready to use:
77
94
  - **Klee One** — `tegaki/fonts/klee-one` _(Japanese: kana + Kyōiku grade 1–2 kanji + Latin)_
78
95
  - **Nanum Pen Script** — `tegaki/fonts/nanum-pen-script` _(Korean: Hangul syllables + jamo + Latin)_
79
96
 
80
- For other fonts, use the [interactive generator](https://gkurt.com/tegaki/generator/) to create a custom bundle.
97
+ For other fonts, use the [interactive studio](https://gkurt.com/tegaki/studio/) to create a custom bundle.
81
98
 
82
99
  ## Documentation
83
100
 
@@ -91,7 +108,7 @@ Visit **[gkurt.com/tegaki](https://gkurt.com/tegaki)** for full documentation:
91
108
  ## Integrations
92
109
 
93
110
  - [Sli.dev](https://sli.dev/) - Create presentations using markdown. [See Tegaki integration example](https://andreas-taranetz.github.io/slidev-addon-animated-text/)
94
- - [Remotion](https://www.remotion.dev/) - Create videos programmatically with React. [See Tegaki integration example](https://github.com/KurtGokhan/tegaki/blob/gokhan/support-font-features/examples/remotion/src/Root.tsx)
111
+ - [Remotion](https://www.remotion.dev/) - Create videos programmatically with React. [See Tegaki integration example](https://github.com/gkurt/tegaki/blob/gokhan/support-font-features/examples/remotion/src/Root.tsx)
95
112
 
96
113
  ## License
97
114
 
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ import { t as textToSvg } from "./textToSvg-GTQMM8Le.mjs";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ //#region src/cli.ts
5
+ /**
6
+ * `tegaki` — turn text into an animated handwriting SVG from the command line.
7
+ *
8
+ * npx tegaki "Tegaki is awesome" # → tegaki-is-awesome.svg (looping)
9
+ * npx tegaki "Hello" -f tangerine -o hi.svg
10
+ *
11
+ * SVG is the only format emitted: it's the one the renderer can produce with
12
+ * zero native dependencies (no canvas, no headless browser). Raster/video
13
+ * export (PNG/GIF/WebM) lives in the browser studio at gkurt.com/tegaki/studio.
14
+ */
15
+ /** Bundled fonts the CLI can load, keyed by `--font` name. */
16
+ const FONTS = {
17
+ caveat: "Caveat (Latin)",
18
+ italianno: "Italianno (Latin)",
19
+ tangerine: "Tangerine (Latin)",
20
+ parisienne: "Parisienne (Latin)",
21
+ "suez-one": "Suez One (Hebrew + Latin)",
22
+ amiri: "Amiri (Arabic + Latin)",
23
+ tillana: "Tillana (Devanagari + Latin)",
24
+ "klee-one": "Klee One (Japanese + Latin)",
25
+ "nanum-pen-script": "Nanum Pen Script (Korean + Latin)"
26
+ };
27
+ /** Fonts whose scripts need shaping the headless CLI does not apply (RTL / complex GPOS). */
28
+ const NEEDS_SHAPING = /* @__PURE__ */ new Set([
29
+ "suez-one",
30
+ "amiri",
31
+ "tillana"
32
+ ]);
33
+ const MODES = [
34
+ "loop",
35
+ "once",
36
+ "static"
37
+ ];
38
+ const HELP = `tegaki — animated handwriting SVG generator
39
+
40
+ Usage:
41
+ tegaki <text...> [options]
42
+
43
+ Options:
44
+ -o, --output <file> Output path (default: <slug>.svg; "-" writes to stdout)
45
+ -f, --font <name> Bundled font (default: caveat). See --list-fonts.
46
+ -m, --mode <mode> loop | once | static (default: loop)
47
+ loop self-drawing, repeats forever (README hero)
48
+ once draws itself once, then stays complete
49
+ static finished artwork, no animation
50
+ --size <px> Font size in px (default: 100)
51
+ --color <css> Stroke color (default: #1a1a1a)
52
+ --line-height <px> Line height in px (default: from font metrics)
53
+ --stagger <advance> Overlap glyphs instead of drawing them in sequence.
54
+ e.g. "80%" (of the previous glyph) or "0.3" (seconds)
55
+ --stagger-duration <s|auto> Per-glyph duration when staggering (default: auto)
56
+ --pressure <0-1> Variable stroke width for once/static (default: 1)
57
+ --smoothing Smooth strokes onto a spline
58
+ --segment-size <px> Stroke subdivision threshold (default: 2 when applicable)
59
+ -h, --help Show this help
60
+ -v, --version Show version
61
+ --list-fonts List bundled fonts
62
+
63
+ Examples:
64
+ tegaki "Tegaki is awesome"
65
+ tegaki "Hello World" --font tangerine --mode once -o hello.svg
66
+ tegaki "ABC" --stagger 80% --size 140 --color "#222"
67
+ `;
68
+ function fail(message) {
69
+ process.stderr.write(`tegaki: ${message}\n`);
70
+ process.exit(1);
71
+ }
72
+ /** Slugify text into a safe base filename. */
73
+ function slug(text) {
74
+ return text.trim().slice(0, 40).replace(/\s+/g, "-").replace(/[^\w-]/g, "").toLowerCase() || "tegaki";
75
+ }
76
+ function parseArgs(argv) {
77
+ const words = [];
78
+ const opts = {
79
+ text: "",
80
+ output: null,
81
+ font: "caveat",
82
+ fontSize: 100,
83
+ color: "#1a1a1a",
84
+ mode: "loop",
85
+ smoothing: false
86
+ };
87
+ const expectValue = (flag, inline, i) => {
88
+ if (inline !== void 0) return inline;
89
+ const next = argv[++i.v];
90
+ if (next === void 0) fail(`option ${flag} requires a value`);
91
+ return next;
92
+ };
93
+ const idx = { v: 0 };
94
+ for (idx.v = 0; idx.v < argv.length; idx.v++) {
95
+ const arg = argv[idx.v];
96
+ if (!arg.startsWith("-") || arg === "-") {
97
+ words.push(arg);
98
+ continue;
99
+ }
100
+ const eq = arg.indexOf("=");
101
+ const flag = eq >= 0 ? arg.slice(0, eq) : arg;
102
+ const inline = eq >= 0 ? arg.slice(eq + 1) : void 0;
103
+ switch (flag) {
104
+ case "-h":
105
+ case "--help":
106
+ process.stdout.write(HELP);
107
+ process.exit(0);
108
+ break;
109
+ case "-v":
110
+ case "--version":
111
+ process.stdout.write(`${cachedVersion}\n`);
112
+ process.exit(0);
113
+ break;
114
+ case "--list-fonts":
115
+ listFonts();
116
+ process.exit(0);
117
+ break;
118
+ case "-o":
119
+ case "--output":
120
+ opts.output = expectValue(flag, inline, idx);
121
+ break;
122
+ case "-f":
123
+ case "--font":
124
+ opts.font = expectValue(flag, inline, idx).toLowerCase();
125
+ break;
126
+ case "-m":
127
+ case "--mode": {
128
+ const m = expectValue(flag, inline, idx);
129
+ if (!MODES.includes(m)) fail(`unknown mode "${m}" (expected: ${MODES.join(", ")})`);
130
+ opts.mode = m;
131
+ break;
132
+ }
133
+ case "--size":
134
+ opts.fontSize = numeric(flag, expectValue(flag, inline, idx));
135
+ break;
136
+ case "--line-height":
137
+ opts.lineHeight = numeric(flag, expectValue(flag, inline, idx));
138
+ break;
139
+ case "--color":
140
+ opts.color = expectValue(flag, inline, idx);
141
+ break;
142
+ case "--stagger":
143
+ opts.stagger = expectValue(flag, inline, idx);
144
+ break;
145
+ case "--stagger-duration": {
146
+ const v = expectValue(flag, inline, idx);
147
+ opts.staggerDuration = v === "auto" ? "auto" : numeric(flag, v);
148
+ break;
149
+ }
150
+ case "--pressure":
151
+ opts.pressure = numeric(flag, expectValue(flag, inline, idx));
152
+ break;
153
+ case "--smoothing":
154
+ opts.smoothing = true;
155
+ break;
156
+ case "--segment-size":
157
+ opts.segmentSize = numeric(flag, expectValue(flag, inline, idx));
158
+ break;
159
+ default: fail(`unknown option "${flag}" (try --help)`);
160
+ }
161
+ }
162
+ opts.text = words.join(" ");
163
+ return opts;
164
+ }
165
+ function numeric(flag, value) {
166
+ const n = Number(value);
167
+ if (!Number.isFinite(n)) fail(`option ${flag} expects a number, got "${value}"`);
168
+ return n;
169
+ }
170
+ function listFonts() {
171
+ process.stdout.write("Bundled fonts:\n");
172
+ for (const [name, label] of Object.entries(FONTS)) process.stdout.write(` ${name.padEnd(18)} ${label}\n`);
173
+ }
174
+ let cachedVersion = "unknown";
175
+ async function resolveVersion() {
176
+ try {
177
+ const url = new URL("../package.json", import.meta.url);
178
+ const pkg = JSON.parse(await readFile(url, "utf-8"));
179
+ if (pkg.version) cachedVersion = pkg.version;
180
+ } catch {}
181
+ }
182
+ /**
183
+ * Load a built font bundle by name. Tries the shipped `dist/fonts/<name>` first
184
+ * (the `npx` path), then the source `fonts/<name>` (running from a checkout).
185
+ */
186
+ async function loadBundle(name) {
187
+ if (!(name in FONTS)) fail(`unknown font "${name}". Run \`tegaki --list-fonts\` to see the ${Object.keys(FONTS).length} bundled fonts.`);
188
+ const candidates = [new URL(`./fonts/${name}/bundle.mjs`, import.meta.url), new URL(`../fonts/${name}/bundle.ts`, import.meta.url)];
189
+ for (const url of candidates) try {
190
+ const mod = await import(url.href);
191
+ if (mod?.default) return mod.default;
192
+ } catch {}
193
+ fail(`could not load font bundle "${name}".`);
194
+ }
195
+ async function main() {
196
+ await resolveVersion();
197
+ const opts = parseArgs(process.argv.slice(2));
198
+ if (!opts.text) {
199
+ process.stdout.write(HELP);
200
+ process.exit(0);
201
+ }
202
+ if (NEEDS_SHAPING.has(opts.font)) process.stderr.write(`tegaki: note — "${opts.font}" is a complex/RTL script. The CLI lays glyphs out by advance width without shaping, so joins and direction may be wrong. Use the browser studio (gkurt.com/tegaki/studio) for shaped output.\n`);
203
+ const font = await loadBundle(opts.font);
204
+ const timing = opts.stagger !== void 0 ? { stagger: {
205
+ advance: /%$/.test(opts.stagger) ? opts.stagger : Number(opts.stagger),
206
+ duration: opts.staggerDuration ?? "auto"
207
+ } } : void 0;
208
+ const svg = textToSvg(opts.text, font, {
209
+ fontSize: opts.fontSize,
210
+ lineHeight: opts.lineHeight,
211
+ color: opts.color,
212
+ mode: opts.mode,
213
+ pressure: opts.pressure,
214
+ smoothing: opts.smoothing,
215
+ segmentSize: opts.segmentSize,
216
+ timing
217
+ });
218
+ if (opts.output === "-") {
219
+ process.stdout.write(svg);
220
+ return;
221
+ }
222
+ const outPath = opts.output ?? `${slug(opts.text)}.svg`;
223
+ await writeFile(outPath, svg, "utf-8");
224
+ const kb = (Buffer.byteLength(svg, "utf-8") / 1024).toFixed(1);
225
+ process.stderr.write(`tegaki: wrote ${outPath} (${opts.mode}, ${FONTS[opts.font]}, ${kb} KB)\n`);
226
+ }
227
+ main().catch((err) => {
228
+ fail(err instanceof Error ? err.message : String(err));
229
+ });
230
+ //#endregion
231
+ export {};
232
+
233
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `tegaki` — turn text into an animated handwriting SVG from the command line.\n *\n * npx tegaki \"Tegaki is awesome\" # → tegaki-is-awesome.svg (looping)\n * npx tegaki \"Hello\" -f tangerine -o hi.svg\n *\n * SVG is the only format emitted: it's the one the renderer can produce with\n * zero native dependencies (no canvas, no headless browser). Raster/video\n * export (PNG/GIF/WebM) lives in the browser studio at gkurt.com/tegaki/studio.\n */\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { type TextToSvgMode, textToSvg } from './lib/textToSvg.ts';\nimport type { TegakiBundle } from './types.ts';\n\n/** Bundled fonts the CLI can load, keyed by `--font` name. */\nconst FONTS: Record<string, string> = {\n caveat: 'Caveat (Latin)',\n italianno: 'Italianno (Latin)',\n tangerine: 'Tangerine (Latin)',\n parisienne: 'Parisienne (Latin)',\n 'suez-one': 'Suez One (Hebrew + Latin)',\n amiri: 'Amiri (Arabic + Latin)',\n tillana: 'Tillana (Devanagari + Latin)',\n 'klee-one': 'Klee One (Japanese + Latin)',\n 'nanum-pen-script': 'Nanum Pen Script (Korean + Latin)',\n};\n\n/** Fonts whose scripts need shaping the headless CLI does not apply (RTL / complex GPOS). */\nconst NEEDS_SHAPING = new Set(['suez-one', 'amiri', 'tillana']);\n\nconst MODES: TextToSvgMode[] = ['loop', 'once', 'static'];\n\ninterface CliOptions {\n text: string;\n output: string | null; // null → derive from text; '-' → stdout\n font: string;\n fontSize: number;\n lineHeight?: number;\n color: string;\n mode: TextToSvgMode;\n stagger?: string;\n staggerDuration?: number | 'auto';\n pressure?: number;\n smoothing: boolean;\n segmentSize?: number;\n}\n\nconst HELP = `tegaki — animated handwriting SVG generator\n\nUsage:\n tegaki <text...> [options]\n\nOptions:\n -o, --output <file> Output path (default: <slug>.svg; \"-\" writes to stdout)\n -f, --font <name> Bundled font (default: caveat). See --list-fonts.\n -m, --mode <mode> loop | once | static (default: loop)\n loop self-drawing, repeats forever (README hero)\n once draws itself once, then stays complete\n static finished artwork, no animation\n --size <px> Font size in px (default: 100)\n --color <css> Stroke color (default: #1a1a1a)\n --line-height <px> Line height in px (default: from font metrics)\n --stagger <advance> Overlap glyphs instead of drawing them in sequence.\n e.g. \"80%\" (of the previous glyph) or \"0.3\" (seconds)\n --stagger-duration <s|auto> Per-glyph duration when staggering (default: auto)\n --pressure <0-1> Variable stroke width for once/static (default: 1)\n --smoothing Smooth strokes onto a spline\n --segment-size <px> Stroke subdivision threshold (default: 2 when applicable)\n -h, --help Show this help\n -v, --version Show version\n --list-fonts List bundled fonts\n\nExamples:\n tegaki \"Tegaki is awesome\"\n tegaki \"Hello World\" --font tangerine --mode once -o hello.svg\n tegaki \"ABC\" --stagger 80% --size 140 --color \"#222\"\n`;\n\nfunction fail(message: string): never {\n process.stderr.write(`tegaki: ${message}\\n`);\n process.exit(1);\n}\n\n/** Slugify text into a safe base filename. */\nfunction slug(text: string): string {\n const s = text\n .trim()\n .slice(0, 40)\n .replace(/\\s+/g, '-')\n .replace(/[^\\w-]/g, '')\n .toLowerCase();\n return s || 'tegaki';\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n const words: string[] = [];\n const opts: CliOptions = {\n text: '',\n output: null,\n font: 'caveat',\n fontSize: 100,\n color: '#1a1a1a',\n mode: 'loop',\n smoothing: false,\n };\n\n // Pull the value for a flag, supporting both `--flag value` and `--flag=value`.\n const expectValue = (flag: string, inline: string | undefined, i: { v: number }): string => {\n if (inline !== undefined) return inline;\n const next = argv[++i.v];\n if (next === undefined) fail(`option ${flag} requires a value`);\n return next;\n };\n\n const idx = { v: 0 };\n for (idx.v = 0; idx.v < argv.length; idx.v++) {\n const arg = argv[idx.v]!;\n if (!arg.startsWith('-') || arg === '-') {\n words.push(arg);\n continue;\n }\n const eq = arg.indexOf('=');\n const flag = eq >= 0 ? arg.slice(0, eq) : arg;\n const inline = eq >= 0 ? arg.slice(eq + 1) : undefined;\n\n switch (flag) {\n case '-h':\n case '--help':\n process.stdout.write(HELP);\n process.exit(0);\n break;\n case '-v':\n case '--version':\n process.stdout.write(`${cachedVersion}\\n`);\n process.exit(0);\n break;\n case '--list-fonts':\n listFonts();\n process.exit(0);\n break;\n case '-o':\n case '--output':\n opts.output = expectValue(flag, inline, idx);\n break;\n case '-f':\n case '--font':\n opts.font = expectValue(flag, inline, idx).toLowerCase();\n break;\n case '-m':\n case '--mode': {\n const m = expectValue(flag, inline, idx) as TextToSvgMode;\n if (!MODES.includes(m)) fail(`unknown mode \"${m}\" (expected: ${MODES.join(', ')})`);\n opts.mode = m;\n break;\n }\n case '--size':\n opts.fontSize = numeric(flag, expectValue(flag, inline, idx));\n break;\n case '--line-height':\n opts.lineHeight = numeric(flag, expectValue(flag, inline, idx));\n break;\n case '--color':\n opts.color = expectValue(flag, inline, idx);\n break;\n case '--stagger':\n opts.stagger = expectValue(flag, inline, idx);\n break;\n case '--stagger-duration': {\n const v = expectValue(flag, inline, idx);\n opts.staggerDuration = v === 'auto' ? 'auto' : numeric(flag, v);\n break;\n }\n case '--pressure':\n opts.pressure = numeric(flag, expectValue(flag, inline, idx));\n break;\n case '--smoothing':\n opts.smoothing = true;\n break;\n case '--segment-size':\n opts.segmentSize = numeric(flag, expectValue(flag, inline, idx));\n break;\n default:\n fail(`unknown option \"${flag}\" (try --help)`);\n }\n }\n\n opts.text = words.join(' ');\n return opts;\n}\n\nfunction numeric(flag: string, value: string): number {\n const n = Number(value);\n if (!Number.isFinite(n)) fail(`option ${flag} expects a number, got \"${value}\"`);\n return n;\n}\n\nfunction listFonts(): void {\n process.stdout.write('Bundled fonts:\\n');\n for (const [name, label] of Object.entries(FONTS)) {\n process.stdout.write(` ${name.padEnd(18)} ${label}\\n`);\n }\n}\n\n// Resolved once at startup (before args are parsed) so `--version` can print synchronously.\nlet cachedVersion = 'unknown';\nasync function resolveVersion(): Promise<void> {\n try {\n const url = new URL('../package.json', import.meta.url);\n const pkg = JSON.parse(await readFile(url, 'utf-8')) as { version?: string };\n if (pkg.version) cachedVersion = pkg.version;\n } catch {\n // leave default\n }\n}\n\n/**\n * Load a built font bundle by name. Tries the shipped `dist/fonts/<name>` first\n * (the `npx` path), then the source `fonts/<name>` (running from a checkout).\n */\nasync function loadBundle(name: string): Promise<TegakiBundle> {\n if (!(name in FONTS)) {\n fail(`unknown font \"${name}\". Run \\`tegaki --list-fonts\\` to see the ${Object.keys(FONTS).length} bundled fonts.`);\n }\n const candidates = [\n new URL(`./fonts/${name}/bundle.mjs`, import.meta.url), // built: dist/cli.mjs → dist/fonts/<name>/bundle.mjs\n new URL(`../fonts/${name}/bundle.ts`, import.meta.url), // dev: src/cli.ts → fonts/<name>/bundle.ts\n ];\n for (const url of candidates) {\n try {\n const mod = (await import(url.href)) as { default?: TegakiBundle };\n if (mod?.default) return mod.default;\n } catch {\n // try next candidate\n }\n }\n fail(`could not load font bundle \"${name}\".`);\n}\n\nasync function main(): Promise<void> {\n await resolveVersion();\n const opts = parseArgs(process.argv.slice(2));\n\n if (!opts.text) {\n process.stdout.write(HELP);\n process.exit(0);\n }\n\n if (NEEDS_SHAPING.has(opts.font)) {\n process.stderr.write(\n `tegaki: note — \"${opts.font}\" is a complex/RTL script. The CLI lays glyphs out by ` +\n `advance width without shaping, so joins and direction may be wrong. ` +\n `Use the browser studio (gkurt.com/tegaki/studio) for shaped output.\\n`,\n );\n }\n\n const font = await loadBundle(opts.font);\n\n const timing =\n opts.stagger !== undefined\n ? {\n stagger: {\n advance: /%$/.test(opts.stagger) ? (opts.stagger as `${number}%`) : Number(opts.stagger),\n duration: opts.staggerDuration ?? ('auto' as const),\n },\n }\n : undefined;\n\n const svg = textToSvg(opts.text, font, {\n fontSize: opts.fontSize,\n lineHeight: opts.lineHeight,\n color: opts.color,\n mode: opts.mode,\n pressure: opts.pressure,\n smoothing: opts.smoothing,\n segmentSize: opts.segmentSize,\n timing,\n });\n\n if (opts.output === '-') {\n process.stdout.write(svg);\n return;\n }\n\n const outPath = opts.output ?? `${slug(opts.text)}.svg`;\n await writeFile(outPath, svg, 'utf-8');\n const kb = (Buffer.byteLength(svg, 'utf-8') / 1024).toFixed(1);\n process.stderr.write(`tegaki: wrote ${outPath} (${opts.mode}, ${FONTS[opts.font]}, ${kb} KB)\\n`);\n}\n\nmain().catch((err: unknown) => {\n fail(err instanceof Error ? err.message : String(err));\n});\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import { S as TimedPoint, _ as TegakiEffectName, a as BBox, b as TegakiMultiEffectName, c as CSSLength, d as LineCap, f as PathCommand, g as TegakiEffectConfigs, h as TegakiBundle, i as ShapedGlyph, l as FontOutput, m as Stroke, o as BUNDLE_VERSION, p as Point, r as BundleShaper, s as COMPATIBLE_BUNDLE_VERSIONS, t as ShaperFactory, u as GlyphData, v as TegakiEffects, x as TegakiSingletonEffectName, y as TegakiGlyphData } from "../shaper-registry-DmlJGnE3.mjs";
2
- import { A as TimelineEntry, C as resolveEffects, D as computeTextLayout, E as computeLayoutBbox, M as computeTimeline, O as Timeline, S as hasRenderHooks, T as TextLayout, _ as RenderStageContext, a as CreateElementFn, b as findEffects, c as TimeControlMode, d as getBundle, f as registerBundle, g as EffectDefinition, h as drawGlyph, i as TegakiEngine, j as TimelineStaggerConfig, k as TimelineConfig, l as TimeControlProp, m as ensureFontFace, n as buildRootProps, o as TegakiEngineOptions, p as resolveBundle, r as domCreateElement, s as TegakiQuality, t as buildChildren, u as createBundle, v as ResolvedEffect, w as LayoutBBox, x as getEffectDefinition, y as findEffect } from "../index-H08fD9_Z.mjs";
3
- export { type BBox, BUNDLE_VERSION, type BundleShaper, COMPATIBLE_BUNDLE_VERSIONS, type CSSLength, type CreateElementFn, type EffectDefinition, type FontOutput, type GlyphData, type LayoutBBox, type LineCap, type PathCommand, type Point, type RenderStageContext, type ResolvedEffect, type ShapedGlyph, type ShaperFactory, type Stroke, type TegakiBundle, type TegakiEffectConfigs, type TegakiEffectName, type TegakiEffects, TegakiEngine, type TegakiEngineOptions, type TegakiGlyphData, type TegakiMultiEffectName, type TegakiQuality, type TegakiSingletonEffectName, type TextLayout, type TimeControlMode, type TimeControlProp, type TimedPoint, type Timeline, type TimelineConfig, type TimelineEntry, type TimelineStaggerConfig, buildChildren, buildRootProps, computeLayoutBbox, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, findEffect, findEffects, getBundle, getEffectDefinition, hasRenderHooks, registerBundle, resolveBundle, resolveEffects };
2
+ import { A as computeTextLayout, C as findEffects, D as LayoutBBox, E as resolveEffects, F as computeTimeline, M as TimelineConfig, N as TimelineEntry, O as TextLayout, P as TimelineStaggerConfig, S as findEffect, T as hasRenderHooks, _ as ensureFontFace, a as CreateElementFn, b as RenderStageContext, c as TimeControlMode, d as getBundle, f as registerBundle, g as textToSvg, h as TextToSvgOptions, i as TegakiEngine, j as Timeline, k as computeLayoutBbox, l as TimeControlProp, m as TextToSvgMode, n as buildRootProps, o as TegakiEngineOptions, p as resolveBundle, r as domCreateElement, s as TegakiQuality, t as buildChildren, u as createBundle, v as drawGlyph, w as getEffectDefinition, x as ResolvedEffect, y as EffectDefinition } from "../index-D-lHv505.mjs";
3
+ export { type BBox, BUNDLE_VERSION, type BundleShaper, COMPATIBLE_BUNDLE_VERSIONS, type CSSLength, type CreateElementFn, type EffectDefinition, type FontOutput, type GlyphData, type LayoutBBox, type LineCap, type PathCommand, type Point, type RenderStageContext, type ResolvedEffect, type ShapedGlyph, type ShaperFactory, type Stroke, type TegakiBundle, type TegakiEffectConfigs, type TegakiEffectName, type TegakiEffects, TegakiEngine, type TegakiEngineOptions, type TegakiGlyphData, type TegakiMultiEffectName, type TegakiQuality, type TegakiSingletonEffectName, type TextLayout, type TextToSvgMode, type TextToSvgOptions, type TimeControlMode, type TimeControlProp, type TimedPoint, type Timeline, type TimelineConfig, type TimelineEntry, type TimelineStaggerConfig, buildChildren, buildRootProps, computeLayoutBbox, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, findEffect, findEffects, getBundle, getEffectDefinition, hasRenderHooks, registerBundle, resolveBundle, resolveEffects, textToSvg };
@@ -1,2 +1,3 @@
1
- import { _ as findEffect, a as createBundle, b as hasRenderHooks, c as resolveBundle, d as computeTimeline, f as computeLayoutBbox, h as drawGlyph, i as domCreateElement, l as BUNDLE_VERSION, m as ensureFontFace, n as buildChildren, o as getBundle, p as computeTextLayout, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS, v as findEffects, x as resolveEffects, y as getEffectDefinition } from "../core-B87FE4Wo.mjs";
2
- export { BUNDLE_VERSION, COMPATIBLE_BUNDLE_VERSIONS, TegakiEngine, buildChildren, buildRootProps, computeLayoutBbox, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, findEffect, findEffects, getBundle, getEffectDefinition, hasRenderHooks, registerBundle, resolveBundle, resolveEffects };
1
+ import { _ as getEffectDefinition, a as createBundle, c as resolveBundle, d as computeLayoutBbox, f as computeTextLayout, g as findEffects, h as findEffect, i as domCreateElement, l as BUNDLE_VERSION, m as drawGlyph, n as buildChildren, o as getBundle, p as ensureFontFace, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS, v as hasRenderHooks, y as resolveEffects } from "../core-VIBhSqFM.mjs";
2
+ import { n as computeTimeline, t as textToSvg } from "../textToSvg-GTQMM8Le.mjs";
3
+ export { BUNDLE_VERSION, COMPATIBLE_BUNDLE_VERSIONS, TegakiEngine, buildChildren, buildRootProps, computeLayoutBbox, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, findEffect, findEffects, getBundle, getEffectDefinition, hasRenderHooks, registerBundle, resolveBundle, resolveEffects, textToSvg };