@flatkit/compiler 0.37.0 → 0.38.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/dist/analysis.js +2 -2
- package/dist/{chunk-KXKX2RMX.js → chunk-BPX2MOW2.js} +3 -3
- package/dist/chunk-BPX2MOW2.js.map +1 -0
- package/dist/{chunk-43MGBTUK.js → chunk-RQKXBHHM.js} +2 -2
- package/dist/cli/flatc.js +99 -22
- package/dist/cli/flatc.js.map +1 -1
- package/dist/cli/render.d.ts +22 -2
- package/dist/cli/render.js +31 -5
- package/dist/cli/render.js.map +1 -1
- package/dist/compile.js +1 -1
- package/dist/index.js +2 -2
- package/docs/dsl-gotchas.md +16 -1
- package/docs/expressions-and-stdlib.md +6 -0
- package/docs/scene-and-drawing.md +7 -0
- package/docs/tooling.md +43 -8
- package/package.json +5 -5
- package/prompts/flatink-core.md +13 -3
- package/prompts/flatink-lite.md +5 -2
- package/prompts/role-asset-creator.md +9 -5
- package/dist/chunk-KXKX2RMX.js.map +0 -1
- /package/dist/{chunk-43MGBTUK.js.map → chunk-RQKXBHHM.js.map} +0 -0
package/dist/analysis.js
CHANGED
|
@@ -12,9 +12,9 @@ function resolveRefs(layers, byName) {
|
|
|
12
12
|
}
|
|
13
13
|
function compileFlatpack(programSrc, assetSrcs = [], media = {}) {
|
|
14
14
|
const libs = assetSrcs.map((src) => parseFlatLib(src));
|
|
15
|
-
const symbols = libs.flatMap((l) => l.symbols);
|
|
16
|
-
const folders = libs.flatMap((l) => l.folders);
|
|
17
15
|
const prog = parseProgramFull(programSrc);
|
|
16
|
+
const symbols = [...libs.flatMap((l) => l.symbols), ...prog.symbols ?? []];
|
|
17
|
+
const folders = libs.flatMap((l) => l.folders);
|
|
18
18
|
const byName = new Map(symbols.map((s) => [s.name, s.id]));
|
|
19
19
|
symbols.forEach((s) => resolveRefs(s.layers, byName));
|
|
20
20
|
resolveRefs(prog.layers, byName);
|
|
@@ -45,4 +45,4 @@ export {
|
|
|
45
45
|
compileFlatpack,
|
|
46
46
|
packToJSON
|
|
47
47
|
};
|
|
48
|
-
//# sourceMappingURL=chunk-
|
|
48
|
+
//# sourceMappingURL=chunk-BPX2MOW2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compile.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// compile.ts — the \"modern SWF\" compiler (RFC, step P2).\n//\n// Takes the PROGRAM (`.flatink`) + the ASSETS (`.flat`) and produces a resolved `Doc` —\n// the \".flatpack\" v1 (= the baked doc the player already plays). Steps:\n// 1. parse the assets → symbols;\n// 2. parse the program → composition + behavior (`@Name` refs);\n// 3. RESOLVE refs by name (instances → symbol id), across ALL libs;\n// 4. assemble the Doc.\n//\n// v1: no binary optimization nor media embedding (P2b). Pure, no DOM.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Doc, Item, Layer } from '@flatkit/types'\nimport { parseFlatLib, parseProgramFull } from '@flatkit/engine/flatFormat'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\n\n/** Resolves `symbolId: '@Name'` instances into real ids (recursive, across groups). */\nfunction resolveRefs(layers: Layer[], byName: Map<string, string>): void {\n const walk = (items: Item[]) => {\n for (const it of items) {\n if (isInstance(it) && it.symbolId.startsWith('@')) it.symbolId = byName.get(it.symbolId.slice(1)) ?? it.symbolId\n if (isGroup(it)) it.layers.forEach((l) => walk(l.items))\n }\n }\n layers.forEach((l) => walk(l.items))\n}\n\n/** Source of a media (base64 data-URI) by declared path (`asset … \"path\" …`). */\nexport type MediaMap = Record<string, { mime: string; data: string }>\n\n/**\n * Compile a `.flatink` program + its `.flat` assets → playable `Doc` (the \".flatpack\").\n * `assetSrcs` = the text of each `.flat` lib. `media` = the content of the referenced media files\n * (by path). Symbol refs (by name) are resolved across the whole set of libs; declared media\n * are EMBEDDED (path → data-URI).\n */\nexport function compileFlatpack(programSrc: string, assetSrcs: string[] = [], media: MediaMap = {}): Doc {\n const libs = assetSrcs.map((src) => parseFlatLib(src))\n const prog = parseProgramFull(programSrc)\n // The program's OWN symbols (`symbol \"X\" { … }` written in the `.flatink`) come last: on a name both\n // declare, the program's wins over the library's.\n const symbols = [...libs.flatMap((l) => l.symbols), ...(prog.symbols ?? [])]\n const folders = libs.flatMap((l) => l.folders) // library folders (organization)\n const byName = new Map(symbols.map((s) => [s.name, s.id]))\n symbols.forEach((s) => resolveRefs(s.layers, byName)) // cross-lib refs\n resolveRefs(prog.layers, byName) // program refs\n\n // Embed declared media (data = path → provided data-URI). Missing = left as is.\n const assets = (prog.assets ?? []).map((a) => {\n const m = media[a.data]\n const r = m ? { ...a, mime: m.mime, data: m.data } : a\n // A font without an explicit family defaults to its declared id — which is exactly what text targets via\n // `font \"<id>\"`. Gives consumers an explicit `family` instead of relying on a `family || id` fallback.\n return r.kind === 'font' && !r.family ? { ...r, family: r.id } : r\n })\n\n return {\n width: prog.width,\n height: prog.height,\n ...(prog.background ? { background: prog.background } : {}),\n symbols,\n ...(folders.length ? { folders } : {}),\n layers: prog.layers,\n ...(prog.variables && Object.keys(prog.variables).length ? { variables: prog.variables } : {}),\n ...(prog.imports?.length ? { imports: prog.imports } : {}),\n ...(prog.functions?.length ? { functions: prog.functions } : {}),\n timeline: prog.timeline ?? { fps: 24, durationFrames: 60, tracks: [] },\n ...(prog.interactions?.length ? { interactions: prog.interactions } : {}),\n ...(prog.interactors?.length ? { interactors: prog.interactors } : {}),\n ...(assets.length ? { assets } : {}),\n }\n}\n\n/** Serialize the `.flatpack` v1 (JSON of the compiled Doc). */\nexport const packToJSON = (doc: Doc): string => JSON.stringify(doc)\n"],"mappings":";AAaA,SAAS,cAAc,wBAAwB;AAC/C,SAAS,SAAS,kBAAkB;AAGpC,SAAS,YAAY,QAAiB,QAAmC;AACvE,QAAM,OAAO,CAAC,UAAkB;AAC9B,eAAW,MAAM,OAAO;AACtB,UAAI,WAAW,EAAE,KAAK,GAAG,SAAS,WAAW,GAAG,EAAG,IAAG,WAAW,OAAO,IAAI,GAAG,SAAS,MAAM,CAAC,CAAC,KAAK,GAAG;AACxG,UAAI,QAAQ,EAAE,EAAG,IAAG,OAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AAAA,IACzD;AAAA,EACF;AACA,SAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AACrC;AAWO,SAAS,gBAAgB,YAAoB,YAAsB,CAAC,GAAG,QAAkB,CAAC,GAAQ;AACvG,QAAM,OAAO,UAAU,IAAI,CAAC,QAAQ,aAAa,GAAG,CAAC;AACrD,QAAM,OAAO,iBAAiB,UAAU;AAGxC,QAAM,UAAU,CAAC,GAAG,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,GAAI,KAAK,WAAW,CAAC,CAAE;AAC3E,QAAM,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO;AAC7C,QAAM,SAAS,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;AACzD,UAAQ,QAAQ,CAAC,MAAM,YAAY,EAAE,QAAQ,MAAM,CAAC;AACpD,cAAY,KAAK,QAAQ,MAAM;AAG/B,QAAM,UAAU,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM;AAC5C,UAAM,IAAI,MAAM,EAAE,IAAI;AACtB,UAAM,IAAI,IAAI,EAAE,GAAG,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,IAAI;AAGrD,WAAO,EAAE,SAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,GAAG,QAAQ,EAAE,GAAG,IAAI;AAAA,EACnE,CAAC;AAED,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IACzD;AAAA,IACA,GAAI,QAAQ,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpC,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,OAAO,KAAK,KAAK,SAAS,EAAE,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC5F,GAAI,KAAK,SAAS,SAAS,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IACxD,GAAI,KAAK,WAAW,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC9D,UAAU,KAAK,YAAY,EAAE,KAAK,IAAI,gBAAgB,IAAI,QAAQ,CAAC,EAAE;AAAA,IACrE,GAAI,KAAK,cAAc,SAAS,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IACvE,GAAI,KAAK,aAAa,SAAS,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,IACpE,GAAI,OAAO,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,EACpC;AACF;AAGO,IAAM,aAAa,CAAC,QAAqB,KAAK,UAAU,GAAG;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
compileFlatpack
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BPX2MOW2.js";
|
|
4
4
|
|
|
5
5
|
// src/scopeProgram.ts
|
|
6
6
|
function matchBrace(s, open) {
|
|
@@ -1181,4 +1181,4 @@ export {
|
|
|
1181
1181
|
applyFixes,
|
|
1182
1182
|
repairLoop
|
|
1183
1183
|
};
|
|
1184
|
-
//# sourceMappingURL=chunk-
|
|
1184
|
+
//# sourceMappingURL=chunk-RQKXBHHM.js.map
|
package/dist/cli/flatc.js
CHANGED
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
lintDocReport,
|
|
6
6
|
programDiagnostics,
|
|
7
7
|
repairLoop
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-RQKXBHHM.js";
|
|
9
9
|
import {
|
|
10
10
|
compileFlatpack,
|
|
11
11
|
packToJSON
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-BPX2MOW2.js";
|
|
13
13
|
|
|
14
14
|
// src/cli/flatc.ts
|
|
15
15
|
import { readFileSync, writeFileSync, existsSync, readdirSync, watch, mkdirSync, copyFileSync } from "fs";
|
|
@@ -79,9 +79,13 @@ Usage:
|
|
|
79
79
|
--since <ver> what the given files (.flatink / .flat, as many as you like) draw DIFFERENTLY from
|
|
80
80
|
that version \u2014 a one-shot report for an upgrade, with file:line. Recorded so far:
|
|
81
81
|
before 0.36 a path made only of lines was rounded at its gentle turns. Lists the
|
|
82
|
-
paths that move by 1% of their size or more
|
|
82
|
+
paths that move by 1% of their size or more, and the open lines that move by 2
|
|
83
|
+
units or more; add --all for the slighter ones
|
|
83
84
|
--seed N (with --play) seed of random(). A replay is always seeded (default 1): the same script
|
|
84
85
|
gives the same result twice; another N gives another draw
|
|
86
|
+
--settle N (with --play, --render --script) simulation steps after each pointer event (default 1:
|
|
87
|
+
a press, a move, a release each take a frame, so "every frame" sees the drag;
|
|
88
|
+
0 = the instantaneous replay). "settle": N on a gesture sets it for that one
|
|
85
89
|
--trace (with --play) HUMAN-READABLE log per gesture: emitted sends + variable diff (debug)
|
|
86
90
|
--script <f> JSON gesture script: [{ "type": "down|move|up|cancel", "x", "y" }, { "type": "set", "name", "value" }, { "type": "wait", "frames": N }, { "type": "wheel", "dy": N }]
|
|
87
91
|
semantic (by NAME, the engine resolves coords): { "type": "drag", "source", "target" } \xB7 { "type": "tap", "target" } (or "x", "y": a point)
|
|
@@ -90,7 +94,9 @@ Usage:
|
|
|
90
94
|
"wait" lets the simulation run N fixed steps (60 Hz): "every frame" + playhead advance like in real playback
|
|
91
95
|
"expect" self-verifies: { "type": "expect", "sends": ["done"], "vars": { "score": 3 } } \u2192 exits \u22600 on mismatch
|
|
92
96
|
(sends = sequence of names emitted SINCE the last expect; vars = current state). Great in CI.
|
|
93
|
-
--render render a PNG IMAGE (headless skia): see what we draw (positioning)
|
|
97
|
+
--render render a PNG IMAGE (headless skia): see what we draw (positioning). With --script,
|
|
98
|
+
replays the gestures first and renders the state they reach; a { "type": "shot",
|
|
99
|
+
"name": "x" } gesture writes <out>.x.png at that point; a failed "expect" exits \u22600
|
|
94
100
|
--preview wrap ONE symbol of a <library.flat> into a playable Doc (a single centered instance on an
|
|
95
101
|
auto-sized stage) \u2192 a .flatpack to drop in the browser player, or a PNG with --render.
|
|
96
102
|
No wrapper .flatink to author by hand. Output defaults to <library>.<symbol>.flatpack/.png
|
|
@@ -294,7 +300,7 @@ function loadDoc(filePath, noLibs = false, explicitFlats = []) {
|
|
|
294
300
|
if (filePath.endsWith(".flatink")) return buildDocFromProgram(filePath, explicitFlats, "inline", "", noLibs).doc;
|
|
295
301
|
return sanitizeDoc(JSON.parse(readFileSync(filePath, "utf8")));
|
|
296
302
|
}
|
|
297
|
-
function playOnce(filePath, scriptPath, trace, noLibs = false, explicitFlats = [], seed) {
|
|
303
|
+
function playOnce(filePath, scriptPath, trace, noLibs = false, explicitFlats = [], seed, settle) {
|
|
298
304
|
if (!scriptPath) {
|
|
299
305
|
process.stderr.write("flatc: --play requires --script <gestures.json>\n");
|
|
300
306
|
return 1;
|
|
@@ -323,7 +329,9 @@ function playOnce(filePath, scriptPath, trace, noLibs = false, explicitFlats = [
|
|
|
323
329
|
process.stderr.write("flatc: the script must be an array of gestures\n");
|
|
324
330
|
return 1;
|
|
325
331
|
}
|
|
326
|
-
const res = playHeadless(doc, gestures, { trace, seed });
|
|
332
|
+
const res = playHeadless(doc, gestures, { trace, seed, settle });
|
|
333
|
+
for (const w of res.warnings ?? []) process.stderr.write(`flatc: warning: ${w}
|
|
334
|
+
`);
|
|
327
335
|
if (!trace) process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
328
336
|
else
|
|
329
337
|
for (const s of res.steps ?? []) {
|
|
@@ -369,7 +377,7 @@ async function renderDocToFile(doc, outPath, frame, vars, scale, steps, params =
|
|
|
369
377
|
`);
|
|
370
378
|
return 0;
|
|
371
379
|
}
|
|
372
|
-
async function renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs = false, params = {}, explicitFlats = []) {
|
|
380
|
+
async function renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs = false, params = {}, explicitFlats = [], scriptPath = "", settle) {
|
|
373
381
|
let doc;
|
|
374
382
|
try {
|
|
375
383
|
doc = loadDoc(filePath, noLibs, explicitFlats);
|
|
@@ -382,7 +390,66 @@ async function renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, n
|
|
|
382
390
|
if (dur > 0 && frame >= dur) process.stderr.write(`flatc: --frame ${frame} is past the timeline (durationFrames ${dur}); the playhead WRAPS, so this renders frame ${frame % dur}
|
|
383
391
|
`);
|
|
384
392
|
const outPath = out ? resolve(out) : join(dirname(filePath), basename(filePath, extname(filePath)) + ".png");
|
|
385
|
-
|
|
393
|
+
const sc = scaleAuto ? autoScale(doc.width, doc.height) : scale;
|
|
394
|
+
if (!scriptPath) return renderDocToFile(doc, outPath, frame, vars, sc, steps, params);
|
|
395
|
+
return renderAfterScript(doc, outPath, scriptPath, frame, vars, sc, steps, params, settle);
|
|
396
|
+
}
|
|
397
|
+
async function renderAfterScript(doc, outPath, scriptPath, frame, vars, scale, steps, params, settle) {
|
|
398
|
+
if (!existsSync(scriptPath)) {
|
|
399
|
+
process.stderr.write(`flatc: script not found: ${scriptPath}
|
|
400
|
+
`);
|
|
401
|
+
return 1;
|
|
402
|
+
}
|
|
403
|
+
let gestures;
|
|
404
|
+
try {
|
|
405
|
+
gestures = JSON.parse(readFileSync(scriptPath, "utf8"));
|
|
406
|
+
} catch (e) {
|
|
407
|
+
process.stderr.write(`flatc: invalid JSON script: ${e.message}
|
|
408
|
+
`);
|
|
409
|
+
return 1;
|
|
410
|
+
}
|
|
411
|
+
if (!Array.isArray(gestures)) {
|
|
412
|
+
process.stderr.write("flatc: the script must be an array of gestures\n");
|
|
413
|
+
return 1;
|
|
414
|
+
}
|
|
415
|
+
const shotPath = (name) => join(dirname(outPath), `${basename(outPath, extname(outPath))}.${name.replace(/[^\w.-]/g, "_")}${extname(outPath) || ".png"}`);
|
|
416
|
+
const written = [];
|
|
417
|
+
let failures = [], warned = [];
|
|
418
|
+
try {
|
|
419
|
+
const { createRenderer } = await import("./render.js");
|
|
420
|
+
const r = await createRenderer(doc, { scale, params: Object.keys(params).length ? params : void 0, interactive: true, settle });
|
|
421
|
+
try {
|
|
422
|
+
await r.frame(frame, { vars: Object.keys(vars).length ? vars : void 0 });
|
|
423
|
+
let shots = 0;
|
|
424
|
+
for (const g of gestures) {
|
|
425
|
+
if (g.type === "shot") {
|
|
426
|
+
const p = shotPath(g.name || String(++shots));
|
|
427
|
+
writeFileSync(p, await r.capture());
|
|
428
|
+
written.push(basename(p));
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const rep = r.play([g]);
|
|
432
|
+
failures = rep.expectFailures;
|
|
433
|
+
warned = rep.warnings;
|
|
434
|
+
}
|
|
435
|
+
if (steps > 0) r.play([{ type: "wait", frames: Math.floor(steps) }]);
|
|
436
|
+
writeFileSync(outPath, await r.capture());
|
|
437
|
+
} finally {
|
|
438
|
+
r.close();
|
|
439
|
+
}
|
|
440
|
+
} catch (e) {
|
|
441
|
+
process.stderr.write(`flatc: render failed: ${e.message}
|
|
442
|
+
`);
|
|
443
|
+
return 1;
|
|
444
|
+
}
|
|
445
|
+
const played = gestures.filter((g) => g.type !== "shot").length;
|
|
446
|
+
process.stdout.write(`flatc: ${basename(outPath)} \u2713 ${doc.width}\xD7${doc.height} \xD7${scale} \xB7 after ${played} gesture(s)${steps ? ` \xB7 ${steps} step(s)` : ""}${written.length ? ` \xB7 shots: ${written.join(", ")}` : ""}
|
|
447
|
+
`);
|
|
448
|
+
for (const w of warned) process.stderr.write(`flatc: warning: ${w}
|
|
449
|
+
`);
|
|
450
|
+
for (const f of failures) process.stderr.write(`flatc: \u2717 ${f}
|
|
451
|
+
`);
|
|
452
|
+
return failures.length ? 1 : 0;
|
|
386
453
|
}
|
|
387
454
|
var gcd = (a, b) => {
|
|
388
455
|
a = Math.abs(a);
|
|
@@ -442,6 +509,15 @@ function buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec = {}) {
|
|
|
442
509
|
const dur = previewDuration(symbol, symbols);
|
|
443
510
|
const timeline = { fps: tl?.fps ?? 24, durationFrames: dur, tracks: [] };
|
|
444
511
|
let doc = { width: 1, height: 1, layers: [layer], symbols, timeline };
|
|
512
|
+
const entries = Object.entries(setSpec);
|
|
513
|
+
if (entries.length) {
|
|
514
|
+
const known = /* @__PURE__ */ new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)]);
|
|
515
|
+
for (const [k, v] of entries) {
|
|
516
|
+
if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: "${symbol.name}" exposes no such param (${[...known].join(", ") || "none"})
|
|
517
|
+
`);
|
|
518
|
+
else (instance.params ??= {})[k] = v;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
445
521
|
const sampleFrames = (n) => {
|
|
446
522
|
const step = Math.max(1, Math.ceil(n / 240));
|
|
447
523
|
const fs = [];
|
|
@@ -449,7 +525,7 @@ function buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec = {}) {
|
|
|
449
525
|
if (fs[fs.length - 1] !== n - 1) fs.push(n - 1);
|
|
450
526
|
return fs;
|
|
451
527
|
};
|
|
452
|
-
const bb = bboxMode === "all" ? containerBBoxUnion(doc, instance, sampleFrames(dur)) : containerBBox(doc, instance, 0);
|
|
528
|
+
const bb = bboxMode === "all" ? containerBBoxUnion(doc, instance, sampleFrames(dur), instance.transform, true) : containerBBox(doc, instance, 0, instance.transform, false, true);
|
|
453
529
|
if (bb) {
|
|
454
530
|
doc = { ...doc, width: Math.max(1, Math.ceil(bb.maxX - bb.minX) + pad * 2), height: Math.max(1, Math.ceil(bb.maxY - bb.minY) + pad * 2) };
|
|
455
531
|
instance.transform = { ...IDENTITY, e: pad - bb.minX, f: pad - bb.minY };
|
|
@@ -457,15 +533,6 @@ function buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec = {}) {
|
|
|
457
533
|
doc = { ...doc, width: 512, height: 512 };
|
|
458
534
|
instance.transform = { ...IDENTITY, e: 256, f: 256 };
|
|
459
535
|
}
|
|
460
|
-
const entries = Object.entries(setSpec);
|
|
461
|
-
if (entries.length) {
|
|
462
|
-
const known = /* @__PURE__ */ new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)]);
|
|
463
|
-
for (const [k, v] of entries) {
|
|
464
|
-
if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: "${symbol.name}" exposes no such param (${[...known].join(", ") || "none"})
|
|
465
|
-
`);
|
|
466
|
-
else (instance.params ??= {})[k] = v;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
536
|
return { doc, symbol, others: symbols.filter((s) => s !== symbol).map((s) => s.name) };
|
|
470
537
|
}
|
|
471
538
|
async function previewOnce(flatPath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto) {
|
|
@@ -478,6 +545,11 @@ async function previewOnce(flatPath, symbolName, out, frame, vars, scale, steps,
|
|
|
478
545
|
return 1;
|
|
479
546
|
}
|
|
480
547
|
const { doc, symbol, others } = built;
|
|
548
|
+
if (frame > 0 && symbol.states?.length) {
|
|
549
|
+
const sm = symbol.states[0];
|
|
550
|
+
process.stderr.write(`flatc: --frame ${frame} does not move "${symbol.name}": its pose is set by its states (\`${sm.param}\`), not by the playhead. To see a state, or a point between two: --set ${sm.param}=<${sm.states.map((x) => x.name).join(" | ")} | a value, e.g. ${sm.param}=0.5>
|
|
551
|
+
`);
|
|
552
|
+
}
|
|
481
553
|
const stem = `${basename(flatPath, extname(flatPath))}.${symbol.name}`;
|
|
482
554
|
let code;
|
|
483
555
|
if (doRender) {
|
|
@@ -494,6 +566,7 @@ async function previewOnce(flatPath, symbolName, out, frame, vars, scale, steps,
|
|
|
494
566
|
`);
|
|
495
567
|
return code;
|
|
496
568
|
}
|
|
569
|
+
var SINCE_OPEN_UNITS = 2;
|
|
497
570
|
function sinceReport(files, since, all = false) {
|
|
498
571
|
const [maj, min] = since.split(".").map(Number);
|
|
499
572
|
if (!Number.isFinite(maj) || !Number.isFinite(min)) {
|
|
@@ -523,7 +596,7 @@ function sinceReport(files, since, all = false) {
|
|
|
523
596
|
const xs = sub.segments.map((sg) => sg.anchor.x), ys = sub.segments.map((sg) => sg.anchor.y);
|
|
524
597
|
const size = Math.hypot(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
|
|
525
598
|
if (moved <= 0 || size <= 0) continue;
|
|
526
|
-
if (moved < size * 0.01 && !all) {
|
|
599
|
+
if (moved < size * 0.01 && (sub.closed || moved < SINCE_OPEN_UNITS) && !all) {
|
|
527
600
|
slight++;
|
|
528
601
|
continue;
|
|
529
602
|
}
|
|
@@ -536,7 +609,7 @@ function sinceReport(files, since, all = false) {
|
|
|
536
609
|
}
|
|
537
610
|
}
|
|
538
611
|
const n = open + closed;
|
|
539
|
-
const rest = slight ? ` ${slight} more move by less than 1% of their size (\`--all\` lists them).` : "";
|
|
612
|
+
const rest = slight ? ` ${slight} more move by less than 1% of their size, and by less than ${SINCE_OPEN_UNITS} units if open (\`--all\` lists them).` : "";
|
|
540
613
|
process.stdout.write(n ? `flatc: ${n} path(s) of lines drawn differently since ${since}: ${open} open, ${closed} closed.${rest} Nothing was modified. (Rounded rectangles and paths mixing lines with curves changed too \u2014 their straight sides no longer bulge \u2014 and are not listed.)
|
|
541
614
|
` : `flatc: no path of lines is drawn visibly differently since ${since} in ${files.length} file(s).${rest}
|
|
542
615
|
`);
|
|
@@ -549,6 +622,7 @@ function run(argv) {
|
|
|
549
622
|
let frame = 0, scale = 2, steps = 0, pad = 24;
|
|
550
623
|
let scaleAuto = false;
|
|
551
624
|
let seed;
|
|
625
|
+
let settle;
|
|
552
626
|
let since = "";
|
|
553
627
|
let bboxMode = "all";
|
|
554
628
|
let assetMode = "inline";
|
|
@@ -579,6 +653,9 @@ function run(argv) {
|
|
|
579
653
|
} else if (a === "--seed") {
|
|
580
654
|
const v = Number(args[++i]);
|
|
581
655
|
if (Number.isFinite(v)) seed = v;
|
|
656
|
+
} else if (a === "--settle") {
|
|
657
|
+
const v = Number(args[++i]);
|
|
658
|
+
if (Number.isFinite(v) && v >= 0) settle = Math.floor(v);
|
|
582
659
|
} else if (a === "--steps") steps = Math.max(0, Number(args[++i] ?? "0") || 0);
|
|
583
660
|
else if (a === "--scale") {
|
|
584
661
|
const v = args[++i] ?? "2";
|
|
@@ -607,8 +684,8 @@ function run(argv) {
|
|
|
607
684
|
if (since) return sinceReport(positional.map((f) => resolve(f)), since, args.includes("--all"));
|
|
608
685
|
const explicitFlats = positional.slice(1);
|
|
609
686
|
if (doPreview) return previewOnce(filePath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto);
|
|
610
|
-
if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats);
|
|
611
|
-
if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed);
|
|
687
|
+
if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats, scriptPath, settle);
|
|
688
|
+
if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed, settle);
|
|
612
689
|
const action = checkOnly && filePath.endsWith(".flat") ? () => checkFlatLibs([filePath, ...explicitFlats]) : () => compileOnce(filePath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix);
|
|
613
690
|
if (doWatch) {
|
|
614
691
|
const code = action();
|
package/dist/cli/flatc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/flatc.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// flatc — the \"modern SWF\" command-line compiler.\n//\n// The VSCode-first flow: you write a `.flatink` program (composition + logic, DSL)\n// + `.flat` asset libs (visuals, exported by the editor) + media, then:\n//\n// flatc game.flatink hero.flat decor.flat -o game.flatpack\n//\n// → a single `.flatpack` (the baked Doc as JSON) that the player runs. Media referenced\n// by `asset \"id\" \"path\" kind` are EMBEDDED (paths relative to the program).\n// ─────────────────────────────────────────────────────────────────────────────\nimport { readFileSync, writeFileSync, existsSync, readdirSync, watch, mkdirSync, copyFileSync } from 'node:fs'\nimport { resolve, dirname, basename, extname, join, relative, isAbsolute } from 'node:path'\nimport { compileFlatpack, packToJSON, type MediaMap } from '../compile'\nimport { parseProgramFull, parseFlatLib } from '@flatkit/engine/flatFormat'\nimport { hasPackage } from '@flatkit/engine/stdlib'\nimport { parseUnits } from '@flatkit/engine/dsl'\nimport { sanitizeDoc } from '@flatkit/engine/validateDoc'\nimport { unitsToFunctions } from '@flatkit/engine/scriptDoc'\nimport { containerBBox, containerBBoxUnion } from '@flatkit/engine/groups'\nimport { isInstance, isGroup } from '@flatkit/engine/layers'\nimport { IDENTITY } from '@flatkit/engine/transform'\nimport { parsePathData } from '@flatkit/engine/svgPath'\nimport { softVertexCount, smoothingDeviation } from '@flatkit/engine/path'\nimport { lintDocReport, docHasErrors } from '../programDoc'\nimport { applyFixes, formatDiagnostics, programDiagnostics, repairLoop } from '../check'\nimport { FlatSyntaxError } from '@flatkit/engine/flatFormat'\nimport { playHeadless, type Gesture } from '@flatkit/player/debug'\nimport type { FuncDef } from '@flatkit/engine/actions'\nimport type { Doc, Instance, Item, Layer, SymbolDef } from '@flatkit/types'\n\nconst MIME: Record<string, string> = {\n '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',\n '.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif',\n '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4',\n // Fonts (RFC 8081 media types) — matches the editor's import (`font/woff2` default, FontFace API).\n '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf', '.otf': 'font/otf',\n // NB: video (.mp4/.webm/…) intentionally omitted — no video runtime in the editor or player yet.\n}\nconst mimeFor = (path: string): string => MIME[extname(path).toLowerCase()] ?? 'application/octet-stream'\n\n/**\n * True if `target` resolves to a file inside `baseDir` (or baseDir itself). Guards against a hostile\n * program escaping its folder via `../` in a media path or a `use` package name — compiling an untrusted\n * `.flatink` must never read (and embed) arbitrary host files.\n */\nfunction isWithin(baseDir: string, target: string): boolean {\n const rel = relative(baseDir, target)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n\nfunction printHelp(): void {\n process.stdout.write(`flatc — compile a FlatInk program into a .flatpack\n\nUsage:\n flatc <program.flatink> [assets.flat …] [-o output.flatpack]\n flatc <program.flatink> --watch\n flatc --play <program.flatink | scene.flatpack> --script <gestures.json>\n flatc --render <program.flatink | scene.flatpack> -o out.png [--frame N] [--at k=v[,k2=v2]] [--scale S]\n flatc --preview <library.flat> [--symbol NAME] [-o out.flatpack | --render -o out.png]\n flatc <library.flat> [more.flat …] --check\n\n program.flatink the program (composition + logic, text DSL)\n assets.flat visual asset libs (default: every .flat in the program's folder)\n -o, --out output file (default: <program>.flatpack — the CANONICAL name, JSON inside)\n --assets MODE media baking: 'inline' (default, base64 in the .flatpack) or 'external'\n (sidecar <out>.assets/ folder; asset.data = relative key — serve the folder and\n play with sameOriginAssetResolver(<flatpackUrl>))\n --check semantic lint only (no .flatpack); exits ≠0 on ERROR (warnings do not stop). Lints a\n program .flatink OR an asset library .flat (per-symbol; several .flat are merged)\n --fix apply the MECHANICAL repairs the diagnostics carry (missing separator, run-on line)\n and re-check, iterating. Writes the file AT MOST ONCE, and never a text it has not\n re-checked; a file it cannot repair is left untouched, mtime included. Implies --check.\n --no-libs do NOT auto-discover the .flat files sitting next to the program (use it in a working\n folder, where a neighbouring scratch file is not a dependency)\n --watch recompile on every change in the folder (agent → player loop)\n --play run the file WITHOUT a canvas, replay --script and print { sends, vars } (JSON)\n --since <ver> what the given files (.flatink / .flat, as many as you like) draw DIFFERENTLY from\n that version — a one-shot report for an upgrade, with file:line. Recorded so far:\n before 0.36 a path made only of lines was rounded at its gentle turns. Lists the\n paths that move by 1% of their size or more; add --all for the slighter ones\n --seed N (with --play) seed of random(). A replay is always seeded (default 1): the same script\n gives the same result twice; another N gives another draw\n --trace (with --play) HUMAN-READABLE log per gesture: emitted sends + variable diff (debug)\n --script <f> JSON gesture script: [{ \"type\": \"down|move|up|cancel\", \"x\", \"y\" }, { \"type\": \"set\", \"name\", \"value\" }, { \"type\": \"wait\", \"frames\": N }, { \"type\": \"wheel\", \"dy\": N }]\n semantic (by NAME, the engine resolves coords): { \"type\": \"drag\", \"source\", \"target\" } · { \"type\": \"tap\", \"target\" } (or \"x\", \"y\": a point)\n · { \"type\": \"scratch\", \"target\" } (sweeps a reveal zone) · { \"type\": \"connect\", \"source\", \"target\" } (pulls a link wire)\n { \"type\": \"key\", \"name\": \"ArrowRight\", \"frames\": N } holds a key (keys.<name> = 1) N steps, then releases it\n \"wait\" lets the simulation run N fixed steps (60 Hz): \"every frame\" + playhead advance like in real playback\n \"expect\" self-verifies: { \"type\": \"expect\", \"sends\": [\"done\"], \"vars\": { \"score\": 3 } } → exits ≠0 on mismatch\n (sends = sequence of names emitted SINCE the last expect; vars = current state). Great in CI.\n --render render a PNG IMAGE (headless skia): see what we draw (positioning)\n --preview wrap ONE symbol of a <library.flat> into a playable Doc (a single centered instance on an\n auto-sized stage) → a .flatpack to drop in the browser player, or a PNG with --render.\n No wrapper .flatink to author by hand. Output defaults to <library>.<symbol>.flatpack/.png\n --symbol NAME (with --preview) which symbol to wrap (default: the first in the lib)\n --bbox all|frame0 (with --preview) auto-size to the UNION over all frames (default 'all', no clipping of\n drifting/rotating/growing motion) or just frame 0 ('frame0', the old behavior)\n --pad N (with --preview) padding in px around the symbol's bounds (default 24)\n --set p=v[,p2=v2] set a SYMBOL's exposed params (works with --preview AND --render); a state param\n takes a state NAME\n or a number (e.g. --set door=open), others take a number → baked into the preview\n --frame N (with --render) target frame (default 0)\n --at k=v[,k2=v2] (with --render) force variables → capture a given state (e.g. a step of an escape)\n --steps N (with --render) run N fixed sim steps (60 Hz, every-frame) BEFORE capture → see a\n stateful act unfold without forcing every derived variable by hand in --at\n --scale S|auto (with --render) resolution factor (default 2); 'auto' picks one from the canvas size\n (enlarges small/thin assets so fine filaments stay legible, large assets stay 1x)\n -h, --help this help\n\nMedia referenced by 'asset \"id\" \"path\" kind' are embedded (paths relative to the program).\n`)\n}\n\n/** How media is baked: `inline` = base64 data-URI in the .flatpack; `external` = relative key + sidecar files. */\ntype AssetMode = 'inline' | 'external'\ntype MediaCopy = { src: string; key: string } // external mode: source file → relative key (forward slashes)\ntype BuildResult = { doc: Doc; flatLibs: number; packages: number; media: number; mediaCopies: MediaCopy[]; src: string }\n\n/**\n * Reads a `.flatink`, resolves libs/packages/media, compiles → standalone Doc. Throws on compile error.\n * `assetMode`: `inline` embeds each media as a base64 data-URI (default); `external` keeps `asset.data` as a\n * relative key (`<assetsDir>/<path>`) and returns the files to copy beside the .flatpack (no base64 bloat).\n */\nfunction buildDocFromProgram(programPath: string, explicitFlats: string[] = [], assetMode: AssetMode = 'inline', assetsDir = '', noLibs = false, srcOverride?: string): BuildResult {\n const baseDir = dirname(programPath)\n // `srcOverride` compiles a CANDIDATE text as if it were the file, without writing it. `--fix` needs it:\n // it must re-check each repair before committing anything, and writing-then-reverting leaves a window\n // where the author's file holds a version we already know we do not want.\n const programSrc = srcOverride ?? readFileSync(programPath, 'utf8')\n const prog = parseProgramFull(programSrc)\n\n // .flat libs: explicit if provided, otherwise auto-discover the .flat files in the program's folder\n // (`--no-libs` opts out — in a working folder, a neighbouring scratch file is not a dependency).\n const flatPaths = new Set<string>(explicitFlats.map((p) => resolve(p)))\n if (!flatPaths.size && !noLibs) for (const f of readdirSync(baseDir).filter((f) => f.endsWith('.flat'))) flatPaths.add(join(baseDir, f))\n\n // PACKAGES: non-stdlib `use \"x\"` → local files inlined (x.flatink = functions, x.flat = symbols).\n const pkgFunctions: FuncDef[] = []\n const localResolved = new Set<string>()\n for (const name of prog.imports ?? []) {\n if (hasPackage(name)) continue // stdlib → left as a reference\n const fink = join(baseDir, name + '.flatink')\n const fflat = join(baseDir, name + '.flat')\n if (!isWithin(baseDir, fink) || !isWithin(baseDir, fflat)) { process.stderr.write(`flatc: package outside the program folder (ignored): \"${name}\"\\n`); continue }\n let found = false\n if (existsSync(fink)) { for (const f of unitsToFunctions(parseUnits(readFileSync(fink, 'utf8')).units)) pkgFunctions.push(f, { ...f, name: `${name}.${f.name}` }); found = true }\n if (existsSync(fflat)) { flatPaths.add(fflat); found = true }\n if (found) localResolved.add(name)\n else process.stderr.write(`flatc: package not found: \"${name}\" (neither stdlib, nor ${name}.flatink / ${name}.flat)\\n`)\n }\n // Parse each lib HERE so a failure names the FILE. Libs are auto-discovered from the folder, so the one\n // that fails is often a neighbour the author never mentioned — a bare \"compile error: …\" sent them hunting\n // through their own program for a fault that was never in it.\n const assetSrcs = [...flatPaths].map((p) => {\n const src = readFileSync(p, 'utf8')\n try { parseFlatLib(src) } catch (e) { throw new Error(`${basename(p)}: ${(e as Error).message} (pass --no-libs to ignore the .flat files sitting next to the program)`, { cause: e }) }\n if (programConstructsIn(src).length) process.stderr.write(`flatc: ${basename(p)}: looks like a PROGRAM saved as .flat — loaded as a symbol library, so its scene/object blocks are ignored. Rename it to .flatink, or pass --no-libs.\\n`)\n return src\n })\n\n // Media: each `asset … \"path\" …` resolved relative to the program. `inline` → base64 data-URI baked in;\n // `external` → `asset.data` becomes a relative key and the file is copied next to the .flatpack.\n const media: MediaMap = {}\n const mediaCopies: MediaCopy[] = []\n for (const a of prog.assets ?? []) {\n const mp = resolve(baseDir, a.data)\n if (!isWithin(baseDir, mp)) { process.stderr.write(`flatc: media outside the program folder (ignored): ${a.data}\\n`); continue }\n if (!existsSync(mp)) { process.stderr.write(`flatc: missing media (ignored): ${a.data}\\n`); continue }\n const mime = mimeFor(mp)\n if (assetMode === 'external') {\n const key = `${assetsDir}/${a.data.replace(/\\\\/g, '/')}` // URL-form relative key (resolved by the host)\n media[a.data] = { mime, data: key }\n mediaCopies.push({ src: mp, key })\n } else {\n media[a.data] = { mime, data: `data:${mime};base64,${readFileSync(mp).toString('base64')}` }\n }\n }\n\n let doc = compileFlatpack(programSrc, assetSrcs, media)\n if (pkgFunctions.length) doc = { ...doc, functions: [...(doc.functions ?? []), ...pkgFunctions] }\n const stdImports = (doc.imports ?? []).filter(hasPackage)\n doc = { ...doc, imports: stdImports.length ? stdImports : undefined }\n // The source travels with the Doc: the source-level passes (`programDiagnostics`) need the author's text,\n // since a parse-level drop and an `object` block binding to nothing leave no trace in the compiled Doc.\n return { doc, flatLibs: flatPaths.size, packages: localResolved.size, media: Object.keys(media).length, mediaCopies, src: programSrc }\n}\n\n/** Compile once (write or --check). Returns the exit code. */\nfunction compileOnce(programPath: string, explicitFlats: string[], out: string, checkOnly: boolean, assetMode: AssetMode = 'inline', noLibs = false, doFix = false): number {\n const outPath = out ? resolve(out) : join(dirname(programPath), basename(programPath, extname(programPath)) + '.flatpack')\n // External mode: sidecar folder next to the .flatpack, e.g. `game.flatpack` → `game.assets/`.\n const assetsDir = assetMode === 'external' ? basename(outPath, extname(outPath)) + '.assets' : ''\n const build = (src?: string) => buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src)\n // `--fix` on a source the parser REJECTS. That case never reaches the diagnostics pass below, and it is\n // exactly where a mechanical repair earns its keep: there is no partial result to work from. Repaired\n // IN MEMORY, one syntax error per turn, so the whole of `--fix` still writes the file at most once.\n let parseRepaired = ''\n if (doFix) {\n let candidate = readFileSync(programPath, 'utf8')\n for (let pass = 0; pass < 10; pass++) {\n try { build(candidate); break } catch (e) {\n if (!(e instanceof FlatSyntaxError) || !e.fix) break\n const { text, applied } = applyFixes(candidate, [{ scope: 'scene', line: e.line, col: e.col, severity: 'error', message: e.message, fix: e.fix }])\n if (!applied) break\n candidate = text\n parseRepaired = candidate\n process.stderr.write(`flatc: --fix: repaired \\`${e.message.split(':')[0]}\\` in ${basename(programPath)}\\n`)\n }\n }\n }\n let built: BuildResult\n try { built = build(parseRepaired || undefined) }\n catch (e) {\n process.stderr.write(`flatc: compile error: ${(e as Error).message}\\n`)\n return 1\n }\n const { doc } = built\n // The SAME pass the `checkProgram` API runs — source-level diagnostics (statements the parser dropped,\n // `object` blocks binding to nothing) merged with the semantic lint of the Doc. Shared on purpose: a\n // check that lives only in the CLI is a check an integrator has to shell out for.\n const diagnostics = programDiagnostics(doc, built.src)\n const report = formatDiagnostics(diagnostics)\n const hasErrors = diagnostics.some((d) => d.severity === 'error')\n // `--fix` applies the MECHANICAL repairs the diagnostics carry (a missing separator, a run-on line),\n // then re-checks. Two rules make it safe to run unattended:\n // • it ITERATES, because repairing one error unmasks the next — a run-on interactor line swallows the\n // statements under it, and their own diagnostics only appear once it is split;\n // • it writes only if the error count strictly DROPS at each pass. An auto-fix that makes things worse\n // must not survive, and the honest way to know is to run the same check again on the result.\n if (doFix) {\n // START from the text the diagnostics were computed on. Falling back to the file would apply edits\n // whose line/col were measured against the parse-repaired text -- a comma insertion survives that,\n // a multi-line replacement does not.\n const original = parseRepaired || readFileSync(programPath, 'utf8')\n const { text, applied: total, first, errors: before } = repairLoop(original, diagnostics, (src) =>\n programDiagnostics(buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src).doc, src))\n if (!total && !parseRepaired) {\n // NOT even a rewrite of the identical bytes: a new mtime wakes every watcher pointed at the folder,\n // and a write fails outright on a read-only checkout we had no reason to touch.\n process.stderr.write(`flatc: --fix: nothing to repair mechanically${hasErrors ? ' — the remaining errors need a decision, not a separator\\n' : '\\n'}`)\n } else {\n writeFileSync(programPath, text) // ONE write, of a text this loop has already re-checked\n process.stderr.write(`flatc: --fix: ${total} repair(s) applied to ${basename(programPath)} · errors ${first} → ${before}\\n`)\n return compileOnce(programPath, explicitFlats, out, checkOnly, assetMode, noLibs, false)\n }\n }\n if (checkOnly) {\n if (report) process.stderr.write(report + '\\n')\n if (hasErrors) return 1\n // Success line deliberately avoids the word \"error\" (the report on a FAILURE already prints \"error\" to\n // stderr + exits ≠0) so a `grep error` over the output can't false-positive — the real signal is the exit\n // code; on success, only warnings remain (non-blocking), surfaced as a count.\n const warnings = diagnostics.length\n process.stdout.write(`flatc: check passed ✓${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n }\n if (report) process.stderr.write(report + '\\n') // compile anyway: diagnostics as warnings\n writeFileSync(outPath, packToJSON(doc))\n const outDir = dirname(outPath)\n for (const c of built.mediaCopies) {\n const dest = join(outDir, ...c.key.split('/'))\n if (!isWithin(outDir, dest)) continue // defensive: never write outside the output folder\n mkdirSync(dirname(dest), { recursive: true })\n copyFileSync(c.src, dest)\n }\n const where = assetMode === 'external' ? ` (external → ${assetsDir}/)` : ''\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.symbols.length} symbol(s) · ${built.flatLibs} lib(s) · ${built.packages} package(s) · ${built.media} media${where}\\n`)\n return 0\n}\n\n/** PROGRAM-only constructs found in what is supposed to be a `.flat` symbol library. `parseFlatLib` reads a\n * `.flat` as a bag of symbols and silently ignores everything else, so a whole program saved under the wrong\n * extension parsed to \"0 symbol(s)\" and passed `--check` with nothing verified — the worst possible answer\n * for tooling, since a check written against the wrong extension ALWAYS passes and looks like a safety net.\n * Both constructs are anchored at line start: only a real block matches, not a mention inside a string. */\nfunction programConstructsIn(src: string): string[] {\n const found: string[] = []\n if (/^[ \\t]*scene\\s*\\{/m.test(src)) found.push('a `scene { … }` block')\n if (/^[ \\t]*object\\s+\"/m.test(src)) found.push('`object \"…\" { … }` blocks')\n return found\n}\n\n/**\n * `--check` for a `.flat` asset LIBRARY (one or several): parse via `parseFlatLib` (NOT the program parser,\n * which would choke on `symbol`/`params`/`layer`), merge the symbols into an empty-scene Doc, and run the SAME\n * Doc lint as a program -> per-symbol diagnostics (`[<Symbol>] line:col: …`). Reuses `lintDocReport`/\n * `docHasErrors`, so every existing check (params-in-expr, undeclared color param in a paint, unknown\n * functions/objects…) applies for free, with the same `[scope] line:col: level: msg` format and exit code.\n * No `behaviorDiagnostics` (those are scene-level — a pure lib has no scene).\n */\nfunction checkFlatLibs(flatPaths: string[]): number {\n const symbols: SymbolDef[] = []\n for (const p of flatPaths) {\n let src: string\n try { src = readFileSync(p, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n const disguised = programConstructsIn(src)\n if (disguised.length) { // a PROGRAM saved as `.flat` — see programConstructsIn\n process.stderr.write(`flatc: ${basename(p)}: this is a PROGRAM, not a symbol library — it contains ${disguised.join(' and ')}, which a .flat never carries and which --check therefore does NOT verify. Rename it to .flatink.\\n`)\n return 1\n }\n try { symbols.push(...parseFlatLib(src).symbols) } // ids are uid-unique across calls -> safe to merge libs\n catch (e) { process.stderr.write(`flatc: ${basename(p)}: ${(e as Error).message}\\n`); return 1 } // a malformed lib -> a clean parse error (not \"[scene] …\")\n }\n const doc: Doc = { width: 1, height: 1, timeline: { fps: 24, durationFrames: 1, tracks: [] }, variables: {}, layers: [], symbols }\n const report = lintDocReport(doc) // scene scope is empty -> only per-symbol diagnostics\n if (report) process.stderr.write(report + '\\n')\n if (docHasErrors(doc)) return 1\n // \"check passed\", NOT \"no errors\" — the word \"error\" must not appear on success (a `grep error` trap); the\n // exit code is the signal, and any remaining lines are warnings (non-blocking), reported as a count.\n const warnings = report ? report.split('\\n').filter(Boolean).length : 0\n process.stdout.write(`flatc: check passed ✓ ${symbols.length} symbol(s)${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n}\n\n/** Loads a Doc from a `.flatink` (compiled) or a `.flatpack`/`.flatpack.json` (already-baked JSON). */\nfunction loadDoc(filePath: string, noLibs = false, explicitFlats: string[] = []): Doc {\n // The `.flat` libraries named on the command line are part of the program, exactly as for a compile:\n // dropping them here left every instance of theirs unresolved — an empty scene, with `--check` green.\n if (filePath.endsWith('.flatink')) return buildDocFromProgram(filePath, explicitFlats, 'inline', '', noLibs).doc\n // .flatpack (canonical) or .flatpack.json (alias): untrusted JSON → normalize before use.\n return sanitizeDoc(JSON.parse(readFileSync(filePath, 'utf8')))\n}\n\n/** --play: runs the file headless, replays --script, prints { sends, vars }. */\nfunction playOnce(filePath: string, scriptPath: string, trace: boolean, noLibs = false, explicitFlats: string[] = [], seed?: number): number {\n if (!scriptPath) { process.stderr.write('flatc: --play requires --script <gestures.json>\\n'); return 1 }\n if (!existsSync(scriptPath)) { process.stderr.write(`flatc: script not found: ${scriptPath}\\n`); return 1 }\n let doc: Doc, gestures: Gesture[]\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n try { gestures = JSON.parse(readFileSync(scriptPath, 'utf8')) as Gesture[] } catch (e) { process.stderr.write(`flatc: invalid JSON script: ${(e as Error).message}\\n`); return 1 }\n if (!Array.isArray(gestures)) { process.stderr.write('flatc: the script must be an array of gestures\\n'); return 1 }\n const res = playHeadless(doc, gestures, { trace, seed })\n if (!trace) process.stdout.write(JSON.stringify(res, null, 2) + '\\n')\n else // --trace: readable log (one gesture per line) → inspection / debug-player.\n for (const s of res.steps ?? []) {\n // `name` · `name=value` · `name{a=1, b=2}` (record payload) — the three shapes the host receives.\n const send = (e: { name: string; value?: number | string; fields?: Record<string, number> }): string =>\n e.fields ? `${e.name}{${Object.entries(e.fields).map(([k, v]) => `${k}=${v}`).join(', ')}}`\n : e.value !== undefined ? `${e.name}=${e.value}` : e.name\n const sends = s.sends.length ? ' sends:[' + s.sends.map(send).join(', ') + ']' : ''\n const vars = Object.entries(s.changed).map(([k, [a, b]]) => `${k}:${JSON.stringify(a)}→${JSON.stringify(b)}`)\n process.stdout.write(`${s.gesture.padEnd(28)}${sends}${vars.length ? ' vars{' + vars.join(' ') + '}' : ''}\\n`)\n }\n // `expect`: any detected mismatch → stderr log + exit code ≠0 (CI self-verification).\n if (res.expectFailures?.length) {\n for (const f of res.expectFailures) process.stderr.write(`flatc: ✗ ${f}\\n`)\n return 1\n }\n return 0\n}\n\n/** Parses `--at`: \"k=v[,k2=v2]\" → variable table (state override for rendering). */\nfunction parseVars(spec: string, into: Record<string, number>): void {\n for (const pair of spec.split(',')) {\n const i = pair.indexOf('=')\n if (i < 0) continue\n const k = pair.slice(0, i).trim(); const v = Number(pair.slice(i + 1).trim())\n if (k && Number.isFinite(v)) into[k] = v\n }\n}\n\n/** `--scale auto`: pick a resolution factor from the canvas size — enlarge small/thin content (so fine\n * filaments stay legible) while leaving large content at 1×. `max(160/long, 48/short)`, clamped [1, 8]. */\nfunction autoScale(w: number, h: number): number {\n const maxSide = Math.max(1, w, h), minSide = Math.max(1, Math.min(w, h))\n return Math.max(1, Math.min(8, Math.round(Math.max(160 / maxSide, 48 / minSide))))\n}\n\n/** Renders a Doc to a PNG file (headless skia). Async (SVG decode + raster). Shared by --render and --preview. */\nasync function renderDocToFile(doc: Doc, outPath: string, frame: number, vars: Record<string, number>, scale: number, steps: number, params: Record<string, string> = {}): Promise<number> {\n try {\n const { renderDocToPng } = await import('./render')\n const png = await renderDocToPng(doc, { frame, vars: Object.keys(vars).length ? vars : undefined, scale, steps: steps || undefined, params: Object.keys(params).length ? params : undefined })\n writeFileSync(outPath, png)\n } catch (e) {\n process.stderr.write(`flatc: render failed: ${(e as Error).message}\\n`)\n if (process.env.FLATC_DEBUG && (e as Error).stack) process.stderr.write(`${(e as Error).stack}\\n`) // FLATC_DEBUG=1 → full stack for diagnosis\n return 1\n }\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.width}×${doc.height} ×${scale}${frame ? ` · frame ${frame}` : ''}${steps ? ` · ${steps} step(s)` : ''}${Object.keys(vars).length ? ` · ${Object.entries(vars).map(([k, v]) => `${k}=${v}`).join(' ')}` : ''}\\n`)\n return 0\n}\n\n/** --render: renders the file to PNG (headless skia). Async (SVG decode + raster). */\nasync function renderOnce(filePath: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, scaleAuto: boolean, noLibs = false, params: Record<string, string> = {}, explicitFlats: string[] = []): Promise<number> {\n let doc: Doc\n // `--no-libs` was parsed and then not passed on, so `--render` still auto-discovered the neighbouring\n // .flat files -- and failed on one, advising the very flag that had been given. Same hole in `--play`.\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n // A frame past the end WRAPS silently, so a render can show frame 20 while the caller asked for 200\n // and reads the result as a renderer bug. It cost exactly that once.\n const dur = doc.timeline?.durationFrames ?? 0\n if (dur > 0 && frame >= dur) process.stderr.write(`flatc: --frame ${frame} is past the timeline (durationFrames ${dur}); the playhead WRAPS, so this renders frame ${frame % dur}\\n`)\n const outPath = out ? resolve(out) : join(dirname(filePath), basename(filePath, extname(filePath)) + '.png')\n return renderDocToFile(doc, outPath, frame, vars, scaleAuto ? autoScale(doc.width, doc.height) : scale, steps, params)\n}\n\nconst gcd = (a: number, b: number): number => { a = Math.abs(a); b = Math.abs(b); while (b) { const t = a % b; a = b; b = t } return a || 1 }\nconst lcm = (a: number, b: number): number => Math.max(1, Math.round((a / gcd(a, b)) * b))\n\n/** Every instance in a symbol's subtree (descends into local groups; does NOT cross into other symbols). */\nfunction* instancesOf(sym: SymbolDef): Generator<Instance> {\n const stack: Item[] = []\n for (const l of sym.layers) stack.push(...l.items)\n for (let it = stack.pop(); it; it = stack.pop()) {\n if (isInstance(it)) yield it\n else if (isGroup(it)) for (const l of it.layers) stack.push(...l.items)\n }\n}\n\n/**\n * Preview window length (frames). A `synced` instance loops on the PARENT's frame, so the symbol's own\n * duration suffices — that's the historical behavior, kept for every symbol with no MovieClip descendant.\n * But an `independent` (`loop`) descendant runs on its OWN duration, immune to the parent's wrap: to SHOW it\n * loop CLEANLY the window must be a common multiple of every such clip's duration (and the root's) — its\n * \"loop seam\". `once` clips only need the window to REACH their last frame (they hold afterwards). We walk\n * the subtree, fold each independent clip's duration into the seam (LCM), and extend past the longest clip,\n * keeping the window a whole number of seams. Capped, so coprime durations can't blow the window up.\n */\nfunction previewDuration(root: SymbolDef, symbols: SymbolDef[]): number {\n const byId = new Map(symbols.map((s) => [s.id, s]))\n const seen = new Set<string>()\n let seam = Math.max(1, root.timeline?.durationFrames ?? 1) // clean-loop period (multiple of every loop clip)\n let longest = seam // window must reach the last frame of any clip\n const walk = (sym: SymbolDef | undefined): void => {\n if (!sym || seen.has(sym.id)) return\n seen.add(sym.id)\n for (const it of instancesOf(sym)) {\n const child = byId.get(it.symbolId)\n const cdur = Math.max(1, child?.timeline?.durationFrames ?? 1)\n const mode = it.playback?.mode\n if (mode === 'independent') { seam = lcm(seam, cdur); longest = Math.max(longest, cdur) }\n else if (mode === 'once') longest = Math.max(longest, cdur)\n walk(child)\n }\n }\n walk(root)\n let dur = seam\n while (dur < longest) dur += seam // keep a clean loop seam while covering the longest MovieClip\n return Math.min(dur, 6000) // hard cap (~4 min @24fps) against a pathological coprime-duration blow-up\n}\n\n/**\n * Wraps ONE symbol from a `.flat` library into a minimal, playable Doc: a single centered instance on a\n * canvas auto-sized to the symbol's frame-0 bounds (+ padding). Lets you preview a library asset without\n * hand-authoring a wrapper `.flatink`. The root timeline borrows the symbol's own fps and a duration long\n * enough to show every nested MovieClip (`loop`/`once`) loop cleanly (`previewDuration`). Throws if the lib\n * is empty or the named symbol is missing.\n */\nfunction buildPreviewDoc(flatPath: string, symbolName: string, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string> = {}): { doc: Doc; symbol: SymbolDef; others: string[] } {\n const { symbols } = parseFlatLib(readFileSync(flatPath, 'utf8'))\n if (!symbols.length) throw new Error('no symbols found in this .flat library')\n let symbol = symbols[0]\n if (symbolName) {\n const found = symbols.find((s) => s.name === symbolName)\n if (!found) throw new Error(`symbol \"${symbolName}\" not found — available: ${symbols.map((s) => s.name).join(', ')}`)\n symbol = found\n }\n\n const instance: Instance = { id: 'preview_instance', kind: 'instance', name: symbol.name, transform: { ...IDENTITY }, symbolId: symbol.id }\n const layer: Layer = { id: 'preview_layer', name: 'preview', visible: true, locked: false, opacity: 1, items: [instance] }\n const tl = symbol.timeline\n const dur = previewDuration(symbol, symbols)\n const timeline = { fps: tl?.fps ?? 24, durationFrames: dur, tracks: [] }\n // ALL symbols stay in the library (the chosen one may instance the others) — only `instance` is on stage.\n let doc: Doc = { width: 1, height: 1, layers: [layer], symbols, timeline }\n\n // Auto-size & center. Default `--bbox all`: UNION over every frame of the symbol (sub-timelines NOT\n // frozen) so motion that drifts/rotates/grows is never clipped. `--bbox frame0` keeps the old frame-0\n // measure (+`--pad` to absorb motion).\n // Sample at most ~240 evenly-spaced frames (+ the last) so a long/abusive `durationFrames` can't blow\n // up memory/time; a sparse union is virtually as tight as every-frame for real motion.\n const sampleFrames = (n: number): number[] => {\n const step = Math.max(1, Math.ceil(n / 240))\n const fs: number[] = []\n for (let f = 0; f < n; f += step) fs.push(f)\n if (fs[fs.length - 1] !== n - 1) fs.push(n - 1)\n return fs\n }\n const bb = bboxMode === 'all' ? containerBBoxUnion(doc, instance, sampleFrames(dur)) : containerBBox(doc, instance, 0)\n if (bb) {\n doc = { ...doc, width: Math.max(1, Math.ceil(bb.maxX - bb.minX) + pad * 2), height: Math.max(1, Math.ceil(bb.maxY - bb.minY) + pad * 2) }\n instance.transform = { ...IDENTITY, e: pad - bb.minX, f: pad - bb.minY }\n } else {\n doc = { ...doc, width: 512, height: 512 } // empty/degenerate symbol: fall back to a fixed centered stage\n instance.transform = { ...IDENTITY, e: 256, f: 256 }\n }\n\n // `--set param=value`: set the preview instance's exposed params (call-site values, raw literals). The\n // renderer resolves them per the symbol's ParamDef / state machines (color → fill, number/bool → scope,\n // state name → driven frame). A light validation warns on names the symbol does not expose.\n const entries = Object.entries(setSpec)\n if (entries.length) {\n const known = new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)])\n for (const [k, v] of entries) {\n if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: \"${symbol.name}\" exposes no such param (${[...known].join(', ') || 'none'})\\n`)\n else (instance.params ??= {})[k] = v\n }\n }\n return { doc, symbol, others: symbols.filter((s) => s !== symbol).map((s) => s.name) }\n}\n\n/** --preview: wraps one symbol of a `.flat` into a playable Doc, then writes a .flatpack (default) or a PNG (with --render). */\nasync function previewOnce(flatPath: string, symbolName: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, doRender: boolean, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string>, scaleAuto: boolean): Promise<number> {\n let built: { doc: Doc; symbol: SymbolDef; others: string[] }\n try { built = buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec) }\n catch (e) { process.stderr.write(`flatc: ${(e as Error).message}\\n`); return 1 }\n const { doc, symbol, others } = built\n const stem = `${basename(flatPath, extname(flatPath))}.${symbol.name}`\n let code: number\n if (doRender) {\n // auto scale from the CONTENT size (minus the padding), so thin filaments still trigger an upscale.\n const sc = scaleAuto ? autoScale(doc.width - 2 * pad, doc.height - 2 * pad) : scale\n code = await renderDocToFile(doc, out ? resolve(out) : join(dirname(flatPath), stem + '.png'), frame, vars, sc, steps)\n } else {\n const outPath = out ? resolve(out) : join(dirname(flatPath), stem + '.flatpack')\n writeFileSync(outPath, packToJSON(doc))\n process.stdout.write(`flatc: ${basename(outPath)} ✓ preview of \"${symbol.name}\" · ${doc.width}×${doc.height} · play it in the browser player\\n`)\n code = 0\n }\n if (!symbolName && others.length) process.stderr.write(`flatc: previewed \"${symbol.name}\"; other symbols in this lib: ${others.join(', ')} (pick with --symbol <name>)\\n`)\n return code\n}\n\n/**\n * `--since <version>`: what these sources draw DIFFERENTLY from that version, for an author deciding what\n * to do about it once, when upgrading. Read from the SOURCE, so every finding has its line.\n *\n * One change is recorded: until 0.36 a path made only of lines was rounded at its gentle turns; it is now\n * drawn straight. Each such path is listed with how far its outline moves — a soft hill written as four\n * points wants `smooth` back, a hexagon does not. This is a report, not a warning of `--check`: a polyline\n * that is MEANT straight has nothing to add to say so, and would be warned about for ever.\n */\nfunction sinceReport(files: string[], since: string, all = false): number {\n const [maj, min] = since.split('.').map(Number)\n if (!Number.isFinite(maj) || !Number.isFinite(min)) { process.stderr.write(`flatc: --since takes a version, e.g. --since 0.35 (got \"${since}\")\\n`); return 1 }\n if (maj > 0 || min >= 36) { process.stdout.write(`flatc: nothing is drawn differently since ${since}\\n`); return 0 }\n let open = 0, closed = 0, slight = 0\n for (const file of files) {\n let src: string\n try { src = readFileSync(file, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read ${file}: ${(e as Error).message}\\n`); return 1 }\n // Every `path \"…\"` of the file that does not already say `smooth` (a text laid `along path \"…\"` is not a shape).\n for (const m of src.matchAll(/(?<!along\\s)\\bpath\\s+\"([^\"]*)\"(?!\\s+smooth\\b)/g)) {\n // Read as it was BEFORE (free-hand material) to measure what smoothing did to it.\n for (const sub of parsePathData(m[1], { smooth: true }).subpaths) {\n if (sub.segments.some((sg) => sg.inHandle || sg.outHandle) || !softVertexCount(sub)) continue // has a curve, or only sharp corners\n const moved = smoothingDeviation(sub)\n const xs = sub.segments.map((sg) => sg.anchor.x), ys = sub.segments.map((sg) => sg.anchor.y)\n const size = Math.hypot(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys))\n // Listed when the outline moves by 1% of the shape's size or more — RELATIVE, because artwork is often\n // drawn in a large space and scaled down by its container. Below that the two renderings are a few\n // pixels apart at most; `--all` lists those too.\n if (moved <= 0 || size <= 0) continue\n if (moved < size * 0.01 && !all) { slight++; continue }\n const line = src.slice(0, m.index).split('\\n').length\n if (sub.closed) closed++; else open++\n process.stdout.write(`${file}:${line}: ${sub.closed ? 'closed' : 'open'} path of ${sub.segments.length} points was rounded before 0.36 and is now straight — it moves by up to ${Math.round(moved * 10) / 10} units (${Math.round((1000 * moved) / (size || 1)) / 10}% of its size). Write \\`smooth\\` after its path data to keep the curve.\\n`)\n }\n }\n }\n const n = open + closed\n const rest = slight ? ` ${slight} more move by less than 1% of their size (\\`--all\\` lists them).` : ''\n process.stdout.write(n\n ? `flatc: ${n} path(s) of lines drawn differently since ${since}: ${open} open, ${closed} closed.${rest} Nothing was modified. (Rounded rectangles and paths mixing lines with curves changed too — their straight sides no longer bulge — and are not listed.)\\n`\n : `flatc: no path of lines is drawn visibly differently since ${since} in ${files.length} file(s).${rest}\\n`)\n return 0\n}\n\nexport function run(argv: string[]): number | Promise<number> {\n const args = argv.slice(2)\n let out = '', scriptPath = '', symbolName = ''\n let checkOnly = false, doFix = false, doWatch = false, doPlay = false, doRender = false, doTrace = false, doPreview = false, noLibs = false\n let frame = 0, scale = 2, steps = 0, pad = 24\n let scaleAuto = false\n let seed: number | undefined\n let since = ''\n let bboxMode: 'all' | 'frame0' = 'all'\n let assetMode: AssetMode = 'inline'\n const vars: Record<string, number> = {}\n const setSpec: Record<string, string> = {} // `--set param=value` (state name or number) for --preview\n const positional: string[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a === '-o' || a === '--out') out = args[++i] ?? ''\n else if (a === '--script') scriptPath = resolve(args[++i] ?? '')\n else if (a === '--assets') assetMode = args[++i] === 'external' ? 'external' : 'inline'\n else if (a === '--check') checkOnly = true\n else if (a === '--fix') { doFix = true; checkOnly = true }\n else if (a === '--no-libs') noLibs = true\n else if (a === '--watch') doWatch = true\n else if (a === '--play') doPlay = true\n else if (a === '--trace') doTrace = true\n else if (a === '--render') doRender = true\n else if (a === '--preview') doPreview = true\n else if (a === '--symbol') symbolName = args[++i] ?? ''\n else if (a === '--pad') pad = Math.max(0, Number(args[++i] ?? '24') || 0)\n else if (a === '--bbox') bboxMode = args[++i] === 'frame0' ? 'frame0' : 'all'\n else if (a === '--frame') frame = Number(args[++i] ?? '0') || 0\n else if (a === '--since') since = args[++i] ?? ''\n else if (a === '--all') { /* read by --since */ }\n else if (a === '--seed') { const v = Number(args[++i]); if (Number.isFinite(v)) seed = v }\n else if (a === '--steps') steps = Math.max(0, Number(args[++i] ?? '0') || 0)\n else if (a === '--scale') { const v = args[++i] ?? '2'; if (v === 'auto') scaleAuto = true; else scale = Number(v) || 2 }\n else if (a === '--at') parseVars(args[++i] ?? '', vars)\n else if (a === '--set') for (const pair of (args[++i] ?? '').split(',')) { const j = pair.indexOf('='); if (j > 0) setSpec[pair.slice(0, j).trim()] = pair.slice(j + 1).trim() }\n else if (a === '-h' || a === '--help') { printHelp(); return 0 }\n else positional.push(a)\n }\n if (!positional.length) { printHelp(); return 1 }\n\n const filePath = resolve(positional[0])\n if (!existsSync(filePath)) { process.stderr.write(`flatc: not found: ${filePath}\\n`); return 1 }\n\n if (since) return sinceReport(positional.map((f) => resolve(f)), since, args.includes('--all'))\n const explicitFlats = positional.slice(1)\n if (doPreview) return previewOnce(filePath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto)\n if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats)\n if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed)\n // `--check <library>.flat`: a `.flat` first positional is an asset LIB, not a program → lint via parseFlatLib\n // (the following positionals are more `.flat` libs to merge). Every other path is unchanged.\n const action: () => number = checkOnly && filePath.endsWith('.flat')\n ? () => checkFlatLibs([filePath, ...explicitFlats])\n : () => compileOnce(filePath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix)\n if (doWatch) {\n const code = action()\n const baseDir = dirname(filePath)\n let timer: ReturnType<typeof setTimeout> | undefined\n // We ignore changes to the OUTPUT (.flatpack) — otherwise writing it would re-trigger the compile in a loop.\n watch(baseDir, { recursive: false }, (_e, filename) => {\n if (filename && (filename.endsWith('.flatpack') || filename.endsWith('.flatpack.json'))) return\n clearTimeout(timer); timer = setTimeout(action, 80)\n })\n process.stdout.write(`flatc: watching ${baseDir} … (Ctrl+C to stop)\\n`)\n return code\n }\n return action()\n}\n"],"mappings":"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","names":["f"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/flatc.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// flatc — the \"modern SWF\" command-line compiler.\n//\n// The VSCode-first flow: you write a `.flatink` program (composition + logic, DSL)\n// + `.flat` asset libs (visuals, exported by the editor) + media, then:\n//\n// flatc game.flatink hero.flat decor.flat -o game.flatpack\n//\n// → a single `.flatpack` (the baked Doc as JSON) that the player runs. Media referenced\n// by `asset \"id\" \"path\" kind` are EMBEDDED (paths relative to the program).\n// ─────────────────────────────────────────────────────────────────────────────\nimport { readFileSync, writeFileSync, existsSync, readdirSync, watch, mkdirSync, copyFileSync } from 'node:fs'\nimport { resolve, dirname, basename, extname, join, relative, isAbsolute } from 'node:path'\nimport { compileFlatpack, packToJSON, type MediaMap } from '../compile'\nimport { parseProgramFull, parseFlatLib } from '@flatkit/engine/flatFormat'\nimport { hasPackage } from '@flatkit/engine/stdlib'\nimport { parseUnits } from '@flatkit/engine/dsl'\nimport { sanitizeDoc } from '@flatkit/engine/validateDoc'\nimport { unitsToFunctions } from '@flatkit/engine/scriptDoc'\nimport { containerBBox, containerBBoxUnion } from '@flatkit/engine/groups'\nimport { isInstance, isGroup } from '@flatkit/engine/layers'\nimport { IDENTITY } from '@flatkit/engine/transform'\nimport { parsePathData } from '@flatkit/engine/svgPath'\nimport { softVertexCount, smoothingDeviation } from '@flatkit/engine/path'\nimport { lintDocReport, docHasErrors } from '../programDoc'\nimport { applyFixes, formatDiagnostics, programDiagnostics, repairLoop } from '../check'\nimport { FlatSyntaxError } from '@flatkit/engine/flatFormat'\nimport { playHeadless, type Gesture } from '@flatkit/player/debug'\nimport type { FuncDef } from '@flatkit/engine/actions'\nimport type { Doc, Instance, Item, Layer, SymbolDef } from '@flatkit/types'\n\nconst MIME: Record<string, string> = {\n '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',\n '.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif',\n '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4',\n // Fonts (RFC 8081 media types) — matches the editor's import (`font/woff2` default, FontFace API).\n '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf', '.otf': 'font/otf',\n // NB: video (.mp4/.webm/…) intentionally omitted — no video runtime in the editor or player yet.\n}\nconst mimeFor = (path: string): string => MIME[extname(path).toLowerCase()] ?? 'application/octet-stream'\n\n/**\n * True if `target` resolves to a file inside `baseDir` (or baseDir itself). Guards against a hostile\n * program escaping its folder via `../` in a media path or a `use` package name — compiling an untrusted\n * `.flatink` must never read (and embed) arbitrary host files.\n */\nfunction isWithin(baseDir: string, target: string): boolean {\n const rel = relative(baseDir, target)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n\nfunction printHelp(): void {\n process.stdout.write(`flatc — compile a FlatInk program into a .flatpack\n\nUsage:\n flatc <program.flatink> [assets.flat …] [-o output.flatpack]\n flatc <program.flatink> --watch\n flatc --play <program.flatink | scene.flatpack> --script <gestures.json>\n flatc --render <program.flatink | scene.flatpack> -o out.png [--frame N] [--at k=v[,k2=v2]] [--scale S]\n flatc --preview <library.flat> [--symbol NAME] [-o out.flatpack | --render -o out.png]\n flatc <library.flat> [more.flat …] --check\n\n program.flatink the program (composition + logic, text DSL)\n assets.flat visual asset libs (default: every .flat in the program's folder)\n -o, --out output file (default: <program>.flatpack — the CANONICAL name, JSON inside)\n --assets MODE media baking: 'inline' (default, base64 in the .flatpack) or 'external'\n (sidecar <out>.assets/ folder; asset.data = relative key — serve the folder and\n play with sameOriginAssetResolver(<flatpackUrl>))\n --check semantic lint only (no .flatpack); exits ≠0 on ERROR (warnings do not stop). Lints a\n program .flatink OR an asset library .flat (per-symbol; several .flat are merged)\n --fix apply the MECHANICAL repairs the diagnostics carry (missing separator, run-on line)\n and re-check, iterating. Writes the file AT MOST ONCE, and never a text it has not\n re-checked; a file it cannot repair is left untouched, mtime included. Implies --check.\n --no-libs do NOT auto-discover the .flat files sitting next to the program (use it in a working\n folder, where a neighbouring scratch file is not a dependency)\n --watch recompile on every change in the folder (agent → player loop)\n --play run the file WITHOUT a canvas, replay --script and print { sends, vars } (JSON)\n --since <ver> what the given files (.flatink / .flat, as many as you like) draw DIFFERENTLY from\n that version — a one-shot report for an upgrade, with file:line. Recorded so far:\n before 0.36 a path made only of lines was rounded at its gentle turns. Lists the\n paths that move by 1% of their size or more, and the open lines that move by 2\n units or more; add --all for the slighter ones\n --seed N (with --play) seed of random(). A replay is always seeded (default 1): the same script\n gives the same result twice; another N gives another draw\n --settle N (with --play, --render --script) simulation steps after each pointer event (default 1:\n a press, a move, a release each take a frame, so \"every frame\" sees the drag;\n 0 = the instantaneous replay). \"settle\": N on a gesture sets it for that one\n --trace (with --play) HUMAN-READABLE log per gesture: emitted sends + variable diff (debug)\n --script <f> JSON gesture script: [{ \"type\": \"down|move|up|cancel\", \"x\", \"y\" }, { \"type\": \"set\", \"name\", \"value\" }, { \"type\": \"wait\", \"frames\": N }, { \"type\": \"wheel\", \"dy\": N }]\n semantic (by NAME, the engine resolves coords): { \"type\": \"drag\", \"source\", \"target\" } · { \"type\": \"tap\", \"target\" } (or \"x\", \"y\": a point)\n · { \"type\": \"scratch\", \"target\" } (sweeps a reveal zone) · { \"type\": \"connect\", \"source\", \"target\" } (pulls a link wire)\n { \"type\": \"key\", \"name\": \"ArrowRight\", \"frames\": N } holds a key (keys.<name> = 1) N steps, then releases it\n \"wait\" lets the simulation run N fixed steps (60 Hz): \"every frame\" + playhead advance like in real playback\n \"expect\" self-verifies: { \"type\": \"expect\", \"sends\": [\"done\"], \"vars\": { \"score\": 3 } } → exits ≠0 on mismatch\n (sends = sequence of names emitted SINCE the last expect; vars = current state). Great in CI.\n --render render a PNG IMAGE (headless skia): see what we draw (positioning). With --script,\n replays the gestures first and renders the state they reach; a { \"type\": \"shot\",\n \"name\": \"x\" } gesture writes <out>.x.png at that point; a failed \"expect\" exits ≠0\n --preview wrap ONE symbol of a <library.flat> into a playable Doc (a single centered instance on an\n auto-sized stage) → a .flatpack to drop in the browser player, or a PNG with --render.\n No wrapper .flatink to author by hand. Output defaults to <library>.<symbol>.flatpack/.png\n --symbol NAME (with --preview) which symbol to wrap (default: the first in the lib)\n --bbox all|frame0 (with --preview) auto-size to the UNION over all frames (default 'all', no clipping of\n drifting/rotating/growing motion) or just frame 0 ('frame0', the old behavior)\n --pad N (with --preview) padding in px around the symbol's bounds (default 24)\n --set p=v[,p2=v2] set a SYMBOL's exposed params (works with --preview AND --render); a state param\n takes a state NAME\n or a number (e.g. --set door=open), others take a number → baked into the preview\n --frame N (with --render) target frame (default 0)\n --at k=v[,k2=v2] (with --render) force variables → capture a given state (e.g. a step of an escape)\n --steps N (with --render) run N fixed sim steps (60 Hz, every-frame) BEFORE capture → see a\n stateful act unfold without forcing every derived variable by hand in --at\n --scale S|auto (with --render) resolution factor (default 2); 'auto' picks one from the canvas size\n (enlarges small/thin assets so fine filaments stay legible, large assets stay 1x)\n -h, --help this help\n\nMedia referenced by 'asset \"id\" \"path\" kind' are embedded (paths relative to the program).\n`)\n}\n\n/** How media is baked: `inline` = base64 data-URI in the .flatpack; `external` = relative key + sidecar files. */\ntype AssetMode = 'inline' | 'external'\ntype MediaCopy = { src: string; key: string } // external mode: source file → relative key (forward slashes)\ntype BuildResult = { doc: Doc; flatLibs: number; packages: number; media: number; mediaCopies: MediaCopy[]; src: string }\n\n/**\n * Reads a `.flatink`, resolves libs/packages/media, compiles → standalone Doc. Throws on compile error.\n * `assetMode`: `inline` embeds each media as a base64 data-URI (default); `external` keeps `asset.data` as a\n * relative key (`<assetsDir>/<path>`) and returns the files to copy beside the .flatpack (no base64 bloat).\n */\nfunction buildDocFromProgram(programPath: string, explicitFlats: string[] = [], assetMode: AssetMode = 'inline', assetsDir = '', noLibs = false, srcOverride?: string): BuildResult {\n const baseDir = dirname(programPath)\n // `srcOverride` compiles a CANDIDATE text as if it were the file, without writing it. `--fix` needs it:\n // it must re-check each repair before committing anything, and writing-then-reverting leaves a window\n // where the author's file holds a version we already know we do not want.\n const programSrc = srcOverride ?? readFileSync(programPath, 'utf8')\n const prog = parseProgramFull(programSrc)\n\n // .flat libs: explicit if provided, otherwise auto-discover the .flat files in the program's folder\n // (`--no-libs` opts out — in a working folder, a neighbouring scratch file is not a dependency).\n const flatPaths = new Set<string>(explicitFlats.map((p) => resolve(p)))\n if (!flatPaths.size && !noLibs) for (const f of readdirSync(baseDir).filter((f) => f.endsWith('.flat'))) flatPaths.add(join(baseDir, f))\n\n // PACKAGES: non-stdlib `use \"x\"` → local files inlined (x.flatink = functions, x.flat = symbols).\n const pkgFunctions: FuncDef[] = []\n const localResolved = new Set<string>()\n for (const name of prog.imports ?? []) {\n if (hasPackage(name)) continue // stdlib → left as a reference\n const fink = join(baseDir, name + '.flatink')\n const fflat = join(baseDir, name + '.flat')\n if (!isWithin(baseDir, fink) || !isWithin(baseDir, fflat)) { process.stderr.write(`flatc: package outside the program folder (ignored): \"${name}\"\\n`); continue }\n let found = false\n if (existsSync(fink)) { for (const f of unitsToFunctions(parseUnits(readFileSync(fink, 'utf8')).units)) pkgFunctions.push(f, { ...f, name: `${name}.${f.name}` }); found = true }\n if (existsSync(fflat)) { flatPaths.add(fflat); found = true }\n if (found) localResolved.add(name)\n else process.stderr.write(`flatc: package not found: \"${name}\" (neither stdlib, nor ${name}.flatink / ${name}.flat)\\n`)\n }\n // Parse each lib HERE so a failure names the FILE. Libs are auto-discovered from the folder, so the one\n // that fails is often a neighbour the author never mentioned — a bare \"compile error: …\" sent them hunting\n // through their own program for a fault that was never in it.\n const assetSrcs = [...flatPaths].map((p) => {\n const src = readFileSync(p, 'utf8')\n try { parseFlatLib(src) } catch (e) { throw new Error(`${basename(p)}: ${(e as Error).message} (pass --no-libs to ignore the .flat files sitting next to the program)`, { cause: e }) }\n if (programConstructsIn(src).length) process.stderr.write(`flatc: ${basename(p)}: looks like a PROGRAM saved as .flat — loaded as a symbol library, so its scene/object blocks are ignored. Rename it to .flatink, or pass --no-libs.\\n`)\n return src\n })\n\n // Media: each `asset … \"path\" …` resolved relative to the program. `inline` → base64 data-URI baked in;\n // `external` → `asset.data` becomes a relative key and the file is copied next to the .flatpack.\n const media: MediaMap = {}\n const mediaCopies: MediaCopy[] = []\n for (const a of prog.assets ?? []) {\n const mp = resolve(baseDir, a.data)\n if (!isWithin(baseDir, mp)) { process.stderr.write(`flatc: media outside the program folder (ignored): ${a.data}\\n`); continue }\n if (!existsSync(mp)) { process.stderr.write(`flatc: missing media (ignored): ${a.data}\\n`); continue }\n const mime = mimeFor(mp)\n if (assetMode === 'external') {\n const key = `${assetsDir}/${a.data.replace(/\\\\/g, '/')}` // URL-form relative key (resolved by the host)\n media[a.data] = { mime, data: key }\n mediaCopies.push({ src: mp, key })\n } else {\n media[a.data] = { mime, data: `data:${mime};base64,${readFileSync(mp).toString('base64')}` }\n }\n }\n\n let doc = compileFlatpack(programSrc, assetSrcs, media)\n if (pkgFunctions.length) doc = { ...doc, functions: [...(doc.functions ?? []), ...pkgFunctions] }\n const stdImports = (doc.imports ?? []).filter(hasPackage)\n doc = { ...doc, imports: stdImports.length ? stdImports : undefined }\n // The source travels with the Doc: the source-level passes (`programDiagnostics`) need the author's text,\n // since a parse-level drop and an `object` block binding to nothing leave no trace in the compiled Doc.\n return { doc, flatLibs: flatPaths.size, packages: localResolved.size, media: Object.keys(media).length, mediaCopies, src: programSrc }\n}\n\n/** Compile once (write or --check). Returns the exit code. */\nfunction compileOnce(programPath: string, explicitFlats: string[], out: string, checkOnly: boolean, assetMode: AssetMode = 'inline', noLibs = false, doFix = false): number {\n const outPath = out ? resolve(out) : join(dirname(programPath), basename(programPath, extname(programPath)) + '.flatpack')\n // External mode: sidecar folder next to the .flatpack, e.g. `game.flatpack` → `game.assets/`.\n const assetsDir = assetMode === 'external' ? basename(outPath, extname(outPath)) + '.assets' : ''\n const build = (src?: string) => buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src)\n // `--fix` on a source the parser REJECTS. That case never reaches the diagnostics pass below, and it is\n // exactly where a mechanical repair earns its keep: there is no partial result to work from. Repaired\n // IN MEMORY, one syntax error per turn, so the whole of `--fix` still writes the file at most once.\n let parseRepaired = ''\n if (doFix) {\n let candidate = readFileSync(programPath, 'utf8')\n for (let pass = 0; pass < 10; pass++) {\n try { build(candidate); break } catch (e) {\n if (!(e instanceof FlatSyntaxError) || !e.fix) break\n const { text, applied } = applyFixes(candidate, [{ scope: 'scene', line: e.line, col: e.col, severity: 'error', message: e.message, fix: e.fix }])\n if (!applied) break\n candidate = text\n parseRepaired = candidate\n process.stderr.write(`flatc: --fix: repaired \\`${e.message.split(':')[0]}\\` in ${basename(programPath)}\\n`)\n }\n }\n }\n let built: BuildResult\n try { built = build(parseRepaired || undefined) }\n catch (e) {\n process.stderr.write(`flatc: compile error: ${(e as Error).message}\\n`)\n return 1\n }\n const { doc } = built\n // The SAME pass the `checkProgram` API runs — source-level diagnostics (statements the parser dropped,\n // `object` blocks binding to nothing) merged with the semantic lint of the Doc. Shared on purpose: a\n // check that lives only in the CLI is a check an integrator has to shell out for.\n const diagnostics = programDiagnostics(doc, built.src)\n const report = formatDiagnostics(diagnostics)\n const hasErrors = diagnostics.some((d) => d.severity === 'error')\n // `--fix` applies the MECHANICAL repairs the diagnostics carry (a missing separator, a run-on line),\n // then re-checks. Two rules make it safe to run unattended:\n // • it ITERATES, because repairing one error unmasks the next — a run-on interactor line swallows the\n // statements under it, and their own diagnostics only appear once it is split;\n // • it writes only if the error count strictly DROPS at each pass. An auto-fix that makes things worse\n // must not survive, and the honest way to know is to run the same check again on the result.\n if (doFix) {\n // START from the text the diagnostics were computed on. Falling back to the file would apply edits\n // whose line/col were measured against the parse-repaired text -- a comma insertion survives that,\n // a multi-line replacement does not.\n const original = parseRepaired || readFileSync(programPath, 'utf8')\n const { text, applied: total, first, errors: before } = repairLoop(original, diagnostics, (src) =>\n programDiagnostics(buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src).doc, src))\n if (!total && !parseRepaired) {\n // NOT even a rewrite of the identical bytes: a new mtime wakes every watcher pointed at the folder,\n // and a write fails outright on a read-only checkout we had no reason to touch.\n process.stderr.write(`flatc: --fix: nothing to repair mechanically${hasErrors ? ' — the remaining errors need a decision, not a separator\\n' : '\\n'}`)\n } else {\n writeFileSync(programPath, text) // ONE write, of a text this loop has already re-checked\n process.stderr.write(`flatc: --fix: ${total} repair(s) applied to ${basename(programPath)} · errors ${first} → ${before}\\n`)\n return compileOnce(programPath, explicitFlats, out, checkOnly, assetMode, noLibs, false)\n }\n }\n if (checkOnly) {\n if (report) process.stderr.write(report + '\\n')\n if (hasErrors) return 1\n // Success line deliberately avoids the word \"error\" (the report on a FAILURE already prints \"error\" to\n // stderr + exits ≠0) so a `grep error` over the output can't false-positive — the real signal is the exit\n // code; on success, only warnings remain (non-blocking), surfaced as a count.\n const warnings = diagnostics.length\n process.stdout.write(`flatc: check passed ✓${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n }\n if (report) process.stderr.write(report + '\\n') // compile anyway: diagnostics as warnings\n writeFileSync(outPath, packToJSON(doc))\n const outDir = dirname(outPath)\n for (const c of built.mediaCopies) {\n const dest = join(outDir, ...c.key.split('/'))\n if (!isWithin(outDir, dest)) continue // defensive: never write outside the output folder\n mkdirSync(dirname(dest), { recursive: true })\n copyFileSync(c.src, dest)\n }\n const where = assetMode === 'external' ? ` (external → ${assetsDir}/)` : ''\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.symbols.length} symbol(s) · ${built.flatLibs} lib(s) · ${built.packages} package(s) · ${built.media} media${where}\\n`)\n return 0\n}\n\n/** PROGRAM-only constructs found in what is supposed to be a `.flat` symbol library. `parseFlatLib` reads a\n * `.flat` as a bag of symbols and silently ignores everything else, so a whole program saved under the wrong\n * extension parsed to \"0 symbol(s)\" and passed `--check` with nothing verified — the worst possible answer\n * for tooling, since a check written against the wrong extension ALWAYS passes and looks like a safety net.\n * Both constructs are anchored at line start: only a real block matches, not a mention inside a string. */\nfunction programConstructsIn(src: string): string[] {\n const found: string[] = []\n if (/^[ \\t]*scene\\s*\\{/m.test(src)) found.push('a `scene { … }` block')\n if (/^[ \\t]*object\\s+\"/m.test(src)) found.push('`object \"…\" { … }` blocks')\n return found\n}\n\n/**\n * `--check` for a `.flat` asset LIBRARY (one or several): parse via `parseFlatLib` (NOT the program parser,\n * which would choke on `symbol`/`params`/`layer`), merge the symbols into an empty-scene Doc, and run the SAME\n * Doc lint as a program -> per-symbol diagnostics (`[<Symbol>] line:col: …`). Reuses `lintDocReport`/\n * `docHasErrors`, so every existing check (params-in-expr, undeclared color param in a paint, unknown\n * functions/objects…) applies for free, with the same `[scope] line:col: level: msg` format and exit code.\n * No `behaviorDiagnostics` (those are scene-level — a pure lib has no scene).\n */\nfunction checkFlatLibs(flatPaths: string[]): number {\n const symbols: SymbolDef[] = []\n for (const p of flatPaths) {\n let src: string\n try { src = readFileSync(p, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n const disguised = programConstructsIn(src)\n if (disguised.length) { // a PROGRAM saved as `.flat` — see programConstructsIn\n process.stderr.write(`flatc: ${basename(p)}: this is a PROGRAM, not a symbol library — it contains ${disguised.join(' and ')}, which a .flat never carries and which --check therefore does NOT verify. Rename it to .flatink.\\n`)\n return 1\n }\n try { symbols.push(...parseFlatLib(src).symbols) } // ids are uid-unique across calls -> safe to merge libs\n catch (e) { process.stderr.write(`flatc: ${basename(p)}: ${(e as Error).message}\\n`); return 1 } // a malformed lib -> a clean parse error (not \"[scene] …\")\n }\n const doc: Doc = { width: 1, height: 1, timeline: { fps: 24, durationFrames: 1, tracks: [] }, variables: {}, layers: [], symbols }\n const report = lintDocReport(doc) // scene scope is empty -> only per-symbol diagnostics\n if (report) process.stderr.write(report + '\\n')\n if (docHasErrors(doc)) return 1\n // \"check passed\", NOT \"no errors\" — the word \"error\" must not appear on success (a `grep error` trap); the\n // exit code is the signal, and any remaining lines are warnings (non-blocking), reported as a count.\n const warnings = report ? report.split('\\n').filter(Boolean).length : 0\n process.stdout.write(`flatc: check passed ✓ ${symbols.length} symbol(s)${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n}\n\n/** Loads a Doc from a `.flatink` (compiled) or a `.flatpack`/`.flatpack.json` (already-baked JSON). */\nfunction loadDoc(filePath: string, noLibs = false, explicitFlats: string[] = []): Doc {\n // The `.flat` libraries named on the command line are part of the program, exactly as for a compile:\n // dropping them here left every instance of theirs unresolved — an empty scene, with `--check` green.\n if (filePath.endsWith('.flatink')) return buildDocFromProgram(filePath, explicitFlats, 'inline', '', noLibs).doc\n // .flatpack (canonical) or .flatpack.json (alias): untrusted JSON → normalize before use.\n return sanitizeDoc(JSON.parse(readFileSync(filePath, 'utf8')))\n}\n\n/** --play: runs the file headless, replays --script, prints { sends, vars }. */\nfunction playOnce(filePath: string, scriptPath: string, trace: boolean, noLibs = false, explicitFlats: string[] = [], seed?: number, settle?: number): number {\n if (!scriptPath) { process.stderr.write('flatc: --play requires --script <gestures.json>\\n'); return 1 }\n if (!existsSync(scriptPath)) { process.stderr.write(`flatc: script not found: ${scriptPath}\\n`); return 1 }\n let doc: Doc, gestures: Gesture[]\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n try { gestures = JSON.parse(readFileSync(scriptPath, 'utf8')) as Gesture[] } catch (e) { process.stderr.write(`flatc: invalid JSON script: ${(e as Error).message}\\n`); return 1 }\n if (!Array.isArray(gestures)) { process.stderr.write('flatc: the script must be an array of gestures\\n'); return 1 }\n const res = playHeadless(doc, gestures, { trace, seed, settle })\n // On stderr, and no effect on the exit code: the JSON on stdout stays what a caller parses.\n for (const w of res.warnings ?? []) process.stderr.write(`flatc: warning: ${w}\\n`)\n if (!trace) process.stdout.write(JSON.stringify(res, null, 2) + '\\n')\n else // --trace: readable log (one gesture per line) → inspection / debug-player.\n for (const s of res.steps ?? []) {\n // `name` · `name=value` · `name{a=1, b=2}` (record payload) — the three shapes the host receives.\n const send = (e: { name: string; value?: number | string; fields?: Record<string, number> }): string =>\n e.fields ? `${e.name}{${Object.entries(e.fields).map(([k, v]) => `${k}=${v}`).join(', ')}}`\n : e.value !== undefined ? `${e.name}=${e.value}` : e.name\n const sends = s.sends.length ? ' sends:[' + s.sends.map(send).join(', ') + ']' : ''\n const vars = Object.entries(s.changed).map(([k, [a, b]]) => `${k}:${JSON.stringify(a)}→${JSON.stringify(b)}`)\n process.stdout.write(`${s.gesture.padEnd(28)}${sends}${vars.length ? ' vars{' + vars.join(' ') + '}' : ''}\\n`)\n }\n // `expect`: any detected mismatch → stderr log + exit code ≠0 (CI self-verification).\n if (res.expectFailures?.length) {\n for (const f of res.expectFailures) process.stderr.write(`flatc: ✗ ${f}\\n`)\n return 1\n }\n return 0\n}\n\n/** Parses `--at`: \"k=v[,k2=v2]\" → variable table (state override for rendering). */\nfunction parseVars(spec: string, into: Record<string, number>): void {\n for (const pair of spec.split(',')) {\n const i = pair.indexOf('=')\n if (i < 0) continue\n const k = pair.slice(0, i).trim(); const v = Number(pair.slice(i + 1).trim())\n if (k && Number.isFinite(v)) into[k] = v\n }\n}\n\n/** `--scale auto`: pick a resolution factor from the canvas size — enlarge small/thin content (so fine\n * filaments stay legible) while leaving large content at 1×. `max(160/long, 48/short)`, clamped [1, 8]. */\nfunction autoScale(w: number, h: number): number {\n const maxSide = Math.max(1, w, h), minSide = Math.max(1, Math.min(w, h))\n return Math.max(1, Math.min(8, Math.round(Math.max(160 / maxSide, 48 / minSide))))\n}\n\n/** Renders a Doc to a PNG file (headless skia). Async (SVG decode + raster). Shared by --render and --preview. */\nasync function renderDocToFile(doc: Doc, outPath: string, frame: number, vars: Record<string, number>, scale: number, steps: number, params: Record<string, string> = {}): Promise<number> {\n try {\n const { renderDocToPng } = await import('./render')\n const png = await renderDocToPng(doc, { frame, vars: Object.keys(vars).length ? vars : undefined, scale, steps: steps || undefined, params: Object.keys(params).length ? params : undefined })\n writeFileSync(outPath, png)\n } catch (e) {\n process.stderr.write(`flatc: render failed: ${(e as Error).message}\\n`)\n if (process.env.FLATC_DEBUG && (e as Error).stack) process.stderr.write(`${(e as Error).stack}\\n`) // FLATC_DEBUG=1 → full stack for diagnosis\n return 1\n }\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.width}×${doc.height} ×${scale}${frame ? ` · frame ${frame}` : ''}${steps ? ` · ${steps} step(s)` : ''}${Object.keys(vars).length ? ` · ${Object.entries(vars).map(([k, v]) => `${k}=${v}`).join(' ')}` : ''}\\n`)\n return 0\n}\n\n/** --render: renders the file to PNG (headless skia). Async (SVG decode + raster). */\nasync function renderOnce(filePath: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, scaleAuto: boolean, noLibs = false, params: Record<string, string> = {}, explicitFlats: string[] = [], scriptPath = '', settle?: number): Promise<number> {\n let doc: Doc\n // `--no-libs` was parsed and then not passed on, so `--render` still auto-discovered the neighbouring\n // .flat files -- and failed on one, advising the very flag that had been given. Same hole in `--play`.\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n // A frame past the end WRAPS silently, so a render can show frame 20 while the caller asked for 200\n // and reads the result as a renderer bug. It cost exactly that once.\n const dur = doc.timeline?.durationFrames ?? 0\n if (dur > 0 && frame >= dur) process.stderr.write(`flatc: --frame ${frame} is past the timeline (durationFrames ${dur}); the playhead WRAPS, so this renders frame ${frame % dur}\\n`)\n const outPath = out ? resolve(out) : join(dirname(filePath), basename(filePath, extname(filePath)) + '.png')\n const sc = scaleAuto ? autoScale(doc.width, doc.height) : scale\n if (!scriptPath) return renderDocToFile(doc, outPath, frame, vars, sc, steps, params)\n return renderAfterScript(doc, outPath, scriptPath, frame, vars, sc, steps, params, settle)\n}\n\n/**\n * `--render --script`: replays a gesture script (as `--play` does) and writes the image of the scene it\n * leaves. A `{\"type\":\"shot\",\"name\":\"…\"}` gesture writes the image AT THAT POINT, next to the output\n * (`out.<name>.png`, or `out.<n>.png` unnamed). The output itself is always the final state. A failed\n * `expect` fails the run, images written all the same: they are what one wants to look at then.\n */\nasync function renderAfterScript(doc: Doc, outPath: string, scriptPath: string, frame: number, vars: Record<string, number>, scale: number, steps: number, params: Record<string, string>, settle?: number): Promise<number> {\n if (!existsSync(scriptPath)) { process.stderr.write(`flatc: script not found: ${scriptPath}\\n`); return 1 }\n let gestures: Gesture[]\n try { gestures = JSON.parse(readFileSync(scriptPath, 'utf8')) as Gesture[] } catch (e) { process.stderr.write(`flatc: invalid JSON script: ${(e as Error).message}\\n`); return 1 }\n if (!Array.isArray(gestures)) { process.stderr.write('flatc: the script must be an array of gestures\\n'); return 1 }\n const shotPath = (name: string) => join(dirname(outPath), `${basename(outPath, extname(outPath))}.${name.replace(/[^\\w.-]/g, '_')}${extname(outPath) || '.png'}`)\n const written: string[] = []\n let failures: string[] = [], warned: string[] = []\n try {\n const { createRenderer } = await import('./render')\n const r = await createRenderer(doc, { scale, params: Object.keys(params).length ? params : undefined, interactive: true, settle })\n try {\n // The starting point is the one `--render` would draw: the frame, then the `--at` values.\n await r.frame(frame, { vars: Object.keys(vars).length ? vars : undefined })\n let shots = 0\n for (const g of gestures) {\n if (g.type === 'shot') { const p = shotPath(g.name || String(++shots)); writeFileSync(p, await r.capture()); written.push(basename(p)); continue }\n const rep = r.play([g])\n failures = rep.expectFailures; warned = rep.warnings\n }\n if (steps > 0) r.play([{ type: 'wait', frames: Math.floor(steps) }])\n writeFileSync(outPath, await r.capture())\n } finally { r.close() }\n } catch (e) {\n process.stderr.write(`flatc: render failed: ${(e as Error).message}\\n`)\n return 1\n }\n const played = gestures.filter((g) => g.type !== 'shot').length\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.width}×${doc.height} ×${scale} · after ${played} gesture(s)${steps ? ` · ${steps} step(s)` : ''}${written.length ? ` · shots: ${written.join(', ')}` : ''}\\n`)\n for (const w of warned) process.stderr.write(`flatc: warning: ${w}\\n`)\n for (const f of failures) process.stderr.write(`flatc: ✗ ${f}\\n`)\n return failures.length ? 1 : 0\n}\n\nconst gcd = (a: number, b: number): number => { a = Math.abs(a); b = Math.abs(b); while (b) { const t = a % b; a = b; b = t } return a || 1 }\nconst lcm = (a: number, b: number): number => Math.max(1, Math.round((a / gcd(a, b)) * b))\n\n/** Every instance in a symbol's subtree (descends into local groups; does NOT cross into other symbols). */\nfunction* instancesOf(sym: SymbolDef): Generator<Instance> {\n const stack: Item[] = []\n for (const l of sym.layers) stack.push(...l.items)\n for (let it = stack.pop(); it; it = stack.pop()) {\n if (isInstance(it)) yield it\n else if (isGroup(it)) for (const l of it.layers) stack.push(...l.items)\n }\n}\n\n/**\n * Preview window length (frames). A `synced` instance loops on the PARENT's frame, so the symbol's own\n * duration suffices — that's the historical behavior, kept for every symbol with no MovieClip descendant.\n * But an `independent` (`loop`) descendant runs on its OWN duration, immune to the parent's wrap: to SHOW it\n * loop CLEANLY the window must be a common multiple of every such clip's duration (and the root's) — its\n * \"loop seam\". `once` clips only need the window to REACH their last frame (they hold afterwards). We walk\n * the subtree, fold each independent clip's duration into the seam (LCM), and extend past the longest clip,\n * keeping the window a whole number of seams. Capped, so coprime durations can't blow the window up.\n */\nfunction previewDuration(root: SymbolDef, symbols: SymbolDef[]): number {\n const byId = new Map(symbols.map((s) => [s.id, s]))\n const seen = new Set<string>()\n let seam = Math.max(1, root.timeline?.durationFrames ?? 1) // clean-loop period (multiple of every loop clip)\n let longest = seam // window must reach the last frame of any clip\n const walk = (sym: SymbolDef | undefined): void => {\n if (!sym || seen.has(sym.id)) return\n seen.add(sym.id)\n for (const it of instancesOf(sym)) {\n const child = byId.get(it.symbolId)\n const cdur = Math.max(1, child?.timeline?.durationFrames ?? 1)\n const mode = it.playback?.mode\n if (mode === 'independent') { seam = lcm(seam, cdur); longest = Math.max(longest, cdur) }\n else if (mode === 'once') longest = Math.max(longest, cdur)\n walk(child)\n }\n }\n walk(root)\n let dur = seam\n while (dur < longest) dur += seam // keep a clean loop seam while covering the longest MovieClip\n return Math.min(dur, 6000) // hard cap (~4 min @24fps) against a pathological coprime-duration blow-up\n}\n\n/**\n * Wraps ONE symbol from a `.flat` library into a minimal, playable Doc: a single centered instance on a\n * canvas auto-sized to the symbol's frame-0 bounds (+ padding). Lets you preview a library asset without\n * hand-authoring a wrapper `.flatink`. The root timeline borrows the symbol's own fps and a duration long\n * enough to show every nested MovieClip (`loop`/`once`) loop cleanly (`previewDuration`). Throws if the lib\n * is empty or the named symbol is missing.\n */\nfunction buildPreviewDoc(flatPath: string, symbolName: string, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string> = {}): { doc: Doc; symbol: SymbolDef; others: string[] } {\n const { symbols } = parseFlatLib(readFileSync(flatPath, 'utf8'))\n if (!symbols.length) throw new Error('no symbols found in this .flat library')\n let symbol = symbols[0]\n if (symbolName) {\n const found = symbols.find((s) => s.name === symbolName)\n if (!found) throw new Error(`symbol \"${symbolName}\" not found — available: ${symbols.map((s) => s.name).join(', ')}`)\n symbol = found\n }\n\n const instance: Instance = { id: 'preview_instance', kind: 'instance', name: symbol.name, transform: { ...IDENTITY }, symbolId: symbol.id }\n const layer: Layer = { id: 'preview_layer', name: 'preview', visible: true, locked: false, opacity: 1, items: [instance] }\n const tl = symbol.timeline\n const dur = previewDuration(symbol, symbols)\n const timeline = { fps: tl?.fps ?? 24, durationFrames: dur, tracks: [] }\n // ALL symbols stay in the library (the chosen one may instance the others) — only `instance` is on stage.\n let doc: Doc = { width: 1, height: 1, layers: [layer], symbols, timeline }\n\n // `--set param=value`: set the preview instance's exposed params (call-site values, raw literals). The\n // renderer resolves them per the symbol's ParamDef / state machines (color → fill, number/bool → scope,\n // state name → driven frame). A light validation warns on names the symbol does not expose.\n const entries = Object.entries(setSpec)\n if (entries.length) {\n const known = new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)])\n for (const [k, v] of entries) {\n if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: \"${symbol.name}\" exposes no such param (${[...known].join(', ') || 'none'})\\n`)\n else (instance.params ??= {})[k] = v\n }\n }\n // Auto-size & center. Default `--bbox all`: UNION over every frame of the symbol (sub-timelines NOT\n // frozen) so motion that drifts/rotates/grows is never clipped. `--bbox frame0` keeps the old frame-0\n // measure (+`--pad` to absorb motion).\n // Sample at most ~240 evenly-spaced frames (+ the last) so a long/abusive `durationFrames` can't blow\n // up memory/time; a sparse union is virtually as tight as every-frame for real motion.\n const sampleFrames = (n: number): number[] => {\n const step = Math.max(1, Math.ceil(n / 240))\n const fs: number[] = []\n for (let f = 0; f < n; f += step) fs.push(f)\n if (fs[fs.length - 1] !== n - 1) fs.push(n - 1)\n return fs\n }\n // Measured AS DRAWN (`scoped`): the symbol's `expr` channels evaluated with its params — the declared\n // defaults and the `--set` values, which is why they are applied first. A bar stretched by\n // `expr scaleX \"long\"` used to be framed on its base shape, and came out cropped.\n const bb = bboxMode === 'all' ? containerBBoxUnion(doc, instance, sampleFrames(dur), instance.transform, true) : containerBBox(doc, instance, 0, instance.transform, false, true)\n if (bb) {\n doc = { ...doc, width: Math.max(1, Math.ceil(bb.maxX - bb.minX) + pad * 2), height: Math.max(1, Math.ceil(bb.maxY - bb.minY) + pad * 2) }\n instance.transform = { ...IDENTITY, e: pad - bb.minX, f: pad - bb.minY }\n } else {\n doc = { ...doc, width: 512, height: 512 } // empty/degenerate symbol: fall back to a fixed centered stage\n instance.transform = { ...IDENTITY, e: 256, f: 256 }\n }\n\n return { doc, symbol, others: symbols.filter((s) => s !== symbol).map((s) => s.name) }\n}\n\n/** --preview: wraps one symbol of a `.flat` into a playable Doc, then writes a .flatpack (default) or a PNG (with --render). */\nasync function previewOnce(flatPath: string, symbolName: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, doRender: boolean, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string>, scaleAuto: boolean): Promise<number> {\n let built: { doc: Doc; symbol: SymbolDef; others: string[] }\n try { built = buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec) }\n catch (e) { process.stderr.write(`flatc: ${(e as Error).message}\\n`); return 1 }\n const { doc, symbol, others } = built\n // A symbol with `states` takes its pose from the STATE, not from the playhead: `--frame` then only\n // advances the loops nested in it, and two frames come out identical without a word.\n if (frame > 0 && symbol.states?.length) {\n const sm = symbol.states[0]\n process.stderr.write(`flatc: --frame ${frame} does not move \"${symbol.name}\": its pose is set by its states (\\`${sm.param}\\`), not by the playhead. To see a state, or a point between two: --set ${sm.param}=<${sm.states.map((x) => x.name).join(' | ')} | a value, e.g. ${sm.param}=0.5>\\n`)\n }\n const stem = `${basename(flatPath, extname(flatPath))}.${symbol.name}`\n let code: number\n if (doRender) {\n // auto scale from the CONTENT size (minus the padding), so thin filaments still trigger an upscale.\n const sc = scaleAuto ? autoScale(doc.width - 2 * pad, doc.height - 2 * pad) : scale\n code = await renderDocToFile(doc, out ? resolve(out) : join(dirname(flatPath), stem + '.png'), frame, vars, sc, steps)\n } else {\n const outPath = out ? resolve(out) : join(dirname(flatPath), stem + '.flatpack')\n writeFileSync(outPath, packToJSON(doc))\n process.stdout.write(`flatc: ${basename(outPath)} ✓ preview of \"${symbol.name}\" · ${doc.width}×${doc.height} · play it in the browser player\\n`)\n code = 0\n }\n if (!symbolName && others.length) process.stderr.write(`flatc: previewed \"${symbol.name}\"; other symbols in this lib: ${others.join(', ')} (pick with --symbol <name>)\\n`)\n return code\n}\n\n/**\n * `--since <version>`: what these sources draw DIFFERENTLY from that version, for an author deciding what\n * to do about it once, when upgrading. Read from the SOURCE, so every finding has its line.\n *\n * One change is recorded: until 0.36 a path made only of lines was rounded at its gentle turns; it is now\n * drawn straight. Each such path is listed with how far its outline moves — a soft hill written as four\n * points wants `smooth` back, a hexagon does not. This is a report, not a warning of `--check`: a polyline\n * that is MEANT straight has nothing to add to say so, and would be warned about for ever.\n */\nconst SINCE_OPEN_UNITS = 2 // an open line moving by this much is reported whatever its length\n\nfunction sinceReport(files: string[], since: string, all = false): number {\n const [maj, min] = since.split('.').map(Number)\n if (!Number.isFinite(maj) || !Number.isFinite(min)) { process.stderr.write(`flatc: --since takes a version, e.g. --since 0.35 (got \"${since}\")\\n`); return 1 }\n if (maj > 0 || min >= 36) { process.stdout.write(`flatc: nothing is drawn differently since ${since}\\n`); return 0 }\n let open = 0, closed = 0, slight = 0\n for (const file of files) {\n let src: string\n try { src = readFileSync(file, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read ${file}: ${(e as Error).message}\\n`); return 1 }\n // Every `path \"…\"` of the file that does not already say `smooth` (a text laid `along path \"…\"` is not a shape).\n for (const m of src.matchAll(/(?<!along\\s)\\bpath\\s+\"([^\"]*)\"(?!\\s+smooth\\b)/g)) {\n // Read as it was BEFORE (free-hand material) to measure what smoothing did to it.\n for (const sub of parsePathData(m[1], { smooth: true }).subpaths) {\n if (sub.segments.some((sg) => sg.inHandle || sg.outHandle) || !softVertexCount(sub)) continue // has a curve, or only sharp corners\n const moved = smoothingDeviation(sub)\n const xs = sub.segments.map((sg) => sg.anchor.x), ys = sub.segments.map((sg) => sg.anchor.y)\n const size = Math.hypot(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys))\n // Listed when the outline moves by 1% of the shape's size or more — RELATIVE, because artwork is often\n // drawn in a large space and scaled down by its container. An OPEN line is also listed from 2 units:\n // a long stroke with gentle turns (a hill, a horizon) moves by little of its length and visibly all\n // the same. Below that the two renderings are a few pixels apart at most; `--all` lists those too.\n if (moved <= 0 || size <= 0) continue\n if (moved < size * 0.01 && (sub.closed || moved < SINCE_OPEN_UNITS) && !all) { slight++; continue }\n const line = src.slice(0, m.index).split('\\n').length\n if (sub.closed) closed++; else open++\n process.stdout.write(`${file}:${line}: ${sub.closed ? 'closed' : 'open'} path of ${sub.segments.length} points was rounded before 0.36 and is now straight — it moves by up to ${Math.round(moved * 10) / 10} units (${Math.round((1000 * moved) / (size || 1)) / 10}% of its size). Write \\`smooth\\` after its path data to keep the curve.\\n`)\n }\n }\n }\n const n = open + closed\n const rest = slight ? ` ${slight} more move by less than 1% of their size, and by less than ${SINCE_OPEN_UNITS} units if open (\\`--all\\` lists them).` : ''\n process.stdout.write(n\n ? `flatc: ${n} path(s) of lines drawn differently since ${since}: ${open} open, ${closed} closed.${rest} Nothing was modified. (Rounded rectangles and paths mixing lines with curves changed too — their straight sides no longer bulge — and are not listed.)\\n`\n : `flatc: no path of lines is drawn visibly differently since ${since} in ${files.length} file(s).${rest}\\n`)\n return 0\n}\n\nexport function run(argv: string[]): number | Promise<number> {\n const args = argv.slice(2)\n let out = '', scriptPath = '', symbolName = ''\n let checkOnly = false, doFix = false, doWatch = false, doPlay = false, doRender = false, doTrace = false, doPreview = false, noLibs = false\n let frame = 0, scale = 2, steps = 0, pad = 24\n let scaleAuto = false\n let seed: number | undefined\n let settle: number | undefined\n let since = ''\n let bboxMode: 'all' | 'frame0' = 'all'\n let assetMode: AssetMode = 'inline'\n const vars: Record<string, number> = {}\n const setSpec: Record<string, string> = {} // `--set param=value` (state name or number) for --preview\n const positional: string[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a === '-o' || a === '--out') out = args[++i] ?? ''\n else if (a === '--script') scriptPath = resolve(args[++i] ?? '')\n else if (a === '--assets') assetMode = args[++i] === 'external' ? 'external' : 'inline'\n else if (a === '--check') checkOnly = true\n else if (a === '--fix') { doFix = true; checkOnly = true }\n else if (a === '--no-libs') noLibs = true\n else if (a === '--watch') doWatch = true\n else if (a === '--play') doPlay = true\n else if (a === '--trace') doTrace = true\n else if (a === '--render') doRender = true\n else if (a === '--preview') doPreview = true\n else if (a === '--symbol') symbolName = args[++i] ?? ''\n else if (a === '--pad') pad = Math.max(0, Number(args[++i] ?? '24') || 0)\n else if (a === '--bbox') bboxMode = args[++i] === 'frame0' ? 'frame0' : 'all'\n else if (a === '--frame') frame = Number(args[++i] ?? '0') || 0\n else if (a === '--since') since = args[++i] ?? ''\n else if (a === '--all') { /* read by --since */ }\n else if (a === '--seed') { const v = Number(args[++i]); if (Number.isFinite(v)) seed = v }\n else if (a === '--settle') { const v = Number(args[++i]); if (Number.isFinite(v) && v >= 0) settle = Math.floor(v) }\n else if (a === '--steps') steps = Math.max(0, Number(args[++i] ?? '0') || 0)\n else if (a === '--scale') { const v = args[++i] ?? '2'; if (v === 'auto') scaleAuto = true; else scale = Number(v) || 2 }\n else if (a === '--at') parseVars(args[++i] ?? '', vars)\n else if (a === '--set') for (const pair of (args[++i] ?? '').split(',')) { const j = pair.indexOf('='); if (j > 0) setSpec[pair.slice(0, j).trim()] = pair.slice(j + 1).trim() }\n else if (a === '-h' || a === '--help') { printHelp(); return 0 }\n else positional.push(a)\n }\n if (!positional.length) { printHelp(); return 1 }\n\n const filePath = resolve(positional[0])\n if (!existsSync(filePath)) { process.stderr.write(`flatc: not found: ${filePath}\\n`); return 1 }\n\n if (since) return sinceReport(positional.map((f) => resolve(f)), since, args.includes('--all'))\n const explicitFlats = positional.slice(1)\n if (doPreview) return previewOnce(filePath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto)\n if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats, scriptPath, settle)\n if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed, settle)\n // `--check <library>.flat`: a `.flat` first positional is an asset LIB, not a program → lint via parseFlatLib\n // (the following positionals are more `.flat` libs to merge). Every other path is unchanged.\n const action: () => number = checkOnly && filePath.endsWith('.flat')\n ? () => checkFlatLibs([filePath, ...explicitFlats])\n : () => compileOnce(filePath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix)\n if (doWatch) {\n const code = action()\n const baseDir = dirname(filePath)\n let timer: ReturnType<typeof setTimeout> | undefined\n // We ignore changes to the OUTPUT (.flatpack) — otherwise writing it would re-trigger the compile in a loop.\n watch(baseDir, { recursive: false }, (_e, filename) => {\n if (filename && (filename.endsWith('.flatpack') || filename.endsWith('.flatpack.json'))) return\n clearTimeout(timer); timer = setTimeout(action, 80)\n })\n process.stdout.write(`flatc: watching ${baseDir} … (Ctrl+C to stop)\\n`)\n return code\n }\n return action()\n}\n"],"mappings":"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","names":["f"]}
|
package/dist/cli/render.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SendEvent } from '@flatkit/player';
|
|
2
|
+
import { Gesture } from '@flatkit/player/debug';
|
|
1
3
|
import { Doc } from '@flatkit/types';
|
|
2
4
|
|
|
3
5
|
type RenderOpts = {
|
|
@@ -6,6 +8,14 @@ type RenderOpts = {
|
|
|
6
8
|
scale?: number;
|
|
7
9
|
steps?: number;
|
|
8
10
|
params?: Record<string, string>;
|
|
11
|
+
script?: Gesture[];
|
|
12
|
+
settle?: number;
|
|
13
|
+
};
|
|
14
|
+
/** What a replayed script reports: the `send`s it emitted, and the `expect` gestures that did not hold. */
|
|
15
|
+
type PlayReport = {
|
|
16
|
+
sends: SendEvent[];
|
|
17
|
+
expectFailures: string[];
|
|
18
|
+
warnings: string[];
|
|
9
19
|
};
|
|
10
20
|
/**
|
|
11
21
|
* A renderer held OPEN over a document: the expensive setup is paid once, then any number of frames.
|
|
@@ -18,11 +28,19 @@ type RenderOpts = {
|
|
|
18
28
|
* shims — including that `document` must exist or every `filter` and `tint` is dropped in SILENCE.
|
|
19
29
|
*/
|
|
20
30
|
type Renderer = {
|
|
21
|
-
/** PNG of one frame. `vars` overrides state for this frame; `
|
|
31
|
+
/** PNG of one frame. `vars` overrides state for this frame; `script` replays gestures after the seek
|
|
32
|
+
* (an `interactive` renderer only); `steps` then runs N sim steps before capture. */
|
|
22
33
|
frame(frame: number, opts?: {
|
|
23
34
|
vars?: Record<string, number>;
|
|
24
35
|
steps?: number;
|
|
36
|
+
script?: Gesture[];
|
|
25
37
|
}): Promise<Uint8Array>;
|
|
38
|
+
/** Replays gestures on the live scene, as `flatc --play` does, and leaves it in the state they reach.
|
|
39
|
+
* Successive calls continue the same session (one state, one `expect` window). Needs
|
|
40
|
+
* `createRenderer(doc, { interactive: true })`. */
|
|
41
|
+
play(gestures: Gesture[]): PlayReport;
|
|
42
|
+
/** PNG of the scene AS IT IS NOW — after a `play`, without seeking. */
|
|
43
|
+
capture(): Promise<Uint8Array>;
|
|
26
44
|
/** The document's pixel size at the renderer's scale. */
|
|
27
45
|
readonly width: number;
|
|
28
46
|
readonly height: number;
|
|
@@ -38,9 +56,11 @@ type Renderer = {
|
|
|
38
56
|
declare function createRenderer(doc: Doc, opts?: {
|
|
39
57
|
scale?: number;
|
|
40
58
|
params?: Record<string, string>;
|
|
59
|
+
interactive?: boolean;
|
|
60
|
+
settle?: number;
|
|
41
61
|
}): Promise<Renderer>;
|
|
42
62
|
/** Render `doc` to a PNG (Buffer). `frame` = target image; `vars` = state override; `scale` = x-px (default 2).
|
|
43
63
|
* A one-shot convenience over `createRenderer` — reach for the renderer itself as soon as you want two. */
|
|
44
64
|
declare function renderDocToPng(doc: Doc, opts?: RenderOpts): Promise<Uint8Array>;
|
|
45
65
|
|
|
46
|
-
export { type RenderOpts, type Renderer, createRenderer, renderDocToPng };
|
|
66
|
+
export { type PlayReport, type RenderOpts, type Renderer, createRenderer, renderDocToPng };
|
package/dist/cli/render.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/cli/render.ts
|
|
2
2
|
import { FlatPlayer } from "@flatkit/player";
|
|
3
|
+
import { createReplayer } from "@flatkit/player/debug";
|
|
3
4
|
import { isGroup, isInstance } from "@flatkit/engine/layers";
|
|
4
5
|
import { mkdtempSync, writeFileSync, rmSync } from "fs";
|
|
5
6
|
import { tmpdir } from "os";
|
|
@@ -81,17 +82,34 @@ async function createRenderer(doc, opts = {}) {
|
|
|
81
82
|
set("document", { createElement: (t) => t === "canvas" ? new Canvas(1, 1) : {} });
|
|
82
83
|
const pxW = Math.max(1, Math.round(W * scale));
|
|
83
84
|
const pxH = Math.max(1, Math.round(H * scale));
|
|
85
|
+
const handlers = {};
|
|
84
86
|
const canvas = new Canvas(pxW, pxH);
|
|
85
87
|
const el = canvas;
|
|
86
88
|
Object.assign(el, {
|
|
87
89
|
getBoundingClientRect: () => ({ width: W, height: H, left: 0, top: 0, right: W, bottom: H }),
|
|
88
|
-
|
|
90
|
+
// The listeners the player registers are KEPT: a replayed script fires into them (`play`).
|
|
91
|
+
addEventListener: (type, fn) => {
|
|
92
|
+
handlers[type] = fn;
|
|
89
93
|
},
|
|
90
|
-
removeEventListener: () => {
|
|
94
|
+
removeEventListener: (type) => {
|
|
95
|
+
delete handlers[type];
|
|
96
|
+
},
|
|
97
|
+
setPointerCapture: () => {
|
|
98
|
+
},
|
|
99
|
+
releasePointerCapture: () => {
|
|
91
100
|
},
|
|
92
101
|
style: {}
|
|
93
102
|
});
|
|
94
|
-
|
|
103
|
+
const sends = [];
|
|
104
|
+
let player = new FlatPlayer(el, withParams, { input: !!opts.interactive, audio: false, padding: 0, seed: 1, image: (id) => images.get(id) ?? null, onEvent: (e) => sends.push(e) });
|
|
105
|
+
let replayer = null;
|
|
106
|
+
const play = (gestures) => {
|
|
107
|
+
if (!player) throw new Error("renderer is closed");
|
|
108
|
+
if (!opts.interactive) throw new Error("this renderer cannot replay gestures: open it with createRenderer(doc, { interactive: true })");
|
|
109
|
+
replayer ??= createReplayer(player, withParams, handlers, sends, { settle: opts.settle });
|
|
110
|
+
for (const g2 of gestures) replayer.apply(g2);
|
|
111
|
+
return { sends: [...sends], expectFailures: [...replayer.expectFailures], warnings: [...replayer.warnings] };
|
|
112
|
+
};
|
|
95
113
|
return {
|
|
96
114
|
width: pxW,
|
|
97
115
|
height: pxH,
|
|
@@ -99,6 +117,7 @@ async function createRenderer(doc, opts = {}) {
|
|
|
99
117
|
if (!player) throw new Error("renderer is closed");
|
|
100
118
|
if (frameOpts.vars) for (const [k, v] of Object.entries(frameOpts.vars)) player.setVar(k, v);
|
|
101
119
|
player.seek(frame);
|
|
120
|
+
if (frameOpts.script?.length) play(frameOpts.script);
|
|
102
121
|
const steps = frameOpts.steps ?? 0;
|
|
103
122
|
if (steps > 0) {
|
|
104
123
|
const n = Math.min(Math.floor(steps), MAX_RENDER_STEPS);
|
|
@@ -106,6 +125,13 @@ async function createRenderer(doc, opts = {}) {
|
|
|
106
125
|
`);
|
|
107
126
|
player.stepSim(n);
|
|
108
127
|
}
|
|
128
|
+
player.render();
|
|
129
|
+
return el.toBuffer("png");
|
|
130
|
+
},
|
|
131
|
+
play,
|
|
132
|
+
async capture() {
|
|
133
|
+
if (!player) throw new Error("renderer is closed");
|
|
134
|
+
player.render();
|
|
109
135
|
return el.toBuffer("png");
|
|
110
136
|
},
|
|
111
137
|
close() {
|
|
@@ -155,9 +181,9 @@ function applyParams(doc, params) {
|
|
|
155
181
|
return out;
|
|
156
182
|
}
|
|
157
183
|
async function renderDocToPng(doc, opts = {}) {
|
|
158
|
-
const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params });
|
|
184
|
+
const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params, interactive: !!opts.script?.length, settle: opts.settle });
|
|
159
185
|
try {
|
|
160
|
-
return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps });
|
|
186
|
+
return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps, script: opts.script });
|
|
161
187
|
} finally {
|
|
162
188
|
renderer.close();
|
|
163
189
|
}
|
package/dist/cli/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/render.ts"],"sourcesContent":["// -----------------------------------------------------------------------------\n// render.ts -- HEADLESS PNG rendering of a Doc (skia-canvas backend), for `flatc --render`.\n//\n// Gives agents/CI an IMAGE of what they author (the \"blind positioning\" safeguard). We replay the\n// real `FlatPlayer` on a skia canvas: same paths, gradients, filters (glow/shadow via `ctx.filter`)\n// and SVG as the browser. skia-canvas is loaded through a DYNAMIC import with a NON-LITERAL specifier,\n// so it is never part of the public build graph -- install it on demand only when `--render` is used.\n// -----------------------------------------------------------------------------\nimport { FlatPlayer } from '@flatkit/player'\nimport type { Doc, Item } from '@flatkit/types'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\nimport { mkdtempSync, writeFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nexport type RenderOpts = { frame?: number; vars?: Record<string, number>; scale?: number; steps?: number; params?: Record<string, string> }\n\n/** Cap on `--steps` (anti-DoS: an untrusted doc must not freeze the render host). One step = 1/60 s of sim. */\nconst MAX_RENDER_STEPS = 10_000\n\n/** Minimal structural view of the `skia-canvas` surface we use. Kept local so the public build needs\n * no native binary; the real module is resolved at runtime only when `--render` runs. */\ninterface SkiaCanvas {\n Canvas: new (w: number, h: number) => HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n loadImage: (src: string) => Promise<unknown>\n Path2D: typeof Path2D\n /** Browser global under Node: the mask/clip path builder does `new DOMMatrix([...])` (drawScene). */\n DOMMatrix: typeof DOMMatrix\n /** Global font registry. `use(paths)` reads each file's intrinsic name-table family; the 2-arg\n * `use(family, paths)` form FORCES that alias (fixes variable-font statics whose name table lies). */\n FontLibrary: {\n use(fontPaths: readonly string[]): Array<{ family: string }>\n use(family: string, fontPaths: readonly string[]): Array<{ family: string }>\n }\n}\n\n/** Map a font asset's MIME (or its data-URI) to a file extension skia-canvas can parse. */\nconst FONT_EXT: Record<string, string> = {\n 'font/woff2': '.woff2', 'font/woff': '.woff', 'font/ttf': '.ttf', 'font/otf': '.otf',\n 'font/collection': '.ttc', 'application/font-woff': '.woff', 'application/x-font-ttf': '.ttf',\n}\n\n/** Registers every embedded `font` asset with skia's FontLibrary so headless text uses the authored\n * faces (not host fallbacks). Fonts are baked as base64 data-URIs by flatc; FontLibrary only reads\n * FILE paths, so we materialize each into a temp dir, register it, and return that dir for cleanup. */\nfunction registerFonts(doc: Doc, FontLibrary: SkiaCanvas['FontLibrary']): { dir: string | null; families: string[] } {\n const fonts = (doc.assets ?? []).filter((a) => a.kind === 'font' && /^data:/.test(a.data ?? ''))\n if (fonts.length === 0) return { dir: null, families: [] }\n const dir = mkdtempSync(join(tmpdir(), 'flatc-fonts-'))\n const families: string[] = []\n for (const a of fonts) {\n const b64 = a.data.slice(a.data.indexOf(',') + 1)\n const ext = FONT_EXT[a.mime] ?? `.${(a.mime.split('/')[1] || 'ttf').replace(/[^\\w]/g, '')}`\n const file = join(dir, a.id.replace(/[^\\w.-]/g, '_') + ext)\n try {\n writeFileSync(file, Buffer.from(b64, 'base64'))\n // `family` alias (from `asset … font \"Name\"`) forces the registered name, so the face matches the\n // text's `font \"Name\"` even when the file's own name table is wrong (variable-font static export).\n const used = a.family ? FontLibrary.use(a.family, [file]) : FontLibrary.use([file])\n for (const f of used) if (!families.includes(f.family)) families.push(f.family)\n } catch (e) {\n process.stderr.write(`flatc: skipped font asset \"${a.id}\" (${a.mime}): ${(e as Error).message}\\n`)\n }\n }\n return { dir, families }\n}\n\n/**\n * A renderer held OPEN over a document: the expensive setup is paid once, then any number of frames.\n *\n * Every consumer that needed more than one image — a GIF, an MP4, a contact sheet, a loop check —\n * reimplemented this on top of the player, because the only thing on offer rendered a single frame and\n * paid the whole cost each time: the dynamic `skia-canvas` import, writing the embedded fonts to a temp\n * dir and registering them, decoding every image asset, building a `FlatPlayer`, installing ten globals.\n * Four such harnesses exist across two neighbouring repos, ~430 lines, each rediscovering the same\n * shims — including that `document` must exist or every `filter` and `tint` is dropped in SILENCE.\n */\nexport type Renderer = {\n /** PNG of one frame. `vars` overrides state for this frame; `steps` runs N sim steps before capture. */\n frame(frame: number, opts?: { vars?: Record<string, number>; steps?: number }): Promise<Uint8Array>\n /** The document's pixel size at the renderer's scale. */\n readonly width: number\n readonly height: number\n /** Restore the globals and drop the temp font files. Always call it — it is not optional. */\n close(): void\n}\n\n/**\n * Open a renderer over `doc`. The globals it installs (`document`, `Path2D`, `DOMMatrix`, …) stay in\n * place until `close()`, which is what makes a frame loop cheap; it also means one renderer at a time\n * per process. `params` sets a SYMBOL's exposed params (a state name or a number) before the first\n * frame — the reason a consumer had to write its own harness to preview anything with a state.\n */\nexport async function createRenderer(doc: Doc, opts: { scale?: number; params?: Record<string, string> } = {}): Promise<Renderer> {\n // Non-literal specifier: tsc does not resolve it, so skia-canvas is not a build dependency.\n const skiaPkg: string = 'skia-canvas'\n let skia: SkiaCanvas\n try { skia = (await import(skiaPkg)) as unknown as SkiaCanvas }\n catch {\n throw new Error(\n 'skia-canvas is required for rendering. Install it as a dev dependency: `npm i -D skia-canvas` ' +\n '(pnpm users: also allow its build script with `pnpm approve-builds` or add \"skia-canvas\" to ' +\n 'pnpm.onlyBuiltDependencies, then reinstall). If the native binary is still missing afterwards, ' +\n 'run `node node_modules/skia-canvas/lib/prebuild.mjs download`.',\n )\n }\n const { Canvas, loadImage, Path2D, FontLibrary, DOMMatrix } = skia\n const scale = opts.scale && opts.scale > 0 ? opts.scale : 2\n const W = doc.width, H = doc.height\n\n const withParams = opts.params ? applyParams(doc, opts.params) : doc\n\n // Register embedded fonts up front so the player's `ctx.font` resolves to the authored faces.\n const { dir: fontDir, families } = registerFonts(withParams, FontLibrary)\n if (families.length) process.stderr.write(`flatc: registered ${families.length} font famil${families.length > 1 ? 'ies' : 'y'}: ${families.join(', ')}\\n`)\n\n // Pre-decode the image assets (skia reads SVG/PNG/... from the data-URIs embedded by flatc).\n const images = new Map<string, CanvasImageSource>()\n for (const a of withParams.assets ?? []) {\n if (a.kind === 'svg' || a.kind === 'image' || /^data:image\\//.test(a.data ?? '')) {\n try { images.set(a.id, (await loadImage(a.data)) as unknown as CanvasImageSource) } catch { /* unreadable asset: ignored (placeholder) */ }\n }\n }\n\n // Globals the player/rendering expects under Node (restored by close()).\n const g = globalThis as Record<string, unknown>\n const saved: Record<string, unknown> = {}\n const set = (k: string, v: unknown) => { saved[k] = g[k]; g[k] = v }\n set('Path2D', Path2D)\n set('DOMMatrix', DOMMatrix) // mask/clip layers build their clip path with `new DOMMatrix([...])` (no browser global under Node)\n set('window', { addEventListener() {}, removeEventListener() {}, devicePixelRatio: scale })\n set('addEventListener', () => {})\n set('removeEventListener', () => {})\n set('requestAnimationFrame', () => 0)\n set('cancelAnimationFrame', () => {})\n set('document', { createElement: (t: string) => (t === 'canvas' ? new Canvas(1, 1) : {}) }) // off-screen canvas (filters/tint)\n\n const pxW = Math.max(1, Math.round(W * scale))\n const pxH = Math.max(1, Math.round(H * scale))\n const canvas = new Canvas(pxW, pxH)\n const el = canvas as unknown as HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n Object.assign(el, {\n getBoundingClientRect: () => ({ width: W, height: H, left: 0, top: 0, right: W, bottom: H }),\n addEventListener: () => {}, removeEventListener: () => {}, style: {},\n })\n\n let player: FlatPlayer | null = new FlatPlayer(el, withParams, { input: false, audio: false, padding: 0, seed: 1, image: (id) => images.get(id) ?? null })\n\n return {\n width: pxW,\n height: pxH,\n async frame(frame, frameOpts = {}) {\n if (!player) throw new Error('renderer is closed')\n if (frameOpts.vars) for (const [k, v] of Object.entries(frameOpts.vars)) player.setVar(k, v)\n player.seek(frame) // applies frame + state\n const steps = frameOpts.steps ?? 0\n if (steps > 0) {\n const n = Math.min(Math.floor(steps), MAX_RENDER_STEPS)\n if (steps > MAX_RENDER_STEPS) process.stderr.write(`flatc: steps clamped to ${MAX_RENDER_STEPS} (was ${steps})\\n`)\n player.stepSim(n) // run N fixed sim steps (onEnterFrame) so a stateful act unfolds before capture\n }\n return el.toBuffer('png')\n },\n close() {\n player?.destroy()\n player = null\n for (const k in saved) { if (saved[k] === undefined) delete g[k]; else g[k] = saved[k] }\n // skia keeps the parsed faces in memory after `use()`, so the temp files are safe to drop now.\n if (fontDir) try { rmSync(fontDir, { recursive: true, force: true }) } catch { /* best-effort cleanup */ }\n },\n }\n}\n\n/**\n * A copy of `doc` with the scene's instances carrying `params` — a state NAME or a number, the same\n * spelling `--preview --set` accepts, resolved by the renderer per the symbol's ParamDef / state\n * machines. Rendering a program could only override document `var`s, so previewing a door in its `open`\n * state meant writing a harness that mutated the symbol by hand. That was one of four such harnesses.\n *\n * Set on the INSTANCE, not on the symbol's defaults: that is where a call-site value belongs, and it is\n * what `--preview` already does.\n */\nfunction applyParams(doc: Doc, params: Record<string, string>): Doc {\n const entries = Object.entries(params)\n if (!entries.length) return doc\n // Which names each symbol exposes: its typed params, plus the param each state machine drives.\n const exposed = new Map<string, Set<string>>()\n for (const sym of doc.symbols) {\n exposed.set(sym.id, new Set([...(sym.states ?? []).map((st) => st.param), ...(sym.params ?? []).map((p) => p.name)]))\n }\n const out = structuredClone(doc)\n const applied = new Set<string>()\n const visit = (items: Item[]): void => {\n for (const it of items) {\n if (isInstance(it)) {\n const known = exposed.get(it.symbolId)\n if (known) {\n for (const [k, v] of entries) {\n if (!known.has(k)) continue\n ;(it.params ??= {})[k] = v\n applied.add(k)\n }\n }\n }\n if (isGroup(it)) for (const l of it.layers) visit(l.items)\n }\n }\n for (const l of out.layers) visit(l.items)\n for (const sym of out.symbols) for (const l of sym.layers) visit(l.items)\n for (const [k] of entries) {\n if (!applied.has(k)) process.stderr.write(`flatc: no symbol exposes a param named \"${k}\"\\n`)\n }\n return out\n}\n\n/** Render `doc` to a PNG (Buffer). `frame` = target image; `vars` = state override; `scale` = x-px (default 2).\n * A one-shot convenience over `createRenderer` — reach for the renderer itself as soon as you want two. */\nexport async function renderDocToPng(doc: Doc, opts: RenderOpts = {}): Promise<Uint8Array> {\n const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params })\n try {\n return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps })\n } finally {\n renderer.close()\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/render.ts"],"sourcesContent":["// -----------------------------------------------------------------------------\n// render.ts -- HEADLESS PNG rendering of a Doc (skia-canvas backend), for `flatc --render`.\n//\n// Gives agents/CI an IMAGE of what they author (the \"blind positioning\" safeguard). We replay the\n// real `FlatPlayer` on a skia canvas: same paths, gradients, filters (glow/shadow via `ctx.filter`)\n// and SVG as the browser. skia-canvas is loaded through a DYNAMIC import with a NON-LITERAL specifier,\n// so it is never part of the public build graph -- install it on demand only when `--render` is used.\n// -----------------------------------------------------------------------------\nimport { FlatPlayer, type SendEvent } from '@flatkit/player'\nimport { createReplayer, type Gesture, type Handlers, type Replayer } from '@flatkit/player/debug'\nimport type { Doc, Item } from '@flatkit/types'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\nimport { mkdtempSync, writeFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nexport type RenderOpts = { frame?: number; vars?: Record<string, number>; scale?: number; steps?: number; params?: Record<string, string>; script?: Gesture[]; settle?: number }\n\n/** What a replayed script reports: the `send`s it emitted, and the `expect` gestures that did not hold. */\nexport type PlayReport = { sends: SendEvent[]; expectFailures: string[]; warnings: string[] }\n\n/** Cap on `--steps` (anti-DoS: an untrusted doc must not freeze the render host). One step = 1/60 s of sim. */\nconst MAX_RENDER_STEPS = 10_000\n\n/** Minimal structural view of the `skia-canvas` surface we use. Kept local so the public build needs\n * no native binary; the real module is resolved at runtime only when `--render` runs. */\ninterface SkiaCanvas {\n Canvas: new (w: number, h: number) => HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n loadImage: (src: string) => Promise<unknown>\n Path2D: typeof Path2D\n /** Browser global under Node: the mask/clip path builder does `new DOMMatrix([...])` (drawScene). */\n DOMMatrix: typeof DOMMatrix\n /** Global font registry. `use(paths)` reads each file's intrinsic name-table family; the 2-arg\n * `use(family, paths)` form FORCES that alias (fixes variable-font statics whose name table lies). */\n FontLibrary: {\n use(fontPaths: readonly string[]): Array<{ family: string }>\n use(family: string, fontPaths: readonly string[]): Array<{ family: string }>\n }\n}\n\n/** Map a font asset's MIME (or its data-URI) to a file extension skia-canvas can parse. */\nconst FONT_EXT: Record<string, string> = {\n 'font/woff2': '.woff2', 'font/woff': '.woff', 'font/ttf': '.ttf', 'font/otf': '.otf',\n 'font/collection': '.ttc', 'application/font-woff': '.woff', 'application/x-font-ttf': '.ttf',\n}\n\n/** Registers every embedded `font` asset with skia's FontLibrary so headless text uses the authored\n * faces (not host fallbacks). Fonts are baked as base64 data-URIs by flatc; FontLibrary only reads\n * FILE paths, so we materialize each into a temp dir, register it, and return that dir for cleanup. */\nfunction registerFonts(doc: Doc, FontLibrary: SkiaCanvas['FontLibrary']): { dir: string | null; families: string[] } {\n const fonts = (doc.assets ?? []).filter((a) => a.kind === 'font' && /^data:/.test(a.data ?? ''))\n if (fonts.length === 0) return { dir: null, families: [] }\n const dir = mkdtempSync(join(tmpdir(), 'flatc-fonts-'))\n const families: string[] = []\n for (const a of fonts) {\n const b64 = a.data.slice(a.data.indexOf(',') + 1)\n const ext = FONT_EXT[a.mime] ?? `.${(a.mime.split('/')[1] || 'ttf').replace(/[^\\w]/g, '')}`\n const file = join(dir, a.id.replace(/[^\\w.-]/g, '_') + ext)\n try {\n writeFileSync(file, Buffer.from(b64, 'base64'))\n // `family` alias (from `asset … font \"Name\"`) forces the registered name, so the face matches the\n // text's `font \"Name\"` even when the file's own name table is wrong (variable-font static export).\n const used = a.family ? FontLibrary.use(a.family, [file]) : FontLibrary.use([file])\n for (const f of used) if (!families.includes(f.family)) families.push(f.family)\n } catch (e) {\n process.stderr.write(`flatc: skipped font asset \"${a.id}\" (${a.mime}): ${(e as Error).message}\\n`)\n }\n }\n return { dir, families }\n}\n\n/**\n * A renderer held OPEN over a document: the expensive setup is paid once, then any number of frames.\n *\n * Every consumer that needed more than one image — a GIF, an MP4, a contact sheet, a loop check —\n * reimplemented this on top of the player, because the only thing on offer rendered a single frame and\n * paid the whole cost each time: the dynamic `skia-canvas` import, writing the embedded fonts to a temp\n * dir and registering them, decoding every image asset, building a `FlatPlayer`, installing ten globals.\n * Four such harnesses exist across two neighbouring repos, ~430 lines, each rediscovering the same\n * shims — including that `document` must exist or every `filter` and `tint` is dropped in SILENCE.\n */\nexport type Renderer = {\n /** PNG of one frame. `vars` overrides state for this frame; `script` replays gestures after the seek\n * (an `interactive` renderer only); `steps` then runs N sim steps before capture. */\n frame(frame: number, opts?: { vars?: Record<string, number>; steps?: number; script?: Gesture[] }): Promise<Uint8Array>\n /** Replays gestures on the live scene, as `flatc --play` does, and leaves it in the state they reach.\n * Successive calls continue the same session (one state, one `expect` window). Needs\n * `createRenderer(doc, { interactive: true })`. */\n play(gestures: Gesture[]): PlayReport\n /** PNG of the scene AS IT IS NOW — after a `play`, without seeking. */\n capture(): Promise<Uint8Array>\n /** The document's pixel size at the renderer's scale. */\n readonly width: number\n readonly height: number\n /** Restore the globals and drop the temp font files. Always call it — it is not optional. */\n close(): void\n}\n\n/**\n * Open a renderer over `doc`. The globals it installs (`document`, `Path2D`, `DOMMatrix`, …) stay in\n * place until `close()`, which is what makes a frame loop cheap; it also means one renderer at a time\n * per process. `params` sets a SYMBOL's exposed params (a state name or a number) before the first\n * frame — the reason a consumer had to write its own harness to preview anything with a state.\n */\nexport async function createRenderer(doc: Doc, opts: { scale?: number; params?: Record<string, string>; interactive?: boolean; settle?: number } = {}): Promise<Renderer> {\n // Non-literal specifier: tsc does not resolve it, so skia-canvas is not a build dependency.\n const skiaPkg: string = 'skia-canvas'\n let skia: SkiaCanvas\n try { skia = (await import(skiaPkg)) as unknown as SkiaCanvas }\n catch {\n throw new Error(\n 'skia-canvas is required for rendering. Install it as a dev dependency: `npm i -D skia-canvas` ' +\n '(pnpm users: also allow its build script with `pnpm approve-builds` or add \"skia-canvas\" to ' +\n 'pnpm.onlyBuiltDependencies, then reinstall). If the native binary is still missing afterwards, ' +\n 'run `node node_modules/skia-canvas/lib/prebuild.mjs download`.',\n )\n }\n const { Canvas, loadImage, Path2D, FontLibrary, DOMMatrix } = skia\n const scale = opts.scale && opts.scale > 0 ? opts.scale : 2\n const W = doc.width, H = doc.height\n\n const withParams = opts.params ? applyParams(doc, opts.params) : doc\n\n // Register embedded fonts up front so the player's `ctx.font` resolves to the authored faces.\n const { dir: fontDir, families } = registerFonts(withParams, FontLibrary)\n if (families.length) process.stderr.write(`flatc: registered ${families.length} font famil${families.length > 1 ? 'ies' : 'y'}: ${families.join(', ')}\\n`)\n\n // Pre-decode the image assets (skia reads SVG/PNG/... from the data-URIs embedded by flatc).\n const images = new Map<string, CanvasImageSource>()\n for (const a of withParams.assets ?? []) {\n if (a.kind === 'svg' || a.kind === 'image' || /^data:image\\//.test(a.data ?? '')) {\n try { images.set(a.id, (await loadImage(a.data)) as unknown as CanvasImageSource) } catch { /* unreadable asset: ignored (placeholder) */ }\n }\n }\n\n // Globals the player/rendering expects under Node (restored by close()).\n const g = globalThis as Record<string, unknown>\n const saved: Record<string, unknown> = {}\n const set = (k: string, v: unknown) => { saved[k] = g[k]; g[k] = v }\n set('Path2D', Path2D)\n set('DOMMatrix', DOMMatrix) // mask/clip layers build their clip path with `new DOMMatrix([...])` (no browser global under Node)\n set('window', { addEventListener() {}, removeEventListener() {}, devicePixelRatio: scale })\n set('addEventListener', () => {})\n set('removeEventListener', () => {})\n set('requestAnimationFrame', () => 0)\n set('cancelAnimationFrame', () => {})\n set('document', { createElement: (t: string) => (t === 'canvas' ? new Canvas(1, 1) : {}) }) // off-screen canvas (filters/tint)\n\n const pxW = Math.max(1, Math.round(W * scale))\n const pxH = Math.max(1, Math.round(H * scale))\n const handlers: Handlers = {}\n const canvas = new Canvas(pxW, pxH)\n const el = canvas as unknown as HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n Object.assign(el, {\n getBoundingClientRect: () => ({ width: W, height: H, left: 0, top: 0, right: W, bottom: H }),\n // The listeners the player registers are KEPT: a replayed script fires into them (`play`).\n addEventListener: (type: string, fn: Handlers[string]) => { handlers[type] = fn },\n removeEventListener: (type: string) => { delete handlers[type] },\n setPointerCapture: () => {}, releasePointerCapture: () => {}, style: {},\n })\n\n // `interactive`: the player listens to its canvas, so gestures can be replayed on it. Off by default —\n // a renderer that only draws frames registers nothing.\n const sends: SendEvent[] = []\n let player: FlatPlayer | null = new FlatPlayer(el, withParams, { input: !!opts.interactive, audio: false, padding: 0, seed: 1, image: (id) => images.get(id) ?? null, onEvent: (e) => sends.push(e) })\n let replayer: Replayer | null = null\n const play = (gestures: Gesture[]): PlayReport => {\n if (!player) throw new Error('renderer is closed')\n if (!opts.interactive) throw new Error('this renderer cannot replay gestures: open it with createRenderer(doc, { interactive: true })')\n replayer ??= createReplayer(player, withParams, handlers, sends, { settle: opts.settle })\n for (const g of gestures) replayer.apply(g)\n return { sends: [...sends], expectFailures: [...replayer.expectFailures], warnings: [...replayer.warnings] }\n }\n\n return {\n width: pxW,\n height: pxH,\n async frame(frame, frameOpts = {}) {\n if (!player) throw new Error('renderer is closed')\n if (frameOpts.vars) for (const [k, v] of Object.entries(frameOpts.vars)) player.setVar(k, v)\n player.seek(frame) // applies frame + state\n if (frameOpts.script?.length) play(frameOpts.script)\n const steps = frameOpts.steps ?? 0\n if (steps > 0) {\n const n = Math.min(Math.floor(steps), MAX_RENDER_STEPS)\n if (steps > MAX_RENDER_STEPS) process.stderr.write(`flatc: steps clamped to ${MAX_RENDER_STEPS} (was ${steps})\\n`)\n player.stepSim(n) // run N fixed sim steps (onEnterFrame) so a stateful act unfolds before capture\n }\n player.render() // a gesture or a step paints only what it changed: draw the frame that is asked for\n return el.toBuffer('png')\n },\n play,\n async capture() {\n if (!player) throw new Error('renderer is closed')\n player.render()\n return el.toBuffer('png')\n },\n close() {\n player?.destroy()\n player = null\n for (const k in saved) { if (saved[k] === undefined) delete g[k]; else g[k] = saved[k] }\n // skia keeps the parsed faces in memory after `use()`, so the temp files are safe to drop now.\n if (fontDir) try { rmSync(fontDir, { recursive: true, force: true }) } catch { /* best-effort cleanup */ }\n },\n }\n}\n\n/**\n * A copy of `doc` with the scene's instances carrying `params` — a state NAME or a number, the same\n * spelling `--preview --set` accepts, resolved by the renderer per the symbol's ParamDef / state\n * machines. Rendering a program could only override document `var`s, so previewing a door in its `open`\n * state meant writing a harness that mutated the symbol by hand. That was one of four such harnesses.\n *\n * Set on the INSTANCE, not on the symbol's defaults: that is where a call-site value belongs, and it is\n * what `--preview` already does.\n */\nfunction applyParams(doc: Doc, params: Record<string, string>): Doc {\n const entries = Object.entries(params)\n if (!entries.length) return doc\n // Which names each symbol exposes: its typed params, plus the param each state machine drives.\n const exposed = new Map<string, Set<string>>()\n for (const sym of doc.symbols) {\n exposed.set(sym.id, new Set([...(sym.states ?? []).map((st) => st.param), ...(sym.params ?? []).map((p) => p.name)]))\n }\n const out = structuredClone(doc)\n const applied = new Set<string>()\n const visit = (items: Item[]): void => {\n for (const it of items) {\n if (isInstance(it)) {\n const known = exposed.get(it.symbolId)\n if (known) {\n for (const [k, v] of entries) {\n if (!known.has(k)) continue\n ;(it.params ??= {})[k] = v\n applied.add(k)\n }\n }\n }\n if (isGroup(it)) for (const l of it.layers) visit(l.items)\n }\n }\n for (const l of out.layers) visit(l.items)\n for (const sym of out.symbols) for (const l of sym.layers) visit(l.items)\n for (const [k] of entries) {\n if (!applied.has(k)) process.stderr.write(`flatc: no symbol exposes a param named \"${k}\"\\n`)\n }\n return out\n}\n\n/** Render `doc` to a PNG (Buffer). `frame` = target image; `vars` = state override; `scale` = x-px (default 2).\n * A one-shot convenience over `createRenderer` — reach for the renderer itself as soon as you want two. */\nexport async function renderDocToPng(doc: Doc, opts: RenderOpts = {}): Promise<Uint8Array> {\n const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params, interactive: !!opts.script?.length, settle: opts.settle })\n try {\n return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps, script: opts.script })\n } finally {\n renderer.close()\n }\n}\n"],"mappings":"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","names":["g"]}
|
package/dist/compile.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -22,11 +22,11 @@ import {
|
|
|
22
22
|
scopeProgram,
|
|
23
23
|
scopeRegions,
|
|
24
24
|
splitScopeProgram
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-RQKXBHHM.js";
|
|
26
26
|
import {
|
|
27
27
|
compileFlatpack,
|
|
28
28
|
packToJSON
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-BPX2MOW2.js";
|
|
30
30
|
|
|
31
31
|
// src/manifest.ts
|
|
32
32
|
import { EXPR_CHANNELS as EXPR_CHANNELS2, OFFSET_CHANNELS } from "@flatkit/engine/timeline";
|
package/docs/dsl-gotchas.md
CHANGED
|
@@ -59,6 +59,16 @@
|
|
|
59
59
|
- `ellipse <cx> <cy> <rx> <ry>`
|
|
60
60
|
- `rect <x> <y> <w> <h>` · `… <r>` (uniform rounded corners) · `… <rx> <ry>`
|
|
61
61
|
No more hand-computing the k = 0.5523·r Bezier constant.
|
|
62
|
+
- **A path of lines is drawn STRAIGHT (since 0.36).** `L`, `H` and `V` are flat segments, next to a curve
|
|
63
|
+
or on their own: a hexagon has six flat sides, and a hill written as `path "M-20 250 L220 218 L620 228
|
|
64
|
+
L980 222"` is a broken line. Before 0.36 such a path was rounded at every turn under 60°. For a curve
|
|
65
|
+
through the points, write **`smooth`** right after the path data: `path "M-20 250 L220 218 L620 228"
|
|
66
|
+
smooth nofill stroke #fff 3`. **There is no warning on a short path**: `flatc --check` reports a
|
|
67
|
+
missing `smooth` only on a run of 12 points or more whose outline would really move. When upgrading a
|
|
68
|
+
corpus, `flatc <files> --since 0.35` lists every path of lines that changed, with its `file:line`.
|
|
69
|
+
- **`polyline <xs> <ys> [count <n> | count "<expr>"] [closed]`** draws through two ARRAY variables, read
|
|
70
|
+
every frame (a trajectory, a curve being computed). It takes the arrays' **names**, its segments are
|
|
71
|
+
straight, and fewer than two points draw nothing.
|
|
62
72
|
- **`filter` on a path**: accepted (`path "…" fill #000 filter glow 6 #fff`). No need to wrap
|
|
63
73
|
in a `group` just to add a shadow/glow. (Also works on group/image/text.)
|
|
64
74
|
- **`linear(angle, …)` gradient**: `0` = → (left to right), `90` = ↓ (top to bottom).
|
|
@@ -368,12 +378,17 @@ and composes with `self.x`/`self.y` etc. (same `self`).
|
|
|
368
378
|
param = error.
|
|
369
379
|
- ⚠️ Compile-time sugar (re-serialized as groups, like `def`/`repeat`). For shared
|
|
370
380
|
BEHAVIOR, see `each` below.
|
|
381
|
+
- **A plain `symbol "X" { … }` may be written in the program too** (before or after the `scene`): it is
|
|
382
|
+
the very block a `.flat` holds — its own `timeline`, cels, `states`, `params` — instanced without
|
|
383
|
+
parens, and it wins over a library symbol of the same name. No dummy parameter needed any more.
|
|
384
|
+
- **`repeat` / `def` / `$()` work in a `.flat` library**, as in a program's scene: `repeat i from 0 to 8
|
|
385
|
+
{ circle $(i*20) 0 6 fill #333 }` inside a symbol's layer is unfolded when the library is read.
|
|
371
386
|
- ⚠️ **`.flatink`-only**: a parameterized `symbol "X"(…)` lives in the **program** (`.flatink`), NOT in a
|
|
372
387
|
`.flat` library. `.flat` libs hold **non-parameterized** symbols, instanced **without** parens
|
|
373
388
|
(`instance "Hero" as "H"`); a parameterized one is instanced **with** args (`instance "Card"("A")`).
|
|
374
389
|
Putting a `(…)` symbol in a `.flat` — or letting `flatc` auto-discover such a `.flat` in the folder —
|
|
375
390
|
surfaces as a misleading `"{" expected, "("` (the `.flat` parser doesn't take parameters). Rule of
|
|
376
|
-
thumb: parens ⇔ parameterized ⇔
|
|
391
|
+
thumb: parens ⇔ parameterized ⇔ program only; no parens ⇔ a real symbol, in a `.flat` or in the program.
|
|
377
392
|
- **`each "Symbol" as i { … }`**: applies BEHAVIOR to every instance of a symbol, with
|
|
378
393
|
index `i`.
|
|
379
394
|
- **Channel bindings** on real instances (`each "Brick" as i { opacity = bricks[i] }`) →
|
|
@@ -62,8 +62,14 @@ per second), `deg(r)` (the inverse, for readouts). Or bind the **`rotationDeg`**
|
|
|
62
62
|
var slots = [0, 0, 0]
|
|
63
63
|
object "P" { x = slots[i] } // computed index
|
|
64
64
|
slots[i + 1] = 1 // indexed assignment (in actions)
|
|
65
|
+
v = [10, 20, 30][i] // a table written in place, indexed at once
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
A table written in place — `[a, b, c][i]` — saves a global array for a lookup used once. Its elements and
|
|
69
|
+
its index are expressions, only the element picked is evaluated, and it indexes as an array does (the
|
|
70
|
+
index is rounded; outside the table it is `NaN`, so the binding keeps its fallback). It is not a value on
|
|
71
|
+
its own: `[1, 2, 3]` without an index is an error — to keep a table, declare `var t = [1, 2, 3]`.
|
|
72
|
+
|
|
67
73
|
## Functions (`fn`)
|
|
68
74
|
|
|
69
75
|
Define reusable helpers — a **value** function (an expression) or a **procedure** (actions):
|
|
@@ -212,8 +212,15 @@ at <x>,<y> // translation -- a COMMA between the two, n
|
|
|
212
212
|
matrix(a,b,c,d,e,f) // full affine
|
|
213
213
|
at center · at center,540 · at 120,center // canvas-relative anchor (resolved from `size`)
|
|
214
214
|
align <point> of "Name" [offset dx,dy] // pin this item's origin onto another item's bbox
|
|
215
|
+
rotate <deg> · scale <s> · scaleX <s> · scaleY <s> // a FIXED rotation / scale, after `at`
|
|
215
216
|
```
|
|
216
217
|
|
|
218
|
+
`rotate` and `scale` are written among the attributes that follow `at` — `group "G" at 100,100 pivot 20,0
|
|
219
|
+
rotate 45 scale 2 { … }` — in the units of a `pose`: **degrees** and multipliers. They turn around the
|
|
220
|
+
item's `pivot`, which stays where `at` put it, in whatever order the two are written. They are baked into
|
|
221
|
+
the item's matrix: for a rotation that *changes*, bind the channel (`expr rotation "…"`, or `rotationDeg =
|
|
222
|
+
…` in an `object` block). Not combinable with `align`.
|
|
223
|
+
|
|
217
224
|
> **Placement & naming gotchas.** The order is fixed: **content → `as "…"` → `at …` / `matrix(…)` →
|
|
218
225
|
> style attributes** (`font`/`box`/`fill`/…). So `text "…" box W H at x,y` fails — write `text "…" at x,y
|
|
219
226
|
> box W H`; and `image "logo" 80 80 at 0,0 as "L"` fails — write `image "logo" 80 80 as "L" at 0,0`
|
package/docs/tooling.md
CHANGED
|
@@ -161,6 +161,15 @@ item, and a roster item no cel ever poses. Each renders an empty frame with no o
|
|
|
161
161
|
Render a PNG (skia backend, faithful to the browser). Needs the optional `skia-canvas` dep
|
|
162
162
|
(`npm i -D skia-canvas`).
|
|
163
163
|
|
|
164
|
+
`skia-canvas` 3 (3.0.8 or later) and 4 (from `4.0.0-rc7`) are both accepted. They do not render to the
|
|
165
|
+
same pixels: **version 4 can set a line of text one device pixel higher or lower** than version 3 (half a
|
|
166
|
+
unit at the default scale of 2); shapes are identical. It is a rounding that falls one way or the other
|
|
167
|
+
from one text to the next, not a uniform shift: on a same picture one block may move down, another up, a
|
|
168
|
+
third not at all (measured by a consumer on 545 scenes: 16 identical to the pixel, a median of 0.31% of
|
|
169
|
+
pixels visibly different, 3.4% at most on a page of text). Frames rendered with one must not be mixed
|
|
170
|
+
with frames rendered with the other in the same video, and a pixel comparison between two renders needs
|
|
171
|
+
the same version on both sides. While version 4 is a release candidate, pin the exact version.
|
|
172
|
+
|
|
164
173
|
```
|
|
165
174
|
flatc <file> --render -o out.png [--frame N] [--at k=v[,k2=v2]] [--steps N] [--scale S]
|
|
166
175
|
```
|
|
@@ -169,6 +178,13 @@ flatc <file> --render -o out.png [--frame N] [--at k=v[,k2=v2]] [--steps N] [--s
|
|
|
169
178
|
- `--at score=3,step=2` forces variables → capture a precise state.
|
|
170
179
|
- **`--steps N`** runs N fixed simulation steps (`every frame`, 60 Hz) *before* capture, so a stateful
|
|
171
180
|
act unfolds on its own — no need to force every derived ramp variable by hand.
|
|
181
|
+
- **`--script gestures.json`** replays a gesture script first, exactly as `--play` does, and renders the
|
|
182
|
+
state it reaches — the third right answer, the piece dropped, level 2 — instead of a copy of the program
|
|
183
|
+
with other initial `var`s. The order is: `--frame`, `--at`, the script, `--steps`, the capture. A
|
|
184
|
+
**`{ "type": "shot", "name": "after" }`** gesture writes the image *at that point* next to the output
|
|
185
|
+
(`out.after.png`; unnamed shots are numbered), and the output is always the final state. A failed
|
|
186
|
+
`expect` makes the run exit ≠0, images written all the same. `--settle` applies as in `--play`. From
|
|
187
|
+
code: `createRenderer(doc, { interactive: true })`, then `play(gestures)` and `capture()`.
|
|
172
188
|
- **Embedded fonts render too**: any `asset "id" "font.woff2" font` is registered with skia before
|
|
173
189
|
capture, so text uses the authored face (matched by the font's intrinsic family name — the same name
|
|
174
190
|
you put in `text … font "…"`) instead of a host fallback. `.woff2/.woff/.ttf/.otf` are all supported;
|
|
@@ -192,7 +208,12 @@ flatc --preview <library.flat> [--symbol NAME] [-o out.flatpack | --render -o ou
|
|
|
192
208
|
- `--symbol NAME` picks the symbol (default: the first; others are listed on stderr).
|
|
193
209
|
- **`--bbox all`** (default) auto-sizes to the UNION of bounds over every frame (sub-timelines unfrozen),
|
|
194
210
|
so drifting/rotating/growing motion is never clipped. `--bbox frame0` is the old frame-0-only measure;
|
|
195
|
-
`--pad N` adds a margin (default 24).
|
|
211
|
+
`--pad N` adds a margin (default 24). The symbol is measured **as it is drawn**: its `expr` channels
|
|
212
|
+
are evaluated with its params — the declared defaults, or the `--set` values — so a bar stretched by
|
|
213
|
+
`expr scaleX "long"` is framed at its real length.
|
|
214
|
+
- **`--frame N` does not move a symbol that has `states`**: its pose is set by the state, not by the
|
|
215
|
+
playhead (only the loops nested in it advance). `flatc` says so; to see a state, or a point between
|
|
216
|
+
two, use `--set <param>=<state name | value>` (`--set pos=0.5` is halfway).
|
|
196
217
|
- **`--set p=v`** sets the symbol's exposed [params](animating-symbols.md#exposed-parameters-params): a
|
|
197
218
|
`color` (`hull=#1a5`), a `number`/`bool` (`wave=1.5`), or a `state` by name (`door=open`). Baked into the
|
|
198
219
|
preview (flatpack + render).
|
|
@@ -214,7 +235,7 @@ Use `external` for big media you don't want inflating the JSON; serve the folder
|
|
|
214
235
|
Run a scene **without a canvas**, replay a gesture script, and print `{ sends, vars }` — great in CI.
|
|
215
236
|
|
|
216
237
|
```
|
|
217
|
-
flatc <file> --play --script gestures.json [--trace]
|
|
238
|
+
flatc <file> --play --script gestures.json [--trace] [--settle N]
|
|
218
239
|
```
|
|
219
240
|
|
|
220
241
|
**Prefer semantic gestures** (by object NAME — robust, the engine resolves coordinates):
|
|
@@ -238,10 +259,22 @@ flatc <file> --play --script gestures.json [--trace]
|
|
|
238
259
|
- Audio is off in `--play`: a `sound` action is a silent no-op, so a program is replayed as written.
|
|
239
260
|
- `random()` is seeded in `--play` (seed `1`), so a replay says the same thing twice and an `expect` can
|
|
240
261
|
assert on a draw. `--seed N` picks another one.
|
|
241
|
-
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
262
|
+
- **A pointer event takes a frame.** Each press, move and release is followed by one simulation step, as
|
|
263
|
+
a real pointer stays at least one frame on each position — so a rule written in `every frame` sees the
|
|
264
|
+
drag. `--settle N` sets that number for the whole script, and `"settle": N` on a gesture sets it for that
|
|
265
|
+
one. **`--settle 0` is exactly the replay of before 0.38** (`turn` keeps the step it always took between
|
|
266
|
+
its sub-moves). A script that already paces itself with `wait` gestures gets one more frame per event:
|
|
267
|
+
drop the `wait`, or keep the script as it is with `--settle 0`. A script that `expect`s a value which
|
|
268
|
+
decays every frame (a feedback pulse read right after the tap) needs `"settle": 0` on that tap.
|
|
269
|
+
- **`turn`** turns a `turn`/`turnDeg` target **to** `angle`: the value the gesture ENDS at, wherever the
|
|
270
|
+
press was — not a rotation added to the current one. Degrees for `turnDeg`, radians for `turn`; `0` is
|
|
271
|
+
to the right of the pivot, positive is clockwise on screen. It is swept in sub-steps, so several turns
|
|
272
|
+
land (`"angle": 540`). The press goes to the object's position, then to the centre of its drawn box (a
|
|
273
|
+
hand drawn *from* its pivot has its origin on the edge of its shape). **`"from": [x, y]`** names the
|
|
274
|
+
press point — the way to pick one of two hands overlapping at noon. A press that does not grab the
|
|
275
|
+
target is **reported** — a `warnings` entry in the JSON, a `flatc: warning:` line on stderr — naming
|
|
276
|
+
what is grabbed there instead; it used to turn the wrong object, or none, in silence. It is not a
|
|
277
|
+
failure: a script may be proving that a locked dial does not respond.
|
|
245
278
|
- `set` drives a variable from the host; `wait` runs N fixed 60 Hz steps (advances `every frame` physics).
|
|
246
279
|
- **`key`** holds a key down (`keys.<name>` reads `1`) for `frames` steps — default `1` — then releases
|
|
247
280
|
it: the way to test a keyboard-driven scene in CI. Use the authored name (`"ArrowRight"`, `"Space"`).
|
|
@@ -275,8 +308,10 @@ with its `file:line`. Nothing is modified, and the exit code is 0.
|
|
|
275
308
|
|
|
276
309
|
One change is recorded so far. Until 0.36 a path made only of lines (`L`, `H`, `V`) was rounded at its
|
|
277
310
|
gentle turns; it is now drawn straight, as its data says. The report lists each such path that moves by
|
|
278
|
-
1% of its size or more
|
|
279
|
-
|
|
311
|
+
1% of its size or more, open or closed, with how far it moves — a soft hill written as four points wants
|
|
312
|
+
`smooth` back (`path "…" smooth`), a hexagon does not. An open line is also listed when it moves by 2
|
|
313
|
+
units or more, however little that is of its length: a horizon across the whole frame bends by a few
|
|
314
|
+
units and by a fraction of a percent. `--all` adds the slighter ones.
|
|
280
315
|
|
|
281
316
|
It is a report and not a `--check` warning on purpose: a polyline that is *meant* straight has nothing to
|
|
282
317
|
add to say so, and would be warned about for ever.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flatkit/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "The FlatInk language (parser + AST) and compiler (.flatink → .flatpack). Ships the flatc CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Zwyk Studio",
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"docs"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@flatkit/engine": "0.
|
|
61
|
-
"@flatkit/
|
|
62
|
-
"@flatkit/
|
|
60
|
+
"@flatkit/engine": "0.38.0",
|
|
61
|
+
"@flatkit/player": "0.38.0",
|
|
62
|
+
"@flatkit/types": "0.38.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"skia-canvas": "^3.0.8"
|
|
65
|
+
"skia-canvas": "^3.0.8 || ^4.0.0-rc7"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"skia-canvas": {
|
package/prompts/flatink-core.md
CHANGED
|
@@ -47,13 +47,22 @@ Coordinates are plain numbers; canvas origin is **top-left**. Layers stack botto
|
|
|
47
47
|
circle <cx> <cy> <r>
|
|
48
48
|
ellipse <cx> <cy> <rx> <ry>
|
|
49
49
|
rect <x> <y> <w> <h> [<r> | <rx> <ry>] // optional rounded corners
|
|
50
|
-
path "M0 0 L10 0 L10 10 Z" // raw SVG path data
|
|
50
|
+
path "M0 0 L10 0 L10 10 Z" // raw SVG path data: L is a STRAIGHT line
|
|
51
|
+
path "M0 60 L80 40 L200 52 L320 44" smooth // the same points joined by a curve (a hill, a wave)
|
|
52
|
+
polyline xs ys [count "n"] [closed] // points read from two array vars, every frame
|
|
51
53
|
text "Hi" font "sans-serif" size 24 align center line 1.2 color #fff box 200 40
|
|
52
54
|
image "logo" 80 80 at -40,-40 // origin = top-left → center with at -w/2,-h/2
|
|
53
|
-
group "Name" at x,y pivot px,py { layer "c" { … } } // nests its own layers
|
|
55
|
+
group "Name" at x,y pivot px,py [rotate deg] [scale s] { layer "c" { … } } // nests its own layers; a FIXED rotate/scale, around the pivot
|
|
54
56
|
instance "Symbol" as "Name" at x,y // place a symbol from a .flat lib
|
|
55
57
|
```
|
|
56
58
|
|
|
59
|
+
**A path of lines is drawn straight.** `L`, `H` and `V` join their points with flat segments — a hexagon
|
|
60
|
+
has six flat sides, and a hill written as four points is a broken line. For a curve through the points,
|
|
61
|
+
write **`smooth`** right after the path data (it rounds every turn under 60° and keeps the sharp ones), or
|
|
62
|
+
use real curves (`C`, `Q`, `A`). `flatc --check` only warns about a missing `smooth` from 12 points up:
|
|
63
|
+
on a shorter path nothing tells you. `polyline` takes the NAMES of two arrays (`var xs = fill(50, 0)`);
|
|
64
|
+
its segments are straight too.
|
|
65
|
+
|
|
57
66
|
Paint / style (work on shapes; most on text & groups too):
|
|
58
67
|
```
|
|
59
68
|
fill #rrggbb | nofill
|
|
@@ -263,6 +272,7 @@ feedback lift tilt dim shake(<expr>) // one-liner reactions
|
|
|
263
272
|
def gap = 70 // compile-time constant, used via $()
|
|
264
273
|
repeat i from 0 to 4 { circle $(40 + i*gap) 80 6 fill #ffd98a } // $(expr) = compile-time arithmetic
|
|
265
274
|
symbol "Card"(label, tint = "#fff") { … text "$(label)" … fill $(tint) … } // parameterized symbol
|
|
275
|
+
symbol "Dot" { layer "a" { circle 0 0 10 fill #c33 } } // a plain symbol may live in the program too: instance "Dot" as "D" at 100,100
|
|
266
276
|
instance "Card"($(i+1)) as "C$(i)" at $(80 + i*90),200
|
|
267
277
|
each "Key" as i { when clicked { input = input*10 + (i+1) } } // shared behavior over instances
|
|
268
278
|
match Word1, Word2 onto Good, Bad { // declarative drag+drop pairing
|
|
@@ -276,7 +286,7 @@ align top of "Bin" [offset dx,dy] // pin origin onto ano
|
|
|
276
286
|
## Expressions & stdlib
|
|
277
287
|
|
|
278
288
|
Pure & numeric (no booleans: comparisons/logic yield `1`/`0`). Operators: `?: || && == != < > <= >=
|
|
279
|
-
+ - * / % - ! . [] fn()`.
|
|
289
|
+
+ - * / % - ! . [] fn()`. A lookup table can be written in place and indexed at once: `[10, 20, 30][i]`.
|
|
280
290
|
Built-ins: `sin cos tan asin acos atan atan2 abs sqrt pow exp log floor ceil round sign min max hypot
|
|
281
291
|
clamp(x,lo,hi) lerp(a,b,t) mod(a,b) between(x,lo,hi) rad(deg) deg(rad) turns(n)`. Constants `PI TAU E`.
|
|
282
292
|
Reserved: `time` (seconds, **wraps** every `durationFrames`), `clock` (seconds, **monotone**), `frame`,
|
package/prompts/flatink-lite.md
CHANGED
|
@@ -34,9 +34,12 @@ every frame { if score >= 10 { send "win" } }
|
|
|
34
34
|
circle cx cy r · ellipse cx cy rx ry · rect x y w h [r | rx ry] · path "M0 0 L10 0 L10 10 Z"
|
|
35
35
|
text "Hi" font "sans-serif" size 24 align center line 1.2 color #fff box 200 40 [bold] [italic] [wrap]
|
|
36
36
|
image "id" w h at -w/2,-h/2 // origin top-left → center yourself ; needs: asset "id" "f.png" image
|
|
37
|
-
group "Name" at x,y pivot px,py { layer "c" { … } }
|
|
37
|
+
group "Name" at x,y pivot px,py [rotate deg] [scale s] { layer "c" { … } } // nests its own layers
|
|
38
38
|
instance "Symbol" as "Name" at x,y // place a symbol from a .flat
|
|
39
|
+
polyline xs ys [count "n"] [closed] // straight segments through two array vars, live
|
|
39
40
|
```
|
|
41
|
+
A path of lines (`L`/`H`/`V`) is drawn **straight**; for a curve through the points add `smooth` after the
|
|
42
|
+
data: `path "M0 60 L80 40 L200 52" smooth`. `--check` warns about a missing `smooth` only from 12 points up.
|
|
40
43
|
Style: `fill #rrggbb | nofill` · `stroke #rgb <w> [cap round][join round][dash a,b]` · `opacity 0..1` ·
|
|
41
44
|
`draw <0..1> [from <0..1>]` (stroke extent by ARC LENGTH; quoted = expression: `draw "avance"` = ink drawn
|
|
42
45
|
behind a finger, the measure `trace` reports) ·
|
|
@@ -99,7 +102,7 @@ State/funcs: `var a = 0` · `var arr = fill(8,0)` (also as an ASSIGNMENT: `arr =
|
|
|
99
102
|
· `each "Key" as i { when clicked { … } }` · `at center` · `align top of "Bin" [offset dx,dy]`.
|
|
100
103
|
|
|
101
104
|
## Expressions
|
|
102
|
-
Pure numeric, no booleans (compare/logic → 1/0). Ops: `?: || && == != < > <= >= + - * / % - ! . [] fn()`.
|
|
105
|
+
Pure numeric, no booleans (compare/logic → 1/0). Ops: `?: || && == != < > <= >= + - * / % - ! . [] fn()` · inline table `[10, 20, 30][i]`.
|
|
103
106
|
Funcs: `sin cos tan atan2 abs sqrt pow floor ceil round sign min max hypot clamp(x,lo,hi) lerp(a,b,t)
|
|
104
107
|
mod(a,b) between(x,lo,hi) rad(deg) deg(rad) turns(n)`. Const `PI TAU E`.
|
|
105
108
|
Reserved: `time`(s, **wraps**) `clock`(s, **monotone**) `frame` `value` `mouse.x/y` `keys.<Key>` `self.*` `<Name>.*`.
|
|
@@ -84,14 +84,18 @@ symbol "Boat" {
|
|
|
84
84
|
3. **`stroke`, `opacity`, `filter` all exist on `path` AND `text`.** Outline text with
|
|
85
85
|
`text "…" color #fff stroke #000 4 join round` (stroke drawn behind fill) — never stack two texts.
|
|
86
86
|
4. **`image` origin is top-left** → center with `at -w/2,-h/2`.
|
|
87
|
-
5. **
|
|
87
|
+
5. **A path of lines is drawn STRAIGHT.** `L`/`H`/`V` are flat segments: a hill, a wave or a blob written
|
|
88
|
+
as a few points comes out as a broken line. For a curve through the points write `smooth` after the
|
|
89
|
+
data — `path "M0 60 L80 40 L200 52 L320 44" smooth nofill stroke #fff 3` — or use `C`/`Q`/`A`.
|
|
90
|
+
`flatc --check` warns about a missing `smooth` only from 12 points up; below that, nothing tells you.
|
|
91
|
+
6. **Text never wraps unless you add `wrap`** (`… box W H wrap`); otherwise only explicit `\n` breaks.
|
|
88
92
|
Also, `at x,y` on `text`/`image` must come **right after the content**, before `font`/`box`/`fill`
|
|
89
93
|
(`text "…" at x,y box W H`, not `… box W H at x,y`).
|
|
90
|
-
|
|
94
|
+
7. **Filters are cheap only on static decor** (auto-cached). Fine here since your assets are static —
|
|
91
95
|
but a large blurred plane is costly; for big soft shadows prefer a "baked" offset copy in `#00000028`.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
8. **Layer order = z-order.** Bottom layer drawn first. Put shadows/backings on lower layers.
|
|
97
|
+
9. **Don't hand-compute Bezier circles** — use `circle`/`ellipse`/`rect` sugar; they normalize to paths.
|
|
98
|
+
10. **Center art on `0,0`** so the asset rotates/scales cleanly when someone animates it later.
|
|
95
99
|
|
|
96
100
|
## Self-check
|
|
97
101
|
- File is only `symbol "…" { … }` blocks, no `size`, no behavior.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/compile.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// compile.ts — the \"modern SWF\" compiler (RFC, step P2).\n//\n// Takes the PROGRAM (`.flatink`) + the ASSETS (`.flat`) and produces a resolved `Doc` —\n// the \".flatpack\" v1 (= the baked doc the player already plays). Steps:\n// 1. parse the assets → symbols;\n// 2. parse the program → composition + behavior (`@Name` refs);\n// 3. RESOLVE refs by name (instances → symbol id), across ALL libs;\n// 4. assemble the Doc.\n//\n// v1: no binary optimization nor media embedding (P2b). Pure, no DOM.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Doc, Item, Layer } from '@flatkit/types'\nimport { parseFlatLib, parseProgramFull } from '@flatkit/engine/flatFormat'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\n\n/** Resolves `symbolId: '@Name'` instances into real ids (recursive, across groups). */\nfunction resolveRefs(layers: Layer[], byName: Map<string, string>): void {\n const walk = (items: Item[]) => {\n for (const it of items) {\n if (isInstance(it) && it.symbolId.startsWith('@')) it.symbolId = byName.get(it.symbolId.slice(1)) ?? it.symbolId\n if (isGroup(it)) it.layers.forEach((l) => walk(l.items))\n }\n }\n layers.forEach((l) => walk(l.items))\n}\n\n/** Source of a media (base64 data-URI) by declared path (`asset … \"path\" …`). */\nexport type MediaMap = Record<string, { mime: string; data: string }>\n\n/**\n * Compile a `.flatink` program + its `.flat` assets → playable `Doc` (the \".flatpack\").\n * `assetSrcs` = the text of each `.flat` lib. `media` = the content of the referenced media files\n * (by path). Symbol refs (by name) are resolved across the whole set of libs; declared media\n * are EMBEDDED (path → data-URI).\n */\nexport function compileFlatpack(programSrc: string, assetSrcs: string[] = [], media: MediaMap = {}): Doc {\n const libs = assetSrcs.map((src) => parseFlatLib(src))\n const symbols = libs.flatMap((l) => l.symbols)\n const folders = libs.flatMap((l) => l.folders) // library folders (organization)\n const prog = parseProgramFull(programSrc)\n const byName = new Map(symbols.map((s) => [s.name, s.id]))\n symbols.forEach((s) => resolveRefs(s.layers, byName)) // cross-lib refs\n resolveRefs(prog.layers, byName) // program refs\n\n // Embed declared media (data = path → provided data-URI). Missing = left as is.\n const assets = (prog.assets ?? []).map((a) => {\n const m = media[a.data]\n const r = m ? { ...a, mime: m.mime, data: m.data } : a\n // A font without an explicit family defaults to its declared id — which is exactly what text targets via\n // `font \"<id>\"`. Gives consumers an explicit `family` instead of relying on a `family || id` fallback.\n return r.kind === 'font' && !r.family ? { ...r, family: r.id } : r\n })\n\n return {\n width: prog.width,\n height: prog.height,\n ...(prog.background ? { background: prog.background } : {}),\n symbols,\n ...(folders.length ? { folders } : {}),\n layers: prog.layers,\n ...(prog.variables && Object.keys(prog.variables).length ? { variables: prog.variables } : {}),\n ...(prog.imports?.length ? { imports: prog.imports } : {}),\n ...(prog.functions?.length ? { functions: prog.functions } : {}),\n timeline: prog.timeline ?? { fps: 24, durationFrames: 60, tracks: [] },\n ...(prog.interactions?.length ? { interactions: prog.interactions } : {}),\n ...(prog.interactors?.length ? { interactors: prog.interactors } : {}),\n ...(assets.length ? { assets } : {}),\n }\n}\n\n/** Serialize the `.flatpack` v1 (JSON of the compiled Doc). */\nexport const packToJSON = (doc: Doc): string => JSON.stringify(doc)\n"],"mappings":";AAaA,SAAS,cAAc,wBAAwB;AAC/C,SAAS,SAAS,kBAAkB;AAGpC,SAAS,YAAY,QAAiB,QAAmC;AACvE,QAAM,OAAO,CAAC,UAAkB;AAC9B,eAAW,MAAM,OAAO;AACtB,UAAI,WAAW,EAAE,KAAK,GAAG,SAAS,WAAW,GAAG,EAAG,IAAG,WAAW,OAAO,IAAI,GAAG,SAAS,MAAM,CAAC,CAAC,KAAK,GAAG;AACxG,UAAI,QAAQ,EAAE,EAAG,IAAG,OAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AAAA,IACzD;AAAA,EACF;AACA,SAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AACrC;AAWO,SAAS,gBAAgB,YAAoB,YAAsB,CAAC,GAAG,QAAkB,CAAC,GAAQ;AACvG,QAAM,OAAO,UAAU,IAAI,CAAC,QAAQ,aAAa,GAAG,CAAC;AACrD,QAAM,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO;AAC7C,QAAM,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO;AAC7C,QAAM,OAAO,iBAAiB,UAAU;AACxC,QAAM,SAAS,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;AACzD,UAAQ,QAAQ,CAAC,MAAM,YAAY,EAAE,QAAQ,MAAM,CAAC;AACpD,cAAY,KAAK,QAAQ,MAAM;AAG/B,QAAM,UAAU,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM;AAC5C,UAAM,IAAI,MAAM,EAAE,IAAI;AACtB,UAAM,IAAI,IAAI,EAAE,GAAG,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,IAAI;AAGrD,WAAO,EAAE,SAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,GAAG,QAAQ,EAAE,GAAG,IAAI;AAAA,EACnE,CAAC;AAED,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IACzD;AAAA,IACA,GAAI,QAAQ,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpC,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,OAAO,KAAK,KAAK,SAAS,EAAE,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC5F,GAAI,KAAK,SAAS,SAAS,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IACxD,GAAI,KAAK,WAAW,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC9D,UAAU,KAAK,YAAY,EAAE,KAAK,IAAI,gBAAgB,IAAI,QAAQ,CAAC,EAAE;AAAA,IACrE,GAAI,KAAK,cAAc,SAAS,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IACvE,GAAI,KAAK,aAAa,SAAS,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,IACpE,GAAI,OAAO,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,EACpC;AACF;AAGO,IAAM,aAAa,CAAC,QAAqB,KAAK,UAAU,GAAG;","names":[]}
|
|
File without changes
|