@field/cascade 0.10.0 → 0.11.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 +20 -3
- package/CHANGELOG.md +187 -0
- package/README.md +1 -2
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-4O7FQ__R.js} +4 -4
- package/dist/assets/{CodePanel-sY6sZa7B.js → CodePanel-BU-autmq.js} +2 -2
- package/dist/assets/{CustomNodeDialog-BRKnqZhp.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
- package/dist/assets/{OpenGraphDialog-ZsgL-iwr.js → OpenGraphDialog-BwbRlC0X.js} +1 -1
- package/dist/assets/{ProjectSettings-Cw17nNe3.js → ProjectSettings-DLOXLxcA.js} +1 -1
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-Bz8JxISH.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-D5Z0nJ5-.js} +1 -1
- package/dist/assets/{core-BICrEt0y.js → core-Yqi0Ni61.js} +1 -1
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-CxUzc4vp.js} +1 -1
- package/dist/assets/{definitions-DUww2Rt-.js → definitions-GreEMXLV.js} +1 -1
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-BNFb25CX.js} +1 -1
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-0ZDnWxeM.js} +1 -1
- package/dist/assets/geo-u0n_jOsD.js +1 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-Cy18MoaD.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-B-2Dx7nq.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUAc9B_d.js} +1 -1
- package/dist/assets/{image-DR4oeyJB.js → image-cAa-Nu9O.js} +1 -1
- package/dist/assets/{index-DyvDC6pS.js → index-5ZtDAuy2.js} +42 -41
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-DeQsDqP5.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-BiFwJVSk.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-BLoHH68a.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
- package/dist/assets/math-DioIQOPJ.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-CFXQVNfs.js} +1 -1
- package/dist/assets/pop-DC_TQG9t.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-Cf7S7p2E.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CMHBcG-g.js} +1 -1
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BEih0GDj.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-COEKYD_a.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-BNhAhMNn.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-CPiMpnPl.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.js → yaml-Bgp1ITLR.js} +1 -1
- package/dist/cli/Graph-ZJJTRQTM.js +2975 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-35D343DC.js +1447 -0
- package/dist/cli/chunk-36P5K3SD.js +631 -0
- package/dist/cli/chunk-3JTJHF7V.js +267 -0
- package/dist/cli/chunk-4UDFYF5G.js +132 -0
- package/dist/cli/chunk-53NMBJAO.js +35 -0
- package/dist/cli/chunk-A4OAMOZH.js +310 -0
- package/dist/cli/chunk-BIJO5OFS.js +71 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-D6BUOHUG.js +634 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-DUIUFVU2.js +241 -0
- package/dist/cli/chunk-EUIAC3U3.js +3006 -0
- package/dist/cli/chunk-FCDXJ6EK.js +2208 -0
- package/dist/cli/chunk-GFS6W3C5.js +308 -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-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-MIIEFUGG.js +623 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-POMMNKZZ.js +672 -0
- package/dist/cli/chunk-QBBKRNGY.js +90 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RLCAJBVR.js +224 -0
- package/dist/cli/chunk-RQZ4SX5R.js +1125 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-SGJAET2L.js +197 -0
- package/dist/cli/chunk-VVGSLVSF.js +151 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WIH4TEIS.js +104 -0
- package/dist/cli/chunk-WKURDVYP.js +1119 -0
- package/dist/cli/chunk-WPUDGEFY.js +20 -0
- package/dist/cli/chunk-XWDSKG7N.js +1301 -0
- package/dist/cli/chunk-YAT5Y25U.js +121 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/cliCommands-UQSFGEHF.js +3464 -0
- package/dist/cli/commands-Q23M26QM.js +1292 -0
- package/dist/cli/definitions-HF2FTHYU.js +32 -0
- package/dist/cli/doctorCommand-B7BPQMN6.js +76 -0
- package/dist/cli/edit-JHCPTQFT.js +28 -0
- package/dist/cli/editorCommands-UIF5YLUZ.js +33 -0
- package/dist/cli/encode-HAUZTYPT.js +17 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/geo-4RNK3QVN.js +43 -0
- package/dist/cli/graphCommands-6BMAKGQE.js +184 -0
- package/dist/cli/graphFile-VPSQ6OF7.js +286 -0
- package/dist/cli/headlessCanvas-5XLDZT7M.js +15 -0
- package/dist/cli/helpCommand-3VRE3KVO.js +155 -0
- package/dist/cli/index.js +100 -30756
- package/dist/cli/math-ZRMLKU2D.js +16 -0
- package/dist/cli/mcp-ZLEEMYGF.js +164 -0
- package/dist/cli/parity-KINARCP2.js +328 -0
- package/dist/cli/pop-USDFXB7H.js +39 -0
- package/dist/cli/project-K3QIPGBB.js +12 -0
- package/dist/cli/projectDefinition-DMTZN2NE.js +27 -0
- package/dist/cli/projectRuntime-Z5PJOH47.js +48 -0
- package/dist/cli/registerStandardNodes-S2VLMF6J.js +3081 -0
- package/dist/cli/render-YURAPPUI.js +436 -0
- package/dist/cli/runCommand-PI764LEG.js +198 -0
- package/dist/cli/runner-RA6UKEPD.js +37 -0
- package/dist/cli/runtime/image.ts +277 -0
- package/dist/cli/runtime/stage.ts +4 -1
- package/dist/cli/stageRunner-KUNIMQKJ.js +10 -0
- package/dist/cli/verbs-RCE3K4VQ.js +19 -0
- package/dist/cli/webBuild-GT4PZT5D.js +37 -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/image.d.ts +141 -0
- package/dist/image.js +127 -0
- package/dist/index.html +1 -1
- package/dist/player/editor.js +32 -7
- package/dist/runtime/builtins/headless.d.ts +19 -0
- package/dist/runtime/builtins/headless.js +25 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +226 -0
- package/dist/runtime/builtins/image/contactSheet.js +199 -0
- package/dist/runtime/builtins/image/index.d.ts +9 -0
- package/dist/runtime/builtins/image/index.js +9 -0
- package/dist/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +73 -2
- package/doc/EDITOR_EXPORT.md +11 -2
- package/doc/MCP.md +17 -1
- package/doc/NODE_REFERENCE.md +142 -0
- package/doc/PROJECT_AUTHORING.md +127 -7
- package/package.json +16 -4
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- 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,141 @@
|
|
|
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
|
+
/** An image port value. Mirrors Cascade's core `ImageRef`. */
|
|
47
|
+
export interface ImageRef {
|
|
48
|
+
path: string;
|
|
49
|
+
size: [number, number];
|
|
50
|
+
channels: 'r' | 'a' | 'rg' | 'rgb' | 'rgba';
|
|
51
|
+
depth: 'u8' | 'u16' | 'f16' | 'f32';
|
|
52
|
+
space: 'srgb' | 'linear';
|
|
53
|
+
}
|
|
54
|
+
/** Anything naming stored bytes: an `ImageRef`, an `AssetRef`, or a project path. */
|
|
55
|
+
export type ImageSourceRef = string | {
|
|
56
|
+
readonly path: string;
|
|
57
|
+
};
|
|
58
|
+
/** The part of the `assets` capability these helpers use. */
|
|
59
|
+
export interface ImageAssets {
|
|
60
|
+
read(ref: {
|
|
61
|
+
path: string;
|
|
62
|
+
}, options: {
|
|
63
|
+
signal: unknown;
|
|
64
|
+
}): Promise<Uint8Array>;
|
|
65
|
+
write(data: Uint8Array, metadata: {
|
|
66
|
+
mediaType?: string;
|
|
67
|
+
suggestedName?: string;
|
|
68
|
+
}, options: {
|
|
69
|
+
signal: unknown;
|
|
70
|
+
}): Promise<{
|
|
71
|
+
path: string;
|
|
72
|
+
mediaType?: string;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The slice of a definition-v1 `NodeExecutionContext` these helpers need, so
|
|
77
|
+
* a node passes its own `context`. Typed loosely on purpose: a node that did
|
|
78
|
+
* not declare `assets` still type-checks here and gets a sentence at run time
|
|
79
|
+
* naming the missing declaration rather than a type error about generics.
|
|
80
|
+
*/
|
|
81
|
+
export interface ImageNodeContext {
|
|
82
|
+
readonly capabilities: object;
|
|
83
|
+
readonly signal?: unknown;
|
|
84
|
+
}
|
|
85
|
+
export type ImageType = 'image/png' | 'image/webp' | 'image/jpeg';
|
|
86
|
+
/** What a host gives `registerFont`. A browser realm needs none. */
|
|
87
|
+
export interface ImageHost {
|
|
88
|
+
/** For a `--verbose` line and for errors. */
|
|
89
|
+
readonly renderer: string;
|
|
90
|
+
registerFont(family: string, bytes: Uint8Array, descriptors: FontDescriptors): void | Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
export interface FontDescriptors {
|
|
93
|
+
/** CSS weight, `'400'`, `'bold'`. Headless reads the weight from the file. */
|
|
94
|
+
weight?: string;
|
|
95
|
+
/** `'normal'` or `'italic'`. Headless reads the style from the file. */
|
|
96
|
+
style?: string;
|
|
97
|
+
}
|
|
98
|
+
/** Install the headless host's font door. Returns the disposer. */
|
|
99
|
+
export declare function installImageHost(host: ImageHost | null): () => void;
|
|
100
|
+
/** Whether this host can draw. Never throws. */
|
|
101
|
+
export declare function canvasAvailable(): boolean;
|
|
102
|
+
/** A drawing surface of whole pixels, at least 1x1. */
|
|
103
|
+
export declare function createCanvas(width: number, height: number): OffscreenCanvas;
|
|
104
|
+
/**
|
|
105
|
+
* The canvas's 2D context, CPU-backed. `willReadFrequently` keeps a browser
|
|
106
|
+
* off the GPU rasteriser, so a node draws with the same software Skia in Chrome
|
|
107
|
+
* as headless, and `getImageData` does not stall on a readback.
|
|
108
|
+
*/
|
|
109
|
+
export declare function context2d(canvas: OffscreenCanvas): OffscreenCanvasRenderingContext2D;
|
|
110
|
+
/** `createCanvas` and `context2d` in one: `const { canvas, context } = surface(w, h)`. */
|
|
111
|
+
export declare function surface(width: number, height: number): {
|
|
112
|
+
canvas: OffscreenCanvas;
|
|
113
|
+
context: OffscreenCanvasRenderingContext2D;
|
|
114
|
+
};
|
|
115
|
+
/** Encoded bytes (PNG, JPEG, WebP) to something `drawImage` accepts. */
|
|
116
|
+
export declare function decodeImage(bytes: Uint8Array | ArrayBuffer | Blob): Promise<ImageBitmap>;
|
|
117
|
+
/** A canvas's pixels as encoded bytes, PNG unless asked otherwise. */
|
|
118
|
+
export declare function encodeImage(canvas: OffscreenCanvas, options?: {
|
|
119
|
+
type?: ImageType;
|
|
120
|
+
quality?: number;
|
|
121
|
+
}): Promise<Uint8Array>;
|
|
122
|
+
/**
|
|
123
|
+
* Make `family` drawable by `ctx.font` here. Idempotent per family and bytes.
|
|
124
|
+
* A browser realm (page or worker) uses `FontFace`; the headless host
|
|
125
|
+
* registers with Skia. A host with neither refuses.
|
|
126
|
+
*/
|
|
127
|
+
export declare function registerFont(family: string, bytes: Uint8Array | ArrayBuffer, descriptors?: FontDescriptors): Promise<void>;
|
|
128
|
+
/** A stored file's bytes through the node's `assets` capability: an image, a font, anything. */
|
|
129
|
+
export declare function readBytes(context: ImageNodeContext, ref: ImageSourceRef): Promise<Uint8Array>;
|
|
130
|
+
/** Read and decode an image input, an asset, or a project file. */
|
|
131
|
+
export declare function readImage(context: ImageNodeContext, ref: ImageSourceRef): Promise<ImageBitmap>;
|
|
132
|
+
/**
|
|
133
|
+
* Encode a canvas and store it; set the result on an `image` output. `name`
|
|
134
|
+
* is a suggestion (`context.nodeId + '.png'` keeps instances apart); the host
|
|
135
|
+
* picks the stored path.
|
|
136
|
+
*/
|
|
137
|
+
export declare function writeImage(context: ImageNodeContext, canvas: OffscreenCanvas, name: string, options?: {
|
|
138
|
+
type?: ImageType;
|
|
139
|
+
quality?: number;
|
|
140
|
+
space?: 'srgb' | 'linear';
|
|
141
|
+
}): Promise<ImageRef>;
|
package/dist/image.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// server/src/runtime/image.ts
|
|
2
|
+
var HOST_KEY = "__cascadeImageHost";
|
|
3
|
+
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).';
|
|
4
|
+
function installImageHost(host) {
|
|
5
|
+
const globals = globalThis;
|
|
6
|
+
const previous = globals[HOST_KEY];
|
|
7
|
+
globals[HOST_KEY] = host ?? void 0;
|
|
8
|
+
return () => {
|
|
9
|
+
globals[HOST_KEY] = previous;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function installedHost() {
|
|
13
|
+
const host = globalThis[HOST_KEY];
|
|
14
|
+
return host && typeof host.registerFont === "function" ? host : null;
|
|
15
|
+
}
|
|
16
|
+
function canvasConstructor() {
|
|
17
|
+
return globalThis.OffscreenCanvas;
|
|
18
|
+
}
|
|
19
|
+
function canvasAvailable() {
|
|
20
|
+
return typeof canvasConstructor() === "function";
|
|
21
|
+
}
|
|
22
|
+
function createCanvas(width, height) {
|
|
23
|
+
const Canvas = canvasConstructor();
|
|
24
|
+
if (!Canvas) throw new Error(NO_CANVAS);
|
|
25
|
+
return new Canvas(Math.max(1, Math.round(width)), Math.max(1, Math.round(height)));
|
|
26
|
+
}
|
|
27
|
+
function context2d(canvas) {
|
|
28
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
29
|
+
if (!context) throw new Error("cascade/image: the canvas has no 2D context");
|
|
30
|
+
return context;
|
|
31
|
+
}
|
|
32
|
+
function surface(width, height) {
|
|
33
|
+
const canvas = createCanvas(width, height);
|
|
34
|
+
return { canvas, context: context2d(canvas) };
|
|
35
|
+
}
|
|
36
|
+
async function decodeImage(bytes) {
|
|
37
|
+
const create = globalThis.createImageBitmap;
|
|
38
|
+
if (typeof create !== "function") throw new Error(NO_CANVAS);
|
|
39
|
+
const blob = bytes instanceof Blob ? bytes : new Blob([bytes]);
|
|
40
|
+
const image = await create(blob);
|
|
41
|
+
if (!image?.width || !image?.height) throw new Error("cascade/image: could not decode the image data");
|
|
42
|
+
return image;
|
|
43
|
+
}
|
|
44
|
+
async function encodeImage(canvas, options = {}) {
|
|
45
|
+
const type = options.type ?? "image/png";
|
|
46
|
+
if (typeof canvas.convertToBlob !== "function") throw new Error("cascade/image: this canvas cannot encode (no convertToBlob)");
|
|
47
|
+
const blob = await canvas.convertToBlob({ type, ...options.quality === void 0 ? {} : { quality: options.quality } });
|
|
48
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
49
|
+
}
|
|
50
|
+
var registered = /* @__PURE__ */ new Map();
|
|
51
|
+
function fingerprint(bytes) {
|
|
52
|
+
let hash = 2166136261;
|
|
53
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
54
|
+
hash ^= bytes[index];
|
|
55
|
+
hash = Math.imul(hash, 16777619);
|
|
56
|
+
}
|
|
57
|
+
return `${bytes.length}:${(hash >>> 0).toString(16)}`;
|
|
58
|
+
}
|
|
59
|
+
async function registerFont(family, bytes, descriptors = {}) {
|
|
60
|
+
if (!family.trim()) throw new Error("cascade/image: registerFont needs a family name");
|
|
61
|
+
const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
62
|
+
if (!data.byteLength) throw new Error(`cascade/image: the font for ${family} is empty`);
|
|
63
|
+
const host = installedHost();
|
|
64
|
+
const globals = globalThis;
|
|
65
|
+
const fonts = host ? null : globals.fonts ?? globals.document?.fonts;
|
|
66
|
+
const realm = host ? `headless:${host.renderer}` : fonts ?? globalThis;
|
|
67
|
+
const key = `${family}\0${descriptors.weight ?? ""}\0${descriptors.style ?? ""}\0${fingerprint(data)}`;
|
|
68
|
+
const seen = registered.get(realm) ?? /* @__PURE__ */ new Set();
|
|
69
|
+
if (seen.has(key)) return;
|
|
70
|
+
if (host) {
|
|
71
|
+
await host.registerFont(family, data, descriptors);
|
|
72
|
+
} else if (typeof globals.FontFace === "function" && fonts) {
|
|
73
|
+
const face = new globals.FontFace(family, data.slice().buffer, descriptors);
|
|
74
|
+
fonts.add(await face.load());
|
|
75
|
+
} else {
|
|
76
|
+
throw new Error("cascade/image: this host cannot register fonts (no FontFace and no headless canvas host)");
|
|
77
|
+
}
|
|
78
|
+
seen.add(key);
|
|
79
|
+
registered.set(realm, seen);
|
|
80
|
+
}
|
|
81
|
+
function assetsOf(context) {
|
|
82
|
+
const assets = context.capabilities.assets;
|
|
83
|
+
if (!assets || typeof assets.read !== "function" || typeof assets.write !== "function") {
|
|
84
|
+
throw new Error("cascade/image: this node reads or writes images, so its definition must declare capabilities: ['assets']");
|
|
85
|
+
}
|
|
86
|
+
return assets;
|
|
87
|
+
}
|
|
88
|
+
function refOf(ref) {
|
|
89
|
+
const path = typeof ref === "string" ? ref : ref?.path;
|
|
90
|
+
if (typeof path !== "string" || !path) throw new Error("cascade/image: no image to read (an empty path or an unset image input)");
|
|
91
|
+
return { path };
|
|
92
|
+
}
|
|
93
|
+
var NEVER = { aborted: false, addEventListener() {
|
|
94
|
+
}, removeEventListener() {
|
|
95
|
+
} };
|
|
96
|
+
async function readBytes(context, ref) {
|
|
97
|
+
return assetsOf(context).read(refOf(ref), { signal: context.signal ?? NEVER });
|
|
98
|
+
}
|
|
99
|
+
async function readImage(context, ref) {
|
|
100
|
+
return decodeImage(await readBytes(context, ref));
|
|
101
|
+
}
|
|
102
|
+
async function writeImage(context, canvas, name, options = {}) {
|
|
103
|
+
const type = options.type ?? "image/png";
|
|
104
|
+
const bytes = await encodeImage(canvas, { type, ...options.quality === void 0 ? {} : { quality: options.quality } });
|
|
105
|
+
const stored = await assetsOf(context).write(bytes, { mediaType: type, suggestedName: name }, { signal: context.signal ?? NEVER });
|
|
106
|
+
return {
|
|
107
|
+
path: stored.path,
|
|
108
|
+
size: [canvas.width, canvas.height],
|
|
109
|
+
channels: "rgba",
|
|
110
|
+
depth: "u8",
|
|
111
|
+
space: options.space ?? "srgb"
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
canvasAvailable,
|
|
116
|
+
context2d,
|
|
117
|
+
createCanvas,
|
|
118
|
+
decodeImage,
|
|
119
|
+
encodeImage,
|
|
120
|
+
installImageHost,
|
|
121
|
+
readBytes,
|
|
122
|
+
readImage,
|
|
123
|
+
registerFont,
|
|
124
|
+
surface,
|
|
125
|
+
writeImage
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=image.js.map
|
package/dist/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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-5ZtDAuy2.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">
|
package/dist/player/editor.js
CHANGED
|
@@ -564,6 +564,12 @@ function stepValue(value, direction, step, min, max) {
|
|
|
564
564
|
function notch(value, min, max) {
|
|
565
565
|
return max > min ? Math.round((value - min) / (max - min) * 10) : 0;
|
|
566
566
|
}
|
|
567
|
+
function capture(element, pointerId) {
|
|
568
|
+
try {
|
|
569
|
+
element.setPointerCapture?.(pointerId);
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
}
|
|
567
573
|
function clamp(value, min, max) {
|
|
568
574
|
return Math.min(max, Math.max(min, value));
|
|
569
575
|
}
|
|
@@ -594,7 +600,7 @@ function attachZoom(surface, layer, enabled) {
|
|
|
594
600
|
};
|
|
595
601
|
surface.addEventListener("pointerdown", (event) => {
|
|
596
602
|
if (!enabled()) return;
|
|
597
|
-
surface
|
|
603
|
+
capture(surface, event.pointerId);
|
|
598
604
|
pointers.set(event.pointerId, local(event));
|
|
599
605
|
moved = false;
|
|
600
606
|
begin();
|
|
@@ -637,7 +643,7 @@ function attachSheet(grip, options) {
|
|
|
637
643
|
grip.addEventListener("pointerdown", (event) => {
|
|
638
644
|
if (!options.enabled()) return;
|
|
639
645
|
if (event.target?.closest?.("input,select,textarea,button")) return;
|
|
640
|
-
grip
|
|
646
|
+
capture(grip, event.pointerId);
|
|
641
647
|
drag = { id: event.pointerId, y: event.clientY, height: options.height(), time: performance.now(), velocity: 0, last: event.clientY, moved: false };
|
|
642
648
|
});
|
|
643
649
|
grip.addEventListener("pointermove", (event) => {
|
|
@@ -737,8 +743,11 @@ button.primary{background:var(--accent);color:var(--bg);border-color:var(--accen
|
|
|
737
743
|
|
|
738
744
|
/* Phones (.m, set from a media query): the preview on top, controls in a bottom sheet, one-row header. */
|
|
739
745
|
.ce-grip,.ce-more,.ce-menu,.ce-busy,.ce-step,.ce-toast{display:none}
|
|
740
|
-
.ce.
|
|
741
|
-
.ce.m
|
|
746
|
+
html.ce-phone,html.ce-phone body{overflow-x:hidden;max-width:100%;overscroll-behavior:none;touch-action:pan-x pan-y}
|
|
747
|
+
.ce.m{overflow:hidden;max-width:100vw;padding:0 env(safe-area-inset-right) 0 env(safe-area-inset-left)}
|
|
748
|
+
.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}
|
|
749
|
+
.ce.m .ce-bar>*{min-width:0;max-width:100%}
|
|
750
|
+
.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
751
|
.ce.m .ce-bar>*:not(h1):not(.ce-more):not(.ce-busy){display:none}
|
|
743
752
|
.ce.m .ce-bar h1{flex:1;min-width:0;font-size:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
744
753
|
.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 +810,7 @@ button.primary{background:var(--accent);color:var(--bg);border-color:var(--accen
|
|
|
801
810
|
.ce.m .ce-toast button[hidden]{display:none}
|
|
802
811
|
`;
|
|
803
812
|
var PHONE = "(max-width: 760px), (pointer: coarse) and (max-height: 540px)";
|
|
813
|
+
var VIEWPORT = "width=device-width,initial-scale=1,viewport-fit=cover,maximum-scale=1";
|
|
804
814
|
async function startEditor(options) {
|
|
805
815
|
const document = options.document;
|
|
806
816
|
const editor = document.editor;
|
|
@@ -1049,9 +1059,13 @@ var EditorController = class {
|
|
|
1049
1059
|
toast.append(toastText, toastAction);
|
|
1050
1060
|
shell.append(bar, main, menu, toast);
|
|
1051
1061
|
this.root.replaceChildren(shell);
|
|
1052
|
-
|
|
1053
|
-
if (viewport
|
|
1054
|
-
viewport
|
|
1062
|
+
let viewport = d.querySelector('meta[name="viewport"]');
|
|
1063
|
+
if (!viewport) {
|
|
1064
|
+
viewport = d.createElement("meta");
|
|
1065
|
+
viewport.setAttribute("name", "viewport");
|
|
1066
|
+
d.head.append(viewport);
|
|
1067
|
+
}
|
|
1068
|
+
viewport.setAttribute("content", VIEWPORT);
|
|
1055
1069
|
viewer.addEventListener("dragover", (event) => event.preventDefault());
|
|
1056
1070
|
viewer.addEventListener("drop", (event) => {
|
|
1057
1071
|
event.preventDefault();
|
|
@@ -1671,6 +1685,15 @@ var EditorController = class {
|
|
|
1671
1685
|
query?.addEventListener?.("change", update);
|
|
1672
1686
|
win?.addEventListener("resize", update);
|
|
1673
1687
|
attachZoom(this.ui.surface, this.ui.output, () => this.mobile);
|
|
1688
|
+
const d = this.root.ownerDocument;
|
|
1689
|
+
const onPreview = (target) => target instanceof Node && this.ui.surface.contains(target);
|
|
1690
|
+
for (const type of ["gesturestart", "gesturechange", "gestureend"])
|
|
1691
|
+
d.addEventListener(type, (event) => {
|
|
1692
|
+
if (this.mobile) event.preventDefault();
|
|
1693
|
+
}, { passive: false });
|
|
1694
|
+
d.addEventListener("touchmove", (event) => {
|
|
1695
|
+
if (this.mobile && event.touches.length > 1 && !onPreview(event.target)) event.preventDefault();
|
|
1696
|
+
}, { passive: false });
|
|
1674
1697
|
attachSheet(this.ui.head, {
|
|
1675
1698
|
enabled: () => this.mobile && !this.landscape,
|
|
1676
1699
|
height: () => this.sheetHeight,
|
|
@@ -1688,6 +1711,7 @@ var EditorController = class {
|
|
|
1688
1711
|
this.mobile = mobile;
|
|
1689
1712
|
this.landscape = mobile && landscape;
|
|
1690
1713
|
this.ui.shell.classList.toggle("m", mobile);
|
|
1714
|
+
this.root.ownerDocument.documentElement.classList.toggle("ce-phone", mobile);
|
|
1691
1715
|
this.ui.shell.classList.toggle("land", this.landscape);
|
|
1692
1716
|
if (changed) {
|
|
1693
1717
|
this.placePeek(mobile);
|
|
@@ -1890,5 +1914,6 @@ async function recallFile(editor, id) {
|
|
|
1890
1914
|
}
|
|
1891
1915
|
export {
|
|
1892
1916
|
EditorController,
|
|
1917
|
+
VIEWPORT,
|
|
1893
1918
|
startEditor
|
|
1894
1919
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DefinitionNodeRegistration } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Built-ins the headless host knows and Studio does not yet: today only
|
|
4
|
+
* `cascade.image.ContactSheet`.
|
|
5
|
+
*
|
|
6
|
+
* A separate list rather than a line in `builtinNodeRegistrations` because
|
|
7
|
+
* that list is in Studio's first-paint bundle (the runtime and the Definition
|
|
8
|
+
* panel both import it), and the bundle budget has no room for a node Studio
|
|
9
|
+
* does not register. The CLI passes these to the runtime as ordinary
|
|
10
|
+
* registrations. When Studio registers the image nodes, they move into
|
|
11
|
+
* `index.ts` and this file goes.
|
|
12
|
+
*/
|
|
13
|
+
export declare const headlessOnlyNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
14
|
+
/** Every built-in the headless host can run. */
|
|
15
|
+
export declare const headlessNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
16
|
+
/** A headless-only built-in, which the runtime does not know unless it is passed in. */
|
|
17
|
+
export declare function headlessOnlyNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
|
|
18
|
+
export declare function headlessNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
|
|
19
|
+
//# sourceMappingURL=headless.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { builtinNodeRegistration, builtinNodeRegistrations } from "./index.js";
|
|
2
|
+
import { imageNodeRegistrations } from "./image/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Built-ins the headless host knows and Studio does not yet: today only
|
|
5
|
+
* `cascade.image.ContactSheet`.
|
|
6
|
+
*
|
|
7
|
+
* A separate list rather than a line in `builtinNodeRegistrations` because
|
|
8
|
+
* that list is in Studio's first-paint bundle (the runtime and the Definition
|
|
9
|
+
* panel both import it), and the bundle budget has no room for a node Studio
|
|
10
|
+
* does not register. The CLI passes these to the runtime as ordinary
|
|
11
|
+
* registrations. When Studio registers the image nodes, they move into
|
|
12
|
+
* `index.ts` and this file goes.
|
|
13
|
+
*/
|
|
14
|
+
export const headlessOnlyNodeRegistrations = Object.freeze([...imageNodeRegistrations]);
|
|
15
|
+
/** Every built-in the headless host can run. */
|
|
16
|
+
export const headlessNodeRegistrations = Object.freeze([...builtinNodeRegistrations, ...headlessOnlyNodeRegistrations]);
|
|
17
|
+
const headlessOnlyById = new Map(headlessOnlyNodeRegistrations.map((registration) => [registration.moduleId, registration]));
|
|
18
|
+
/** A headless-only built-in, which the runtime does not know unless it is passed in. */
|
|
19
|
+
export function headlessOnlyNodeRegistration(moduleId) {
|
|
20
|
+
return headlessOnlyById.get(moduleId);
|
|
21
|
+
}
|
|
22
|
+
export function headlessNodeRegistration(moduleId) {
|
|
23
|
+
return builtinNodeRegistration(moduleId) ?? headlessOnlyById.get(moduleId);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=headless.js.map
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { NodeExecutionContext } from "@field/cascade/contracts";
|
|
2
|
+
/**
|
|
3
|
+
* `cascade.image.ContactSheet` — several images as one labelled grid.
|
|
4
|
+
*
|
|
5
|
+
* The selection loop is a ladder of three to five options and a pick, and the
|
|
6
|
+
* pick is made by looking at them side by side. So the sheet is a node rather
|
|
7
|
+
* than a script beside the graph: a graph can end in one, and `cascade ladder`
|
|
8
|
+
* lays out its variants with the same layout and drawing code below.
|
|
9
|
+
*
|
|
10
|
+
* `portable` with `assets`, like `cascade.geo.Render`: it reads each image
|
|
11
|
+
* through the asset capability, decodes it with `createImageBitmap` and draws
|
|
12
|
+
* on an `OffscreenCanvas`, which both hosts supply (the headless one through
|
|
13
|
+
* @napi-rs/canvas). It lives in `cascade.image.*` beside Studio's image
|
|
14
|
+
* library, as the first definition-v1 node there; Studio does not register it
|
|
15
|
+
* yet, so today it runs headless and is listed in `../headless.ts` rather
|
|
16
|
+
* than in the built-ins Studio bundles.
|
|
17
|
+
*/
|
|
18
|
+
export declare const contactSheetDefinition: {
|
|
19
|
+
readonly apiVersion: 1;
|
|
20
|
+
readonly label: "Contact Sheet";
|
|
21
|
+
readonly description: "Lay images out as one labelled grid.";
|
|
22
|
+
readonly icon: "LayoutGrid";
|
|
23
|
+
readonly runsOn: "portable";
|
|
24
|
+
readonly capabilities: readonly ["assets"];
|
|
25
|
+
readonly inputs: {
|
|
26
|
+
readonly images: {
|
|
27
|
+
readonly kind: "data";
|
|
28
|
+
readonly type: "image";
|
|
29
|
+
readonly variadic: true;
|
|
30
|
+
};
|
|
31
|
+
/** More images after the wired ones: image values or project-relative paths. */
|
|
32
|
+
readonly list: {
|
|
33
|
+
readonly kind: "data";
|
|
34
|
+
readonly type: "array";
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
readonly outputs: {
|
|
38
|
+
readonly image: {
|
|
39
|
+
readonly kind: "data";
|
|
40
|
+
readonly type: "image";
|
|
41
|
+
};
|
|
42
|
+
readonly asset: {
|
|
43
|
+
readonly kind: "data";
|
|
44
|
+
readonly type: "asset";
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
readonly props: {
|
|
48
|
+
readonly columns: {
|
|
49
|
+
readonly type: "int";
|
|
50
|
+
readonly default: 0;
|
|
51
|
+
readonly min: 0;
|
|
52
|
+
readonly max: 64;
|
|
53
|
+
readonly description: "0: as square as the count allows.";
|
|
54
|
+
};
|
|
55
|
+
readonly gap: {
|
|
56
|
+
readonly type: "int";
|
|
57
|
+
readonly default: 16;
|
|
58
|
+
readonly min: 0;
|
|
59
|
+
readonly max: 512;
|
|
60
|
+
};
|
|
61
|
+
readonly background: {
|
|
62
|
+
readonly type: "color";
|
|
63
|
+
readonly default: readonly [1, 1, 1, 1];
|
|
64
|
+
readonly label: "Background";
|
|
65
|
+
};
|
|
66
|
+
readonly cellWidth: {
|
|
67
|
+
readonly type: "int";
|
|
68
|
+
readonly default: 0;
|
|
69
|
+
readonly min: 0;
|
|
70
|
+
readonly max: 8192;
|
|
71
|
+
readonly description: "0: images at their own size. Otherwise each is scaled to fit this width.";
|
|
72
|
+
};
|
|
73
|
+
readonly labels: {
|
|
74
|
+
readonly type: "string";
|
|
75
|
+
readonly default: "";
|
|
76
|
+
readonly description: "One label per line, in image order.";
|
|
77
|
+
};
|
|
78
|
+
readonly labelSize: {
|
|
79
|
+
readonly type: "int";
|
|
80
|
+
readonly default: 14;
|
|
81
|
+
readonly min: 6;
|
|
82
|
+
readonly max: 128;
|
|
83
|
+
};
|
|
84
|
+
readonly labelColor: {
|
|
85
|
+
readonly type: "color";
|
|
86
|
+
readonly default: readonly [0.1, 0.1, 0.1, 1];
|
|
87
|
+
readonly label: "Label Colour";
|
|
88
|
+
};
|
|
89
|
+
readonly filename: {
|
|
90
|
+
readonly type: "string";
|
|
91
|
+
readonly default: "contact-sheet.png";
|
|
92
|
+
readonly label: "Filename";
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export interface ContactSheetOptions {
|
|
97
|
+
readonly columns?: number;
|
|
98
|
+
readonly gap?: number;
|
|
99
|
+
readonly cellWidth?: number;
|
|
100
|
+
readonly labels?: readonly string[];
|
|
101
|
+
readonly labelSize?: number;
|
|
102
|
+
/** RGBA, 0 to 1. */
|
|
103
|
+
readonly background?: readonly number[];
|
|
104
|
+
readonly labelColor?: readonly number[];
|
|
105
|
+
}
|
|
106
|
+
export interface SheetCell {
|
|
107
|
+
readonly x: number;
|
|
108
|
+
readonly y: number;
|
|
109
|
+
readonly width: number;
|
|
110
|
+
readonly height: number;
|
|
111
|
+
/** Top-left of the label line. */
|
|
112
|
+
readonly labelX: number;
|
|
113
|
+
readonly labelY: number;
|
|
114
|
+
}
|
|
115
|
+
export interface SheetLayout {
|
|
116
|
+
readonly width: number;
|
|
117
|
+
readonly height: number;
|
|
118
|
+
readonly columns: number;
|
|
119
|
+
readonly rows: number;
|
|
120
|
+
readonly cell: readonly [number, number];
|
|
121
|
+
readonly labelHeight: number;
|
|
122
|
+
readonly cells: readonly SheetCell[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Where each image goes. By default every cell is as wide as the widest image
|
|
126
|
+
* and as tall as the tallest, and images keep their size. With `cellWidth`,
|
|
127
|
+
* cells are that wide and as tall as the tallest image needs at that width,
|
|
128
|
+
* and each image is scaled to fit. Either way an image is centred in its cell
|
|
129
|
+
* and never cropped, in whole pixels, so a sheet is the same bytes every run.
|
|
130
|
+
*/
|
|
131
|
+
export declare function contactSheetLayout(sizes: readonly (readonly [number, number])[], options?: {
|
|
132
|
+
columns?: number;
|
|
133
|
+
gap?: number;
|
|
134
|
+
cellWidth?: number;
|
|
135
|
+
labelHeight?: number;
|
|
136
|
+
}): SheetLayout;
|
|
137
|
+
/** Encoded images (PNG, JPEG, WebP) in, one PNG sheet out. */
|
|
138
|
+
export declare function composeContactSheet(images: readonly Uint8Array[], options?: ContactSheetOptions): Promise<{
|
|
139
|
+
bytes: Uint8Array;
|
|
140
|
+
layout: SheetLayout;
|
|
141
|
+
}>;
|
|
142
|
+
export declare function executeContactSheet(context: NodeExecutionContext<typeof contactSheetDefinition>): Promise<void>;
|
|
143
|
+
export declare const contactSheetRegistration: {
|
|
144
|
+
kind: "definition-v1";
|
|
145
|
+
moduleId: string;
|
|
146
|
+
definition: {
|
|
147
|
+
readonly apiVersion: 1;
|
|
148
|
+
readonly label: "Contact Sheet";
|
|
149
|
+
readonly description: "Lay images out as one labelled grid.";
|
|
150
|
+
readonly icon: "LayoutGrid";
|
|
151
|
+
readonly runsOn: "portable";
|
|
152
|
+
readonly capabilities: readonly ["assets"];
|
|
153
|
+
readonly inputs: {
|
|
154
|
+
readonly images: {
|
|
155
|
+
readonly kind: "data";
|
|
156
|
+
readonly type: "image";
|
|
157
|
+
readonly variadic: true;
|
|
158
|
+
};
|
|
159
|
+
/** More images after the wired ones: image values or project-relative paths. */
|
|
160
|
+
readonly list: {
|
|
161
|
+
readonly kind: "data";
|
|
162
|
+
readonly type: "array";
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
readonly outputs: {
|
|
166
|
+
readonly image: {
|
|
167
|
+
readonly kind: "data";
|
|
168
|
+
readonly type: "image";
|
|
169
|
+
};
|
|
170
|
+
readonly asset: {
|
|
171
|
+
readonly kind: "data";
|
|
172
|
+
readonly type: "asset";
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
readonly props: {
|
|
176
|
+
readonly columns: {
|
|
177
|
+
readonly type: "int";
|
|
178
|
+
readonly default: 0;
|
|
179
|
+
readonly min: 0;
|
|
180
|
+
readonly max: 64;
|
|
181
|
+
readonly description: "0: as square as the count allows.";
|
|
182
|
+
};
|
|
183
|
+
readonly gap: {
|
|
184
|
+
readonly type: "int";
|
|
185
|
+
readonly default: 16;
|
|
186
|
+
readonly min: 0;
|
|
187
|
+
readonly max: 512;
|
|
188
|
+
};
|
|
189
|
+
readonly background: {
|
|
190
|
+
readonly type: "color";
|
|
191
|
+
readonly default: readonly [1, 1, 1, 1];
|
|
192
|
+
readonly label: "Background";
|
|
193
|
+
};
|
|
194
|
+
readonly cellWidth: {
|
|
195
|
+
readonly type: "int";
|
|
196
|
+
readonly default: 0;
|
|
197
|
+
readonly min: 0;
|
|
198
|
+
readonly max: 8192;
|
|
199
|
+
readonly description: "0: images at their own size. Otherwise each is scaled to fit this width.";
|
|
200
|
+
};
|
|
201
|
+
readonly labels: {
|
|
202
|
+
readonly type: "string";
|
|
203
|
+
readonly default: "";
|
|
204
|
+
readonly description: "One label per line, in image order.";
|
|
205
|
+
};
|
|
206
|
+
readonly labelSize: {
|
|
207
|
+
readonly type: "int";
|
|
208
|
+
readonly default: 14;
|
|
209
|
+
readonly min: 6;
|
|
210
|
+
readonly max: 128;
|
|
211
|
+
};
|
|
212
|
+
readonly labelColor: {
|
|
213
|
+
readonly type: "color";
|
|
214
|
+
readonly default: readonly [0.1, 0.1, 0.1, 1];
|
|
215
|
+
readonly label: "Label Colour";
|
|
216
|
+
};
|
|
217
|
+
readonly filename: {
|
|
218
|
+
readonly type: "string";
|
|
219
|
+
readonly default: "contact-sheet.png";
|
|
220
|
+
readonly label: "Filename";
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
loadExecute: () => Promise<typeof executeContactSheet>;
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=contactSheet.d.ts.map
|