@field/cascade 0.10.0 → 0.12.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/AGENTS.md +25 -5
- package/ARCHITECTURE.md +5 -4
- package/CHANGELOG.md +382 -0
- package/README.md +1 -2
- package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
- package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-C-xYG-Wn.js} +120 -120
- package/dist/assets/CodePanel-CtXd50m6.js +2 -0
- package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
- package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
- package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
- package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
- package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
- package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
- package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
- package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-CBX3Wa87.js} +1 -1
- package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
- package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
- package/dist/assets/assert-BO2_erPD.js +1 -0
- package/dist/assets/assert-hvykGCnP.js +1 -0
- package/dist/assets/builder-BmBKcDix.js +1 -0
- package/dist/assets/camera-DFF7YA3F.js +1 -0
- package/dist/assets/checks-DAvyO9Gf.js +1 -0
- package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
- package/dist/assets/{core-BICrEt0y.js → core-DB-MkGjj.js} +3 -4
- package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-DPVckSOc.js} +1 -1
- package/dist/assets/definitions-B227e7f_.js +1 -0
- package/dist/assets/definitions-D0WOUJd7.js +1 -0
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-Dkutg_Z1.js} +1 -1
- package/dist/assets/expressions-X-Z-GrgW.js +5 -0
- package/dist/assets/file-DAL6IfjV.js +1 -0
- package/dist/assets/filters-D3k1LRMa.js +1 -0
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-CLiLKk3B.js} +1 -1
- package/dist/assets/generators-BhDxRAFC.js +1 -0
- package/dist/assets/geo-BsMImJIJ.js +1 -0
- package/dist/assets/geo-C4YngDob.js +2 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-CcD3aMeL.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-DvZWGfqb.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUD3VaoN.js} +1 -1
- package/dist/assets/image-0SfH-snh.js +1 -0
- package/dist/assets/image-Ck-RdGU4.js +2 -0
- package/dist/assets/index-BWnrmIB9.js +137 -0
- package/dist/assets/index-DIaxEONI.css +1 -0
- package/dist/assets/io-BGmt2g0D.js +1 -0
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-Cap2ahgh.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-B0uLjp9C.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-CnxwhfLn.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
- package/dist/assets/math-GeULS-sk.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-BV3jbrVa.js} +1 -1
- package/dist/assets/pop-KMF_qloi.js +1 -0
- package/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-CGyFk4XK.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CQfzX3-N.js} +1 -1
- package/dist/assets/remap-5SjXQU-q.js +1 -0
- package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
- package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
- package/dist/assets/text-ZpX7qNkx.js +2 -0
- package/dist/assets/time-P_OiBhcf.js +1 -0
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BOeXzO9H.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-J1SnE_Tu.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-CYwwTiVF.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-DnPRNzK9.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.js → yaml-BmmYBNa9.js} +1 -1
- package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
- package/dist/cli/Graph-OB6EWSGL.js +2975 -0
- package/dist/cli/assert-GROGQNCQ.js +25 -0
- package/dist/cli/checks-3WB44XBL.js +189 -0
- package/dist/cli/chunk-2JEU3MGB.js +1125 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-3RXL5GHB.js +816 -0
- package/dist/cli/chunk-53LGFPWX.js +138 -0
- package/dist/cli/chunk-5ORUQ6ES.js +267 -0
- package/dist/cli/chunk-73S4JZXK.js +336 -0
- package/dist/cli/chunk-7QE2ZNSN.js +121 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-BPODRH52.js +201 -0
- package/dist/cli/chunk-BZ63XQ3I.js +1301 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-CTPLRSWF.js +2197 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
- package/dist/cli/chunk-EZRWJ4G2.js +104 -0
- package/dist/cli/chunk-FWMSGKEH.js +71 -0
- package/dist/cli/chunk-G23A2BZZ.js +705 -0
- package/dist/cli/chunk-GRIN2DDP.js +1465 -0
- package/dist/cli/chunk-HFHWYL55.js +139 -0
- package/dist/cli/chunk-HSNOBIVT.js +263 -0
- package/dist/cli/chunk-ICH7N5PN.js +26 -0
- package/dist/cli/chunk-J7VK7MNF.js +418 -0
- package/dist/cli/chunk-JVOCK5W4.js +83 -0
- package/dist/cli/chunk-KBGUO27Q.js +15 -0
- package/dist/cli/chunk-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-L6B6FUQP.js +330 -0
- package/dist/cli/chunk-LTXWBXOM.js +438 -0
- package/dist/cli/chunk-MI5JLQYA.js +220 -0
- package/dist/cli/chunk-MP653A5O.js +3766 -0
- package/dist/cli/chunk-NJFMDXHR.js +320 -0
- package/dist/cli/chunk-OM6BEME6.js +267 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-ST6LROUG.js +267 -0
- package/dist/cli/chunk-TJW67QSI.js +225 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WGKPJZDY.js +364 -0
- package/dist/cli/chunk-X4WKAOQP.js +634 -0
- package/dist/cli/chunk-X566FWBV.js +156 -0
- package/dist/cli/chunk-YH637PDL.js +317 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/chunk-ZSCUZE6I.js +112 -0
- package/dist/cli/cliCommands-JJN5Z3ZC.js +3470 -0
- package/dist/cli/commands-PHYFYA3F.js +1374 -0
- package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
- package/dist/cli/definitions-BPOPW52L.js +32 -0
- package/dist/cli/doctorCommand-ORWQRNYP.js +106 -0
- package/dist/cli/edit-S5KEQ5RA.js +28 -0
- package/dist/cli/editorCommands-NFR32JOY.js +41 -0
- package/dist/cli/encode-ICPEPMH5.js +17 -0
- package/dist/cli/file-3HTQHPYQ.js +41 -0
- package/dist/cli/filters-6ODFWYK4.js +379 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/generators-YU7GB4MD.js +210 -0
- package/dist/cli/geo-6ZPGKZRU.js +43 -0
- package/dist/cli/graphCommands-EOUOBOIJ.js +184 -0
- package/dist/cli/graphFile-YTGJB5F5.js +292 -0
- package/dist/cli/headlessCanvas-ENRHC7GS.js +16 -0
- package/dist/cli/helpCommand-PULH6ZYB.js +157 -0
- package/dist/cli/image-TXELZZK5.js +55 -0
- package/dist/cli/index.js +100 -30756
- package/dist/cli/math-RSYRQ3C6.js +16 -0
- package/dist/cli/mcp-JVZFQOEX.js +164 -0
- package/dist/cli/parity-X6DEH2DV.js +334 -0
- package/dist/cli/pop-B7SIIYKN.js +39 -0
- package/dist/cli/project-VYGIA7JX.js +12 -0
- package/dist/cli/projectDefinition-F3D7UIHJ.js +27 -0
- package/dist/cli/projectRuntime-2Q4JKEH3.js +55 -0
- package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
- package/dist/cli/render-PT4MWI6U.js +438 -0
- package/dist/cli/runCommand-HNPQVZMS.js +198 -0
- package/dist/cli/runner-5XPXVW7J.js +43 -0
- package/dist/cli/runtime/image.ts +302 -0
- package/dist/cli/runtime/io.ts +78 -0
- package/dist/cli/runtime/stage.ts +4 -1
- package/dist/cli/share-MHF6TNRK.js +166 -0
- package/dist/cli/stageCache-TY2BFZJR.js +15 -0
- package/dist/cli/stageRunner-KY2EJP7X.js +10 -0
- package/dist/cli/text-MB3EKR4H.js +126 -0
- package/dist/cli/verbs-PI2JH5FI.js +19 -0
- package/dist/cli/webBuild-WPPMGGW7.js +44 -0
- package/dist/contracts/capabilities.d.ts +18 -1
- package/dist/contracts/color.d.ts +8 -0
- package/dist/contracts/color.js +23 -0
- package/dist/contracts/definition.d.ts +10 -0
- package/dist/contracts/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +17 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/schema.d.ts +1 -1
- package/dist/contracts/schema.js +1 -0
- package/dist/contracts/validate.js +11 -1
- package/dist/contracts/values.d.ts +24 -0
- package/dist/image.d.ts +156 -0
- package/dist/image.js +161 -0
- package/dist/index.html +8 -2
- package/dist/io.d.ts +29 -0
- package/dist/io.js +40 -0
- package/dist/player/app.js +3 -2
- package/dist/player/editor.js +35 -9
- package/dist/player/editorEngine.js +3 -2
- package/dist/runtime/builtins/assert/checks.d.ts +18 -0
- package/dist/runtime/builtins/assert/checks.js +207 -0
- package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
- package/dist/runtime/builtins/assert/definitions.js +54 -0
- package/dist/runtime/builtins/assert/index.d.ts +152 -0
- package/dist/runtime/builtins/assert/index.js +36 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +64 -0
- package/dist/runtime/builtins/image/contactSheet.js +150 -0
- package/dist/runtime/builtins/image/definitions.d.ts +950 -0
- package/dist/runtime/builtins/image/definitions.js +343 -0
- package/dist/runtime/builtins/image/file.d.ts +8 -0
- package/dist/runtime/builtins/image/file.js +37 -0
- package/dist/runtime/builtins/image/filters.d.ts +8 -0
- package/dist/runtime/builtins/image/filters.js +394 -0
- package/dist/runtime/builtins/image/generators.d.ts +7 -0
- package/dist/runtime/builtins/image/generators.js +144 -0
- package/dist/runtime/builtins/image/index.d.ts +952 -0
- package/dist/runtime/builtins/image/index.js +44 -0
- package/dist/runtime/builtins/image/io.d.ts +100 -0
- package/dist/runtime/builtins/image/io.js +142 -0
- package/dist/runtime/builtins/image/planar.d.ts +55 -0
- package/dist/runtime/builtins/image/planar.js +274 -0
- package/dist/runtime/builtins/image/simplex.d.ts +22 -0
- package/dist/runtime/builtins/image/simplex.js +92 -0
- package/dist/runtime/builtins/image/text.d.ts +4 -0
- package/dist/runtime/builtins/image/text.js +141 -0
- package/dist/runtime/builtins/index.d.ts +3 -0
- package/dist/runtime/builtins/index.js +7 -0
- package/dist/runtime/gpu.d.ts +25 -0
- package/dist/runtime/gpu.js +40 -0
- package/dist/runtime/runtime.d.ts +2 -0
- package/dist/runtime/runtime.js +268 -44
- package/dist/runtime/types.d.ts +30 -0
- package/dist/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +91 -2
- package/doc/EDITOR_EXPORT.md +37 -2
- package/doc/MCP.md +18 -1
- package/doc/NODE_REFERENCE.md +424 -10
- package/doc/PROJECT_AUTHORING.md +222 -7
- package/package.json +19 -4
- package/dist/assets/CodePanel-sY6sZa7B.js +0 -2
- package/dist/assets/CustomNodeDialog-BRKnqZhp.js +0 -1
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +0 -1
- package/dist/assets/ProjectSettings-Cw17nNe3.js +0 -2
- package/dist/assets/definitions-DUww2Rt-.js +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- package/dist/assets/image-DR4oeyJB.js +0 -2062
- package/dist/assets/index-7vae6YFC.css +0 -1
- package/dist/assets/index-DyvDC6pS.js +0 -219
- package/dist/assets/math-DaDEgWKt.js +0 -1
- package/dist/assets/pop-pLH7L0PW.js +0 -1
package/dist/image.d.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cascade/image` — the one image API a node body uses, in every host.
|
|
3
|
+
*
|
|
4
|
+
* A node that draws used to have two honest choices and one dishonest one.
|
|
5
|
+
* `new OffscreenCanvas` worked in Studio and under `cascade run` (the CLI
|
|
6
|
+
* installs Skia's canvas as that global), but the bytes in and out went
|
|
7
|
+
* through `cascade/io`'s project paths, and fonts had no portable door at all.
|
|
8
|
+
* So the nodes that needed fonts or files reached for `@napi-rs/canvas` and
|
|
9
|
+
* `node:fs` directly, declared `runsOn: 'server'`, and could never be exported
|
|
10
|
+
* as a browser editor — the commons plotter-social job was blocked on exactly
|
|
11
|
+
* that ("Cascade has no browser image contract").
|
|
12
|
+
*
|
|
13
|
+
* The contract, in full:
|
|
14
|
+
*
|
|
15
|
+
* - **Surface.** `createCanvas(w, h)` is an `OffscreenCanvas`: the browser's
|
|
16
|
+
* own in a page or a worker, Skia's (`@napi-rs/canvas`, installed by the
|
|
17
|
+
* headless host) under `cascade run`/`render`/MCP. `context2d(canvas)` asks
|
|
18
|
+
* for a CPU-backed 2D context (`willReadFrequently`), which is what keeps
|
|
19
|
+
* Chrome on the same software Skia rasteriser the headless host uses, and
|
|
20
|
+
* is the right hint anyway for nodes that read pixels back.
|
|
21
|
+
* - **Pixels in.** `readImage(context, ref)` reads an `ImageRef`, `AssetRef`
|
|
22
|
+
* or project path through the node's declared `assets` capability and
|
|
23
|
+
* decodes it with `decodeImage` (`createImageBitmap` in both hosts).
|
|
24
|
+
* - **Pixels out.** `writeImage(context, canvas, name)` encodes PNG through
|
|
25
|
+
* `encodeImage` and stores it with `assets.write`, returning the `ImageRef`
|
|
26
|
+
* to set on an `image` output. The host decides where it lands
|
|
27
|
+
* (`.cascade-cache/<name>.<hash>.png` headless, an in-memory generated
|
|
28
|
+
* asset in an exported editor), so a node never names a path.
|
|
29
|
+
* - **Fonts.** `registerFont(family, bytes)` makes a face drawable by
|
|
30
|
+
* `ctx.font` in this host: `FontFace` added to the page's or worker's font
|
|
31
|
+
* set in a browser, Skia's `GlobalFonts` headless. Bytes come from the same
|
|
32
|
+
* `assets` capability, so a font is a project file like any other.
|
|
33
|
+
* - **Refusal, not fallback.** No canvas in this host, or no `assets`
|
|
34
|
+
* capability declared, is an error with one sentence saying which, never a
|
|
35
|
+
* silent blank image.
|
|
36
|
+
*
|
|
37
|
+
* A node using it declares `runsOn: 'portable'` and `capabilities: ['assets']`
|
|
38
|
+
* and imports nothing host-specific (`node:*`, `@napi-rs/canvas`, `document`).
|
|
39
|
+
* Then the same body cooks in Studio, in `cascade run`, and in an exported
|
|
40
|
+
* editor's worker.
|
|
41
|
+
*
|
|
42
|
+
* The headless host installs its font door under a global key, like
|
|
43
|
+
* `cascade/io`'s bridge, because the node's copy of this module and the CLI's
|
|
44
|
+
* copy are not always the same module instance.
|
|
45
|
+
*/
|
|
46
|
+
import { type ImagePixels } from './io.js';
|
|
47
|
+
export type { ImagePixels } from './io.js';
|
|
48
|
+
/** An image port value. Mirrors Cascade's core `ImageRef`. */
|
|
49
|
+
export interface ImageRef {
|
|
50
|
+
path: string;
|
|
51
|
+
size: [number, number];
|
|
52
|
+
channels: 'r' | 'a' | 'rg' | 'rgb' | 'rgba';
|
|
53
|
+
depth: 'u8' | 'u16' | 'f16' | 'f32';
|
|
54
|
+
space: 'srgb' | 'linear';
|
|
55
|
+
}
|
|
56
|
+
/** Anything naming stored bytes: an `ImageRef`, an `AssetRef`, or a project path. */
|
|
57
|
+
export type ImageSourceRef = string | {
|
|
58
|
+
readonly path: string;
|
|
59
|
+
};
|
|
60
|
+
/** The part of the `assets` capability these helpers use. */
|
|
61
|
+
export interface ImageAssets {
|
|
62
|
+
read(ref: {
|
|
63
|
+
path: string;
|
|
64
|
+
}, options: {
|
|
65
|
+
signal: unknown;
|
|
66
|
+
}): Promise<Uint8Array>;
|
|
67
|
+
write(data: Uint8Array, metadata: {
|
|
68
|
+
mediaType?: string;
|
|
69
|
+
suggestedName?: string;
|
|
70
|
+
}, options: {
|
|
71
|
+
signal: unknown;
|
|
72
|
+
}): Promise<{
|
|
73
|
+
path: string;
|
|
74
|
+
mediaType?: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** Optional: store an intermediate from its pixels (see core `AssetCapability.keepImage`). */
|
|
77
|
+
keepImage?(pixels: ImagePixels, metadata: {
|
|
78
|
+
suggestedName?: string;
|
|
79
|
+
}, options: {
|
|
80
|
+
signal: unknown;
|
|
81
|
+
}): Promise<{
|
|
82
|
+
path: string;
|
|
83
|
+
mediaType?: string;
|
|
84
|
+
}>;
|
|
85
|
+
/** Optional: the pixels behind a stored image while the host holds them in memory. */
|
|
86
|
+
imagePixels?(ref: {
|
|
87
|
+
path: string;
|
|
88
|
+
}): ImagePixels | undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The slice of a definition-v1 `NodeExecutionContext` these helpers need, so
|
|
92
|
+
* a node passes its own `context`. Typed loosely on purpose: a node that did
|
|
93
|
+
* not declare `assets` still type-checks here and gets a sentence at run time
|
|
94
|
+
* naming the missing declaration rather than a type error about generics.
|
|
95
|
+
*/
|
|
96
|
+
export interface ImageNodeContext {
|
|
97
|
+
readonly capabilities: object;
|
|
98
|
+
readonly signal?: unknown;
|
|
99
|
+
}
|
|
100
|
+
export type ImageType = 'image/png' | 'image/webp' | 'image/jpeg';
|
|
101
|
+
/** What a host gives `registerFont`. A browser realm needs none. */
|
|
102
|
+
export interface ImageHost {
|
|
103
|
+
/** For a `--verbose` line and for errors. */
|
|
104
|
+
readonly renderer: string;
|
|
105
|
+
registerFont(family: string, bytes: Uint8Array, descriptors: FontDescriptors): void | Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
export interface FontDescriptors {
|
|
108
|
+
/** CSS weight, `'400'`, `'bold'`. Headless reads the weight from the file. */
|
|
109
|
+
weight?: string;
|
|
110
|
+
/** `'normal'` or `'italic'`. Headless reads the style from the file. */
|
|
111
|
+
style?: string;
|
|
112
|
+
}
|
|
113
|
+
/** Install the headless host's font door. Returns the disposer. */
|
|
114
|
+
export declare function installImageHost(host: ImageHost | null): () => void;
|
|
115
|
+
/** Whether this host can draw. Never throws. */
|
|
116
|
+
export declare function canvasAvailable(): boolean;
|
|
117
|
+
/** A drawing surface of whole pixels, at least 1x1. */
|
|
118
|
+
export declare function createCanvas(width: number, height: number): OffscreenCanvas;
|
|
119
|
+
/**
|
|
120
|
+
* The canvas's 2D context, CPU-backed. `willReadFrequently` keeps a browser
|
|
121
|
+
* off the GPU rasteriser, so a node draws with the same software Skia in Chrome
|
|
122
|
+
* as headless, and `getImageData` does not stall on a readback.
|
|
123
|
+
*/
|
|
124
|
+
export declare function context2d(canvas: OffscreenCanvas): OffscreenCanvasRenderingContext2D;
|
|
125
|
+
/** `createCanvas` and `context2d` in one: `const { canvas, context } = surface(w, h)`. */
|
|
126
|
+
export declare function surface(width: number, height: number): {
|
|
127
|
+
canvas: OffscreenCanvas;
|
|
128
|
+
context: OffscreenCanvasRenderingContext2D;
|
|
129
|
+
};
|
|
130
|
+
/** Encoded bytes (PNG, JPEG, WebP) to something `drawImage` accepts. */
|
|
131
|
+
export declare function decodeImage(bytes: Uint8Array | ArrayBuffer | Blob): Promise<ImageBitmap>;
|
|
132
|
+
/** A canvas's pixels as encoded bytes, PNG unless asked otherwise. */
|
|
133
|
+
export declare function encodeImage(canvas: OffscreenCanvas, options?: {
|
|
134
|
+
type?: ImageType;
|
|
135
|
+
quality?: number;
|
|
136
|
+
}): Promise<Uint8Array>;
|
|
137
|
+
/**
|
|
138
|
+
* Make `family` drawable by `ctx.font` here. Idempotent per family and bytes.
|
|
139
|
+
* A browser realm (page or worker) uses `FontFace`; the headless host
|
|
140
|
+
* registers with Skia. A host with neither refuses.
|
|
141
|
+
*/
|
|
142
|
+
export declare function registerFont(family: string, bytes: Uint8Array | ArrayBuffer, descriptors?: FontDescriptors): Promise<void>;
|
|
143
|
+
/** A stored file's bytes through the node's `assets` capability: an image, a font, anything. */
|
|
144
|
+
export declare function readBytes(context: ImageNodeContext, ref: ImageSourceRef): Promise<Uint8Array>;
|
|
145
|
+
/** Read and decode an image input, an asset, or a project file. */
|
|
146
|
+
export declare function readImage(context: ImageNodeContext, ref: ImageSourceRef): Promise<ImageBitmap>;
|
|
147
|
+
/**
|
|
148
|
+
* Encode a canvas and store it; set the result on an `image` output. `name`
|
|
149
|
+
* is a suggestion (`context.nodeId + '.png'` keeps instances apart); the host
|
|
150
|
+
* picks the stored path.
|
|
151
|
+
*/
|
|
152
|
+
export declare function writeImage(context: ImageNodeContext, canvas: OffscreenCanvas, name: string, options?: {
|
|
153
|
+
type?: ImageType;
|
|
154
|
+
quality?: number;
|
|
155
|
+
space?: 'srgb' | 'linear';
|
|
156
|
+
}): Promise<ImageRef>;
|
package/dist/image.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// server/src/runtime/io.ts
|
|
2
|
+
function pixelsBitmap(pixels) {
|
|
3
|
+
const Canvas = globalThis.OffscreenCanvas;
|
|
4
|
+
if (!Canvas) throw new Error("cascade/io: this host has no OffscreenCanvas to hold the image");
|
|
5
|
+
const canvas = new Canvas(pixels.width, pixels.height);
|
|
6
|
+
const context = canvas.getContext("2d");
|
|
7
|
+
if (!context) throw new Error("cascade/io: the canvas has no 2D context");
|
|
8
|
+
const data = new ImageData(pixels.data, pixels.width, pixels.height);
|
|
9
|
+
context.putImageData(data, 0, 0);
|
|
10
|
+
const drawable = canvas;
|
|
11
|
+
drawable.close = () => {
|
|
12
|
+
};
|
|
13
|
+
drawable.naturalWidth = pixels.width;
|
|
14
|
+
drawable.naturalHeight = pixels.height;
|
|
15
|
+
return canvas;
|
|
16
|
+
}
|
|
17
|
+
function canvasPixels(source) {
|
|
18
|
+
const canvas = source;
|
|
19
|
+
if (typeof canvas?.getContext !== "function" || typeof canvas.width !== "number" || typeof canvas.height !== "number") return null;
|
|
20
|
+
if (!canvas.width || !canvas.height) return null;
|
|
21
|
+
const context = canvas.getContext("2d");
|
|
22
|
+
if (!context || typeof context.getImageData !== "function") return null;
|
|
23
|
+
const image = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
24
|
+
return { width: image.width, height: image.height, data: image.data };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// server/src/runtime/image.ts
|
|
28
|
+
var HOST_KEY = "__cascadeImageHost";
|
|
29
|
+
var NO_CANVAS = 'cascade/image: this host has no canvas. In a browser this needs OffscreenCanvas; headless, Cascade installs @napi-rs/canvas before a run (install it with "npm install @napi-rs/canvas" if it is missing).';
|
|
30
|
+
function installImageHost(host) {
|
|
31
|
+
const globals = globalThis;
|
|
32
|
+
const previous = globals[HOST_KEY];
|
|
33
|
+
globals[HOST_KEY] = host ?? void 0;
|
|
34
|
+
return () => {
|
|
35
|
+
globals[HOST_KEY] = previous;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function installedHost() {
|
|
39
|
+
const host = globalThis[HOST_KEY];
|
|
40
|
+
return host && typeof host.registerFont === "function" ? host : null;
|
|
41
|
+
}
|
|
42
|
+
function canvasConstructor() {
|
|
43
|
+
return globalThis.OffscreenCanvas;
|
|
44
|
+
}
|
|
45
|
+
function canvasAvailable() {
|
|
46
|
+
return typeof canvasConstructor() === "function";
|
|
47
|
+
}
|
|
48
|
+
function createCanvas(width, height) {
|
|
49
|
+
const Canvas = canvasConstructor();
|
|
50
|
+
if (!Canvas) throw new Error(NO_CANVAS);
|
|
51
|
+
return new Canvas(Math.max(1, Math.round(width)), Math.max(1, Math.round(height)));
|
|
52
|
+
}
|
|
53
|
+
function context2d(canvas) {
|
|
54
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
55
|
+
if (!context) throw new Error("cascade/image: the canvas has no 2D context");
|
|
56
|
+
return context;
|
|
57
|
+
}
|
|
58
|
+
function surface(width, height) {
|
|
59
|
+
const canvas = createCanvas(width, height);
|
|
60
|
+
return { canvas, context: context2d(canvas) };
|
|
61
|
+
}
|
|
62
|
+
async function decodeImage(bytes) {
|
|
63
|
+
const create = globalThis.createImageBitmap;
|
|
64
|
+
if (typeof create !== "function") throw new Error(NO_CANVAS);
|
|
65
|
+
const blob = bytes instanceof Blob ? bytes : new Blob([bytes]);
|
|
66
|
+
const image = await create(blob);
|
|
67
|
+
if (!image?.width || !image?.height) throw new Error("cascade/image: could not decode the image data");
|
|
68
|
+
return image;
|
|
69
|
+
}
|
|
70
|
+
async function encodeImage(canvas, options = {}) {
|
|
71
|
+
const type = options.type ?? "image/png";
|
|
72
|
+
if (typeof canvas.convertToBlob !== "function") throw new Error("cascade/image: this canvas cannot encode (no convertToBlob)");
|
|
73
|
+
const blob = await canvas.convertToBlob({ type, ...options.quality === void 0 ? {} : { quality: options.quality } });
|
|
74
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
75
|
+
}
|
|
76
|
+
var registered = /* @__PURE__ */ new Map();
|
|
77
|
+
function fingerprint(bytes) {
|
|
78
|
+
let hash = 2166136261;
|
|
79
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
80
|
+
hash ^= bytes[index];
|
|
81
|
+
hash = Math.imul(hash, 16777619);
|
|
82
|
+
}
|
|
83
|
+
return `${bytes.length}:${(hash >>> 0).toString(16)}`;
|
|
84
|
+
}
|
|
85
|
+
async function registerFont(family, bytes, descriptors = {}) {
|
|
86
|
+
if (!family.trim()) throw new Error("cascade/image: registerFont needs a family name");
|
|
87
|
+
const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
88
|
+
if (!data.byteLength) throw new Error(`cascade/image: the font for ${family} is empty`);
|
|
89
|
+
const host = installedHost();
|
|
90
|
+
const globals = globalThis;
|
|
91
|
+
const fonts = host ? null : globals.fonts ?? globals.document?.fonts;
|
|
92
|
+
const realm = host ? `headless:${host.renderer}` : fonts ?? globalThis;
|
|
93
|
+
const key = `${family}\0${descriptors.weight ?? ""}\0${descriptors.style ?? ""}\0${fingerprint(data)}`;
|
|
94
|
+
const seen = registered.get(realm) ?? /* @__PURE__ */ new Set();
|
|
95
|
+
if (seen.has(key)) return;
|
|
96
|
+
if (host) {
|
|
97
|
+
await host.registerFont(family, data, descriptors);
|
|
98
|
+
} else if (typeof globals.FontFace === "function" && fonts) {
|
|
99
|
+
const face = new globals.FontFace(family, data.slice().buffer, descriptors);
|
|
100
|
+
fonts.add(await face.load());
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error("cascade/image: this host cannot register fonts (no FontFace and no headless canvas host)");
|
|
103
|
+
}
|
|
104
|
+
seen.add(key);
|
|
105
|
+
registered.set(realm, seen);
|
|
106
|
+
}
|
|
107
|
+
function assetsOf(context) {
|
|
108
|
+
const assets = context.capabilities.assets;
|
|
109
|
+
if (!assets || typeof assets.read !== "function" || typeof assets.write !== "function") {
|
|
110
|
+
throw new Error("cascade/image: this node reads or writes images, so its definition must declare capabilities: ['assets']");
|
|
111
|
+
}
|
|
112
|
+
return assets;
|
|
113
|
+
}
|
|
114
|
+
function refOf(ref) {
|
|
115
|
+
const path = typeof ref === "string" ? ref : ref?.path;
|
|
116
|
+
if (typeof path !== "string" || !path) throw new Error("cascade/image: no image to read (an empty path or an unset image input)");
|
|
117
|
+
return { path };
|
|
118
|
+
}
|
|
119
|
+
var NEVER = { aborted: false, addEventListener() {
|
|
120
|
+
}, removeEventListener() {
|
|
121
|
+
} };
|
|
122
|
+
async function readBytes(context, ref) {
|
|
123
|
+
return assetsOf(context).read(refOf(ref), { signal: context.signal ?? NEVER });
|
|
124
|
+
}
|
|
125
|
+
async function readImage(context, ref) {
|
|
126
|
+
const pixels = assetsOf(context).imagePixels?.(refOf(ref));
|
|
127
|
+
if (pixels) return pixelsBitmap(pixels);
|
|
128
|
+
return decodeImage(await readBytes(context, ref));
|
|
129
|
+
}
|
|
130
|
+
async function writeImage(context, canvas, name, options = {}) {
|
|
131
|
+
const type = options.type ?? "image/png";
|
|
132
|
+
const assets = assetsOf(context);
|
|
133
|
+
const signal = context.signal ?? NEVER;
|
|
134
|
+
const pixels = type === "image/png" && assets.keepImage ? canvasPixels(canvas) : null;
|
|
135
|
+
const stored = pixels && assets.keepImage ? await assets.keepImage(pixels, { suggestedName: name }, { signal }) : await assets.write(
|
|
136
|
+
await encodeImage(canvas, { type, ...options.quality === void 0 ? {} : { quality: options.quality } }),
|
|
137
|
+
{ mediaType: type, suggestedName: name },
|
|
138
|
+
{ signal }
|
|
139
|
+
);
|
|
140
|
+
return {
|
|
141
|
+
path: stored.path,
|
|
142
|
+
size: [canvas.width, canvas.height],
|
|
143
|
+
channels: "rgba",
|
|
144
|
+
depth: "u8",
|
|
145
|
+
space: options.space ?? "srgb"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
canvasAvailable,
|
|
150
|
+
context2d,
|
|
151
|
+
createCanvas,
|
|
152
|
+
decodeImage,
|
|
153
|
+
encodeImage,
|
|
154
|
+
installImageHost,
|
|
155
|
+
readBytes,
|
|
156
|
+
readImage,
|
|
157
|
+
registerFont,
|
|
158
|
+
surface,
|
|
159
|
+
writeImage
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=image.js.map
|
package/dist/index.html
CHANGED
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
<!-- Pinterest browser extension: no Save buttons over canvases and previews -->
|
|
7
7
|
<meta name="pinterest" content="nopin">
|
|
8
8
|
<title>Cascade</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BWnrmIB9.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/legacy-CTEeXeqF.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/index-client-DqYmKstU.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/nodeTypeUtils-BDJsc2Ks.js">
|
|
13
|
-
<link rel="
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/coreTypes-DtnDATZ7.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/expressions-X-Z-GrgW.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/DefinitionNode-CaRTXQ_b.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/builder-BmBKcDix.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/preload-helper-Czpn1I53.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/studioImages-CAyH-0Pl.js">
|
|
19
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DIaxEONI.css">
|
|
14
20
|
</head>
|
|
15
21
|
<body>
|
|
16
22
|
<div id="app"></div>
|
package/dist/io.d.ts
CHANGED
|
@@ -33,6 +33,24 @@ export interface IoBridge {
|
|
|
33
33
|
write(path: string, data: Uint8Array): Promise<string>;
|
|
34
34
|
/** Host-owned display URL, valid until the host releases its stored asset. */
|
|
35
35
|
url?(path: string, options?: MediaOptions): string;
|
|
36
|
+
/**
|
|
37
|
+
* Store an intermediate image from its pixels and return the stored path,
|
|
38
|
+
* as `write` would. Optional. The headless host has it: it writes a fast
|
|
39
|
+
* lossless PNG, and in a run that keeps pixels it holds them in memory and
|
|
40
|
+
* writes behind (`pixels` below). Without it `saveImage` encodes and writes.
|
|
41
|
+
*/
|
|
42
|
+
keep?(path: string, pixels: ImagePixels): Promise<string>;
|
|
43
|
+
/** The pixels behind a path `keep` returned, while the host holds them. */
|
|
44
|
+
pixels?(path: string): ImagePixels | undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* An image's pixels in memory. Mirrors Cascade's core `ImagePixels`: 8-bit
|
|
48
|
+
* RGBA, sRGB, unpremultiplied, top row first — `ImageData`'s layout.
|
|
49
|
+
*/
|
|
50
|
+
export interface ImagePixels {
|
|
51
|
+
readonly width: number;
|
|
52
|
+
readonly height: number;
|
|
53
|
+
readonly data: Uint8ClampedArray;
|
|
36
54
|
}
|
|
37
55
|
/** Install the local transport. Returns a disposer, so a test can take it down. */
|
|
38
56
|
export declare function installIoBridge(bridge: IoBridge | null): () => void;
|
|
@@ -47,6 +65,11 @@ export declare function loadBytes(path: string, options?: MediaOptions): Promise
|
|
|
47
65
|
* to hand it. Pass a width explicitly to work at a smaller size on purpose.
|
|
48
66
|
*/
|
|
49
67
|
export declare function loadImage(path: string, options?: MediaOptions): Promise<HTMLImageElement>;
|
|
68
|
+
/**
|
|
69
|
+
* `ImagePixels` as a drawable: a canvas holding them, with an `ImageBitmap`'s
|
|
70
|
+
* `width`, `height` and `close()` (and an image's `naturalWidth`/`naturalHeight`).
|
|
71
|
+
*/
|
|
72
|
+
export declare function pixelsBitmap(pixels: ImagePixels): ImageBitmap;
|
|
50
73
|
/** Same, as an ImageBitmap — the right input for `texImage2D`. */
|
|
51
74
|
export declare function loadBitmap(path: string, options?: MediaOptions): Promise<ImageBitmap>;
|
|
52
75
|
/**
|
|
@@ -83,6 +106,12 @@ export interface ImageSource {
|
|
|
83
106
|
* node.out('path').setValue(out);
|
|
84
107
|
*/
|
|
85
108
|
export declare function saveImage(source: HTMLCanvasElement | OffscreenCanvas | Blob | ImageSource, path: string, options?: SaveOptions): Promise<string>;
|
|
109
|
+
/**
|
|
110
|
+
* A canvas's pixels as `ImagePixels`, or null for anything that is not a 2D
|
|
111
|
+
* canvas (a Blob, an encoder-only source, a WebGL canvas, whose `getContext('2d')`
|
|
112
|
+
* is null).
|
|
113
|
+
*/
|
|
114
|
+
export declare function canvasPixels(source: unknown): ImagePixels | null;
|
|
86
115
|
/** Write any bytes into the project cache and return the path. */
|
|
87
116
|
export declare function saveBytes(data: Blob | ArrayBuffer | Uint8Array, path: string): Promise<string>;
|
|
88
117
|
/**
|
package/dist/io.js
CHANGED
|
@@ -40,6 +40,8 @@ async function loadBytes(path, options = { raw: true }) {
|
|
|
40
40
|
return await response.arrayBuffer();
|
|
41
41
|
}
|
|
42
42
|
async function loadImage(path, options = { raw: true }) {
|
|
43
|
+
const held = heldBitmap(path);
|
|
44
|
+
if (held) return held;
|
|
43
45
|
const image = new Image();
|
|
44
46
|
const bridge = localIo();
|
|
45
47
|
let temporaryUrl;
|
|
@@ -65,7 +67,29 @@ async function loadImage(path, options = { raw: true }) {
|
|
|
65
67
|
if (temporaryUrl) URL.revokeObjectURL(temporaryUrl);
|
|
66
68
|
}
|
|
67
69
|
}
|
|
70
|
+
function heldBitmap(path) {
|
|
71
|
+
if (/^(https?:|data:|blob:)/.test(path)) return null;
|
|
72
|
+
const pixels = localIo()?.pixels?.(path.replace(/^\.?\//, ""));
|
|
73
|
+
return pixels ? pixelsBitmap(pixels) : null;
|
|
74
|
+
}
|
|
75
|
+
function pixelsBitmap(pixels) {
|
|
76
|
+
const Canvas = globalThis.OffscreenCanvas;
|
|
77
|
+
if (!Canvas) throw new Error("cascade/io: this host has no OffscreenCanvas to hold the image");
|
|
78
|
+
const canvas = new Canvas(pixels.width, pixels.height);
|
|
79
|
+
const context = canvas.getContext("2d");
|
|
80
|
+
if (!context) throw new Error("cascade/io: the canvas has no 2D context");
|
|
81
|
+
const data = new ImageData(pixels.data, pixels.width, pixels.height);
|
|
82
|
+
context.putImageData(data, 0, 0);
|
|
83
|
+
const drawable = canvas;
|
|
84
|
+
drawable.close = () => {
|
|
85
|
+
};
|
|
86
|
+
drawable.naturalWidth = pixels.width;
|
|
87
|
+
drawable.naturalHeight = pixels.height;
|
|
88
|
+
return canvas;
|
|
89
|
+
}
|
|
68
90
|
async function loadBitmap(path, options = { raw: true }) {
|
|
91
|
+
const held = heldBitmap(path);
|
|
92
|
+
if (held) return held;
|
|
69
93
|
const bridge = localIo();
|
|
70
94
|
if (bridge?.url) return createImageBitmap(await loadImage(path, options));
|
|
71
95
|
if (bridge) return createImageBitmap(new Blob([await loadBytes(path, options)]));
|
|
@@ -80,6 +104,11 @@ async function saveImage(source, path, options = {}) {
|
|
|
80
104
|
const format = options.format ?? "png";
|
|
81
105
|
const type = `image/${format}`;
|
|
82
106
|
let blob;
|
|
107
|
+
const bridge = localIo();
|
|
108
|
+
if (format === "png" && bridge?.keep) {
|
|
109
|
+
const pixels = canvasPixels(source);
|
|
110
|
+
if (pixels) return bridge.keep(path.replace(/^\.?\//, ""), pixels);
|
|
111
|
+
}
|
|
83
112
|
if (source instanceof Blob) {
|
|
84
113
|
blob = source;
|
|
85
114
|
} else if (typeof source.convertToBlob === "function") {
|
|
@@ -99,6 +128,15 @@ async function saveImage(source, path, options = {}) {
|
|
|
99
128
|
}
|
|
100
129
|
return saveBytes(blob, path);
|
|
101
130
|
}
|
|
131
|
+
function canvasPixels(source) {
|
|
132
|
+
const canvas = source;
|
|
133
|
+
if (typeof canvas?.getContext !== "function" || typeof canvas.width !== "number" || typeof canvas.height !== "number") return null;
|
|
134
|
+
if (!canvas.width || !canvas.height) return null;
|
|
135
|
+
const context = canvas.getContext("2d");
|
|
136
|
+
if (!context || typeof context.getImageData !== "function") return null;
|
|
137
|
+
const image = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
138
|
+
return { width: image.width, height: image.height, data: image.data };
|
|
139
|
+
}
|
|
102
140
|
async function saveBytes(data, path) {
|
|
103
141
|
const clean = path.replace(/^\.?\//, "");
|
|
104
142
|
const bridge = localIo();
|
|
@@ -302,6 +340,7 @@ async function textureToImage(ref, path, options = {}) {
|
|
|
302
340
|
}
|
|
303
341
|
export {
|
|
304
342
|
cachePath,
|
|
343
|
+
canvasPixels,
|
|
305
344
|
createTexture,
|
|
306
345
|
installIoBridge,
|
|
307
346
|
loadBitmap,
|
|
@@ -310,6 +349,7 @@ export {
|
|
|
310
349
|
loadFloats,
|
|
311
350
|
loadImage,
|
|
312
351
|
mediaUrl,
|
|
352
|
+
pixelsBitmap,
|
|
313
353
|
saveBytes,
|
|
314
354
|
saveFloats,
|
|
315
355
|
saveImage,
|
package/dist/player/app.js
CHANGED
|
@@ -12,7 +12,8 @@ import { installIoBridge } from "@field/cascade/io";
|
|
|
12
12
|
|
|
13
13
|
// src/browser/gpu.ts
|
|
14
14
|
import {
|
|
15
|
-
createGpuHost
|
|
15
|
+
createGpuHost,
|
|
16
|
+
requestDeviceWithAdapterLimits
|
|
16
17
|
} from "@field/cascade/runtime";
|
|
17
18
|
function createBrowserGpuHost() {
|
|
18
19
|
const gpu = globalThis.navigator?.gpu;
|
|
@@ -25,7 +26,7 @@ function createBrowserGpuHost() {
|
|
|
25
26
|
"No WebGPU adapter is available \u2014 the browser reports WebGPU but the system offers no GPU to render on."
|
|
26
27
|
);
|
|
27
28
|
}
|
|
28
|
-
const device = await adapter
|
|
29
|
+
const device = await requestDeviceWithAdapterLimits(adapter);
|
|
29
30
|
const info = adapter.info;
|
|
30
31
|
return {
|
|
31
32
|
device,
|
package/dist/player/editor.js
CHANGED
|
@@ -28,7 +28,8 @@ import { installIoBridge } from "@field/cascade/io";
|
|
|
28
28
|
|
|
29
29
|
// src/browser/gpu.ts
|
|
30
30
|
import {
|
|
31
|
-
createGpuHost
|
|
31
|
+
createGpuHost,
|
|
32
|
+
requestDeviceWithAdapterLimits
|
|
32
33
|
} from "@field/cascade/runtime";
|
|
33
34
|
function createBrowserGpuHost() {
|
|
34
35
|
const gpu = globalThis.navigator?.gpu;
|
|
@@ -41,7 +42,7 @@ function createBrowserGpuHost() {
|
|
|
41
42
|
"No WebGPU adapter is available \u2014 the browser reports WebGPU but the system offers no GPU to render on."
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
|
-
const device = await adapter
|
|
45
|
+
const device = await requestDeviceWithAdapterLimits(adapter);
|
|
45
46
|
const info = adapter.info;
|
|
46
47
|
return {
|
|
47
48
|
device,
|
|
@@ -564,6 +565,12 @@ function stepValue(value, direction, step, min, max) {
|
|
|
564
565
|
function notch(value, min, max) {
|
|
565
566
|
return max > min ? Math.round((value - min) / (max - min) * 10) : 0;
|
|
566
567
|
}
|
|
568
|
+
function capture(element, pointerId) {
|
|
569
|
+
try {
|
|
570
|
+
element.setPointerCapture?.(pointerId);
|
|
571
|
+
} catch {
|
|
572
|
+
}
|
|
573
|
+
}
|
|
567
574
|
function clamp(value, min, max) {
|
|
568
575
|
return Math.min(max, Math.max(min, value));
|
|
569
576
|
}
|
|
@@ -594,7 +601,7 @@ function attachZoom(surface, layer, enabled) {
|
|
|
594
601
|
};
|
|
595
602
|
surface.addEventListener("pointerdown", (event) => {
|
|
596
603
|
if (!enabled()) return;
|
|
597
|
-
surface
|
|
604
|
+
capture(surface, event.pointerId);
|
|
598
605
|
pointers.set(event.pointerId, local(event));
|
|
599
606
|
moved = false;
|
|
600
607
|
begin();
|
|
@@ -637,7 +644,7 @@ function attachSheet(grip, options) {
|
|
|
637
644
|
grip.addEventListener("pointerdown", (event) => {
|
|
638
645
|
if (!options.enabled()) return;
|
|
639
646
|
if (event.target?.closest?.("input,select,textarea,button")) return;
|
|
640
|
-
grip
|
|
647
|
+
capture(grip, event.pointerId);
|
|
641
648
|
drag = { id: event.pointerId, y: event.clientY, height: options.height(), time: performance.now(), velocity: 0, last: event.clientY, moved: false };
|
|
642
649
|
});
|
|
643
650
|
grip.addEventListener("pointermove", (event) => {
|
|
@@ -737,8 +744,11 @@ button.primary{background:var(--accent);color:var(--bg);border-color:var(--accen
|
|
|
737
744
|
|
|
738
745
|
/* Phones (.m, set from a media query): the preview on top, controls in a bottom sheet, one-row header. */
|
|
739
746
|
.ce-grip,.ce-more,.ce-menu,.ce-busy,.ce-step,.ce-toast{display:none}
|
|
740
|
-
.ce.
|
|
741
|
-
.ce.m
|
|
747
|
+
html.ce-phone,html.ce-phone body{overflow-x:hidden;max-width:100%;overscroll-behavior:none;touch-action:pan-x pan-y}
|
|
748
|
+
.ce.m{overflow:hidden;max-width:100vw;padding:0 env(safe-area-inset-right) 0 env(safe-area-inset-left)}
|
|
749
|
+
.ce.m,.ce.m button,.ce.m select,.ce.m input,.ce.m textarea,.ce.m summary,.ce.m label,.ce.m a{touch-action:manipulation}
|
|
750
|
+
.ce.m .ce-bar>*{min-width:0;max-width:100%}
|
|
751
|
+
.ce.m .ce-bar{flex-wrap:nowrap;overflow:hidden;max-width:100vw;gap:4px;min-height:52px;padding:calc(4px + env(safe-area-inset-top)) 4px 4px 16px}
|
|
742
752
|
.ce.m .ce-bar>*:not(h1):not(.ce-more):not(.ce-busy){display:none}
|
|
743
753
|
.ce.m .ce-bar h1{flex:1;min-width:0;font-size:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
744
754
|
.ce.m .ce-more{display:inline-grid;place-items:center;width:44px;height:44px;padding:0;border:0;background:transparent;font-size:24px;line-height:1;letter-spacing:1px}
|
|
@@ -801,6 +811,7 @@ button.primary{background:var(--accent);color:var(--bg);border-color:var(--accen
|
|
|
801
811
|
.ce.m .ce-toast button[hidden]{display:none}
|
|
802
812
|
`;
|
|
803
813
|
var PHONE = "(max-width: 760px), (pointer: coarse) and (max-height: 540px)";
|
|
814
|
+
var VIEWPORT = "width=device-width,initial-scale=1,viewport-fit=cover,maximum-scale=1";
|
|
804
815
|
async function startEditor(options) {
|
|
805
816
|
const document = options.document;
|
|
806
817
|
const editor = document.editor;
|
|
@@ -1049,9 +1060,13 @@ var EditorController = class {
|
|
|
1049
1060
|
toast.append(toastText, toastAction);
|
|
1050
1061
|
shell.append(bar, main, menu, toast);
|
|
1051
1062
|
this.root.replaceChildren(shell);
|
|
1052
|
-
|
|
1053
|
-
if (viewport
|
|
1054
|
-
viewport
|
|
1063
|
+
let viewport = d.querySelector('meta[name="viewport"]');
|
|
1064
|
+
if (!viewport) {
|
|
1065
|
+
viewport = d.createElement("meta");
|
|
1066
|
+
viewport.setAttribute("name", "viewport");
|
|
1067
|
+
d.head.append(viewport);
|
|
1068
|
+
}
|
|
1069
|
+
viewport.setAttribute("content", VIEWPORT);
|
|
1055
1070
|
viewer.addEventListener("dragover", (event) => event.preventDefault());
|
|
1056
1071
|
viewer.addEventListener("drop", (event) => {
|
|
1057
1072
|
event.preventDefault();
|
|
@@ -1671,6 +1686,15 @@ var EditorController = class {
|
|
|
1671
1686
|
query?.addEventListener?.("change", update);
|
|
1672
1687
|
win?.addEventListener("resize", update);
|
|
1673
1688
|
attachZoom(this.ui.surface, this.ui.output, () => this.mobile);
|
|
1689
|
+
const d = this.root.ownerDocument;
|
|
1690
|
+
const onPreview = (target) => target instanceof Node && this.ui.surface.contains(target);
|
|
1691
|
+
for (const type of ["gesturestart", "gesturechange", "gestureend"])
|
|
1692
|
+
d.addEventListener(type, (event) => {
|
|
1693
|
+
if (this.mobile) event.preventDefault();
|
|
1694
|
+
}, { passive: false });
|
|
1695
|
+
d.addEventListener("touchmove", (event) => {
|
|
1696
|
+
if (this.mobile && event.touches.length > 1 && !onPreview(event.target)) event.preventDefault();
|
|
1697
|
+
}, { passive: false });
|
|
1674
1698
|
attachSheet(this.ui.head, {
|
|
1675
1699
|
enabled: () => this.mobile && !this.landscape,
|
|
1676
1700
|
height: () => this.sheetHeight,
|
|
@@ -1688,6 +1712,7 @@ var EditorController = class {
|
|
|
1688
1712
|
this.mobile = mobile;
|
|
1689
1713
|
this.landscape = mobile && landscape;
|
|
1690
1714
|
this.ui.shell.classList.toggle("m", mobile);
|
|
1715
|
+
this.root.ownerDocument.documentElement.classList.toggle("ce-phone", mobile);
|
|
1691
1716
|
this.ui.shell.classList.toggle("land", this.landscape);
|
|
1692
1717
|
if (changed) {
|
|
1693
1718
|
this.placePeek(mobile);
|
|
@@ -1890,5 +1915,6 @@ async function recallFile(editor, id) {
|
|
|
1890
1915
|
}
|
|
1891
1916
|
export {
|
|
1892
1917
|
EditorController,
|
|
1918
|
+
VIEWPORT,
|
|
1893
1919
|
startEditor
|
|
1894
1920
|
};
|
|
@@ -5,7 +5,8 @@ import { installIoBridge as installIoBridge2 } from "@field/cascade/io";
|
|
|
5
5
|
|
|
6
6
|
// src/browser/gpu.ts
|
|
7
7
|
import {
|
|
8
|
-
createGpuHost
|
|
8
|
+
createGpuHost,
|
|
9
|
+
requestDeviceWithAdapterLimits
|
|
9
10
|
} from "@field/cascade/runtime";
|
|
10
11
|
function createBrowserGpuHost() {
|
|
11
12
|
const gpu = globalThis.navigator?.gpu;
|
|
@@ -18,7 +19,7 @@ function createBrowserGpuHost() {
|
|
|
18
19
|
"No WebGPU adapter is available \u2014 the browser reports WebGPU but the system offers no GPU to render on."
|
|
19
20
|
);
|
|
20
21
|
}
|
|
21
|
-
const device = await adapter
|
|
22
|
+
const device = await requestDeviceWithAdapterLimits(adapter);
|
|
22
23
|
const info = adapter.info;
|
|
23
24
|
return {
|
|
24
25
|
device,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { NodeExecutionContext } from "@field/cascade/contracts";
|
|
2
|
+
import type { equalsDefinition, notBlankDefinition, rangeDefinition, trueDefinition } from "./definitions.js";
|
|
3
|
+
export declare function executeRange({ nodeId, inputs, props, outputs }: NodeExecutionContext<typeof rangeDefinition>): void;
|
|
4
|
+
export declare function executeEquals({ nodeId, inputs, props, outputs }: NodeExecutionContext<typeof equalsDefinition>): void;
|
|
5
|
+
export declare function executeTrue({ nodeId, inputs, props, outputs }: NodeExecutionContext<typeof trueDefinition>): void;
|
|
6
|
+
export declare function executeNotBlank({ nodeId, inputs, props, outputs, capabilities, signal }: NodeExecutionContext<typeof notBlankDefinition>): Promise<void>;
|
|
7
|
+
export interface Pixels {
|
|
8
|
+
readonly width: number;
|
|
9
|
+
readonly height: number;
|
|
10
|
+
/** Channels per pixel: 1 grey, 2 grey+alpha, 3 RGB, 4 RGBA. */
|
|
11
|
+
readonly channels: number;
|
|
12
|
+
readonly data: ArrayLike<number>;
|
|
13
|
+
}
|
|
14
|
+
/** 'one flat colour', 'fully transparent', or null when the image has content. */
|
|
15
|
+
export declare function blankness(pixels: Pixels, tolerance?: number): string | null;
|
|
16
|
+
/** 8-bit non-interlaced PNG (grey, grey+alpha, RGB, RGBA) or binary PPM/PGM. */
|
|
17
|
+
export declare function decodeImage(bytes: Uint8Array): Promise<Pixels | null>;
|
|
18
|
+
//# sourceMappingURL=checks.d.ts.map
|