diff --git a/.gitignore b/.gitignore index 83b10a0b00..5eee4b56a2 100644 --- a/.gitignore +++ b/.gitignore @@ -205,4 +205,10 @@ node-compile-cache/ /target # Test artifacts -**/__tests__/artifacts/ \ No newline at end of file +**/__tests__/artifacts/ + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class \ No newline at end of file diff --git a/.tools/README.md b/.tools/README.md index b1beff970a..3d6a2764dc 100644 --- a/.tools/README.md +++ b/.tools/README.md @@ -6,9 +6,10 @@ This directory contains miscellaneous, trivial scripts that are useful for inter ## Available Tools -| Tool | Description | Usage | -| -------------- | --------------------------------------------- | -------------------- | -| `pbdump.swift` | Dump macOS clipboard contents (all UTI types) | `swift pbdump.swift` | +| Tool | Description | Usage | +| ------------------ | ----------------------------------------------------------- | -------------------------------------------------------------------- | +| `pbdump.swift` | Dump macOS clipboard contents (all UTI types) | `swift pbdump.swift` | +| `figma_archive.py` | Archive a Figma file via REST API (document.json + images/) | `python .tools/figma_archive.py --filekey --archive-dir ` | ## Contributing diff --git a/.tools/figma_archive.py b/.tools/figma_archive.py new file mode 100644 index 0000000000..4d14c6fd48 --- /dev/null +++ b/.tools/figma_archive.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# https://gist.github.com/softmarshmallow/27ad65dfa5babc2c67b41740f1f05791 +""" +Archive a Figma file via REST API: document.json (with geometry) and images/*. + +Stdlib only. Usage: + + python .tools/figma_archive.py --filekey --archive-dir [--x-figma-token ] + # or set FIGMA_TOKEN in the environment + +Output layout: + /document.json — GET /v1/files/:key?geometry=paths + /images/. — from GET /v1/files/:key/images, then download each URL +""" + +import argparse +import json +import os +import sys +from pathlib import Path +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + +FIGMA_BASE = "https://api.figma.com" +ENV_TOKEN = "FIGMA_TOKEN" + +# Content-Type -> file extension for image fills +MIME_EXT = { + "image/png": "png", + "image/jpeg": "jpeg", + "image/jpg": "jpg", + "image/webp": "webp", + "image/gif": "gif", +} + + +def get_token(flag_value: str | None) -> str: + token = flag_value or os.environ.get(ENV_TOKEN) + if not token or not token.strip(): + print(f"error: provide --x-figma-token or set {ENV_TOKEN}", file=sys.stderr) + sys.exit(1) + return token.strip() + + +def api_get(url: str, token: str) -> bytes: + req = Request(url, method="GET", headers={"X-Figma-Token": token}) + try: + with urlopen(req, timeout=60) as resp: + return resp.read() + except HTTPError as e: + print(f"error: HTTP {e.code} {e.reason}: {url}", file=sys.stderr) + if e.fp: + body = e.fp.read().decode("utf-8", errors="replace")[:500] + print(body, file=sys.stderr) + sys.exit(1) + except URLError as e: + print(f"error: request failed: {e.reason}", file=sys.stderr) + sys.exit(1) + + +def fetch_document(file_key: str, token: str) -> dict: + url = f"{FIGMA_BASE}/v1/files/{file_key}?geometry=paths" + raw = api_get(url, token) + return json.loads(raw.decode("utf-8")) + + +def fetch_image_fills(file_key: str, token: str) -> dict[str, str]: + url = f"{FIGMA_BASE}/v1/files/{file_key}/images" + raw = api_get(url, token) + data = json.loads(raw.decode("utf-8")) + meta = data.get("meta") or {} + images = meta.get("images") or {} + return {k: v for k, v in images.items() if v} + + +def download_image(url: str) -> tuple[bytes, str]: + # Image URLs from the Images API are pre-signed; no token needed. + req = Request(url, method="GET") + with urlopen(req, timeout=120) as resp: + data = resp.read() + content_type = ( + (resp.headers.get("Content-Type") or "").split(";")[0].strip().lower() + ) + ext = MIME_EXT.get(content_type) or "png" + return data, ext + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Archive a Figma file (document.json + images/) via REST API." + ) + parser.add_argument( + "--x-figma-token", + metavar="TOKEN", + default=None, + help=f"Figma personal access token (or set {ENV_TOKEN})", + ) + parser.add_argument("--filekey", required=True, help="Figma file key") + parser.add_argument( + "--archive-dir", + required=True, + type=Path, + help="Output directory (document.json and images/ will be written here)", + ) + args = parser.parse_args() + + token = get_token(args.x_figma_token) + archive_dir = args.archive_dir.resolve() + file_key = args.filekey.strip() + + archive_dir.mkdir(parents=True, exist_ok=True) + images_dir = archive_dir / "images" + images_dir.mkdir(exist_ok=True) + + print("Fetching document (geometry=paths)...", flush=True) + doc = fetch_document(file_key, token) + document_path = archive_dir / "document.json" + with open(document_path, "w", encoding="utf-8") as f: + json.dump(doc, f, indent=2, ensure_ascii=False) + doc_size = document_path.stat().st_size + print(f"Wrote document.json ({doc_size:,} bytes)", flush=True) + + print("Fetching image fills list...", flush=True) + ref_to_url = fetch_image_fills(file_key, token) + n_images = len(ref_to_url) + if not ref_to_url: + print("No image fills.", flush=True) + return + + print(f"Downloading {n_images} image(s)...", flush=True) + total_bytes = 0 + ok = 0 + for i, (ref, url) in enumerate(ref_to_url.items(), 1): + # Ref can contain characters unsafe for filenames; sanitize to alphanumeric + underscore + safe_ref = "".join(c if c.isalnum() or c in "._-" else "_" for c in ref) + if not safe_ref: + safe_ref = f"ref_{i}" + try: + data, ext = download_image(url) + out_path = images_dir / f"{safe_ref}.{ext}" + out_path.write_bytes(data) + size_k = len(data) / 1024 + total_bytes += len(data) + ok += 1 + print(f" [{i}/{n_images}] {safe_ref}.{ext} ({size_k:.1f} KB)", flush=True) + except Exception as e: + print(f" [{i}/{n_images}] {safe_ref} — failed: {e}", file=sys.stderr) + + total_mb = total_bytes / (1024 * 1024) + print( + f"Done. document.json ({doc_size:,} B) + {ok}/{n_images} images ({total_mb:.2f} MB) in {archive_dir}.", + flush=True, + ) + + +if __name__ == "__main__": + main() diff --git a/apps/blog/blog/refig/post.md b/apps/blog/blog/refig/post.md new file mode 100644 index 0000000000..edc02ddaea --- /dev/null +++ b/apps/blog/blog/refig/post.md @@ -0,0 +1,82 @@ +--- +title: Introducing refig — a headless Figma renderer for real-world pipelines +description: Export Figma designs to PNG/SVG/PDF without a browser. Deterministic, CI-friendly rendering from .fig or REST JSON—built for tooling, previews, and automation. +slug: refig +authors: universe +date: 2026-02-17 +tags: [figma, rendering, ci, wasm, skia, tooling] +hide_table_of_contents: false +--- + +Figma exports are easy—until exporting becomes **infrastructure**. + +Teams start with “click Export” and end up needing **thumbnails**, **asset pipelines**, **visual diffs**, and **repeatable builds**. That’s where the usual options start to hurt: + +- A headless browser is slow and flaky in CI. +- The Figma Images API is great, but it’s still **a network dependency** (tokens, rate limits, availability). +- Signed image URLs expire, which makes “render later” workflows fragile. +- Offline or air‑gapped environments simply can’t rely on API calls. + +**refig** is built for that gap. + + + +## What is refig? + +**`@grida/refig`** (“render figma”) is a **headless Figma renderer**. It turns a Figma document + node id into **PNG, JPEG, WebP, PDF, or SVG**—without opening Figma and without driving a browser UI. + +It works with: + +- **`.fig` files** (offline, reproducible) +- **Figma REST API file JSON** (`GET /v1/files/:key`) when you already have your own ingestion layer + +You can use it as a **CLI** (`refig`) or as a **library** in Node.js and the browser. + +- Technical reference and usage: [`@grida/refig` on npm](https://www.npmjs.com/package/@grida/refig) + +## The idea: deterministic rendering you can build on + +When rendering is deterministic and programmable, a bunch of workflows become straightforward: + +- **Preview services**: generate thumbnails for files, pages, frames, components. +- **Asset pipelines**: treat “Export presets” as build artifacts (commit, cache, publish). +- **Visual regression tests**: render the same node on every PR and diff outputs. +- **Offline archives**: store `.fig` snapshots and reproduce outputs later—no tokens, no network. +- **In-app previews**: render inside your product (browser entrypoint) to show design snapshots. + +In other words: refig is less about “export an image” and more about making Figma rendering a reliable building block. + +## What refig intentionally doesn’t do + +refig is opinionated about scope so it stays composable: + +- **No auth / fetching**: you bring your own token storage, HTTP client, and caching. +- **No design-to-code**: this is rendering (pixels / SVG / PDF), not HTML/CSS/Flutter generation. +- **No editor**: read + render only. + +## A tiny taste (CLI) + +If you just want to feel what it’s like: + +```sh +# Render a node from a .fig export +npx @grida/refig ./design.fig --node "1:23" --out ./out.png + +# Or render from REST API JSON you fetched elsewhere +npx @grida/refig ./document.json --node "1:23" --out ./out.svg +``` + +From there, most teams graduate quickly to: “render N nodes”, “render on every commit”, or “render on demand”. + +## Where this is headed + +We’re treating refig as a foundation for “design → build” automation: + +- render outputs that are **repeatable** +- workflows that can run **in CI** +- tooling that can work **offline** + +If that sounds like your problem, start with the docs (high-level) and the npm page (full API/CLI reference), then wire it into the pipeline you already have: + +- [`@grida/refig` docs](https://grida.co/docs/packages/@grida/refig) +- [`@grida/refig` on npm](https://www.npmjs.com/package/@grida/refig) diff --git a/crates/grida-canvas-wasm/lib/__test__/environment-node-api-spec-validation.test.ts b/crates/grida-canvas-wasm/lib/__test__/environment-node-api-spec-validation.test.ts index 07d510dfb1..750f71a57c 100644 --- a/crates/grida-canvas-wasm/lib/__test__/environment-node-api-spec-validation.test.ts +++ b/crates/grida-canvas-wasm/lib/__test__/environment-node-api-spec-validation.test.ts @@ -61,6 +61,7 @@ const EXPECTED_FUNCTIONS = [ // images { name: "_add_image", paramCount: 3 }, + { name: "_add_image_with_rid", paramCount: 5 }, { name: "_get_image_bytes", paramCount: 3 }, { name: "_get_image_size", paramCount: 3 }, diff --git a/crates/grida-canvas-wasm/lib/__test__/environment-node-raster-export.test.ts b/crates/grida-canvas-wasm/lib/__test__/environment-node-raster-export.test.ts index c86c7eac94..051095d2ad 100644 --- a/crates/grida-canvas-wasm/lib/__test__/environment-node-raster-export.test.ts +++ b/crates/grida-canvas-wasm/lib/__test__/environment-node-raster-export.test.ts @@ -70,4 +70,94 @@ describe("raster export (node)", () => { expectPng(data); writePng("gradient-rect", data); }, 30_000); + + it("registers fixture image with addImageWithId and renders with custom RID", async () => { + const imagePath = resolve(process.cwd(), "../../fixtures/images/stripes.png"); + const imageBytes = new Uint8Array(readFileSync(imagePath)); + + const doc = { + version: "0.90.0-beta+20260108", + document: { + nodes: { + "image-rect": { + id: "image-rect", + name: "image-rect", + locked: false, + active: true, + layout_positioning: "absolute", + layout_inset_top: 24, + layout_inset_left: 24, + opacity: 1, + z_index: 0, + rotation: 0, + layout_target_width: 208, + layout_target_height: 208, + type: "rectangle", + corner_radius: 16, + effects: [], + stroke_width: 0, + stroke_cap: "butt", + fill_paints: [ + { + type: "image", + active: true, + src: "res://images/test-fixture-stripes", + fit: "cover", + opacity: 1, + blend_mode: "normal", + filters: { + exposure: 0, + contrast: 0, + saturation: 0, + temperature: 0, + tint: 0, + highlights: 0, + shadows: 0, + }, + }, + ], + }, + main: { + type: "scene", + id: "main", + name: "main", + active: true, + locked: false, + constraints: { children: "multiple" }, + guides: [], + edges: [], + background_color: { r: 0.96, g: 0.96, b: 0.96, a: 1 }, + }, + }, + links: { main: ["image-rect"] }, + scenes_ref: ["main"], + bitmaps: {}, + images: {}, + properties: {}, + }, + }; + + const canvas = await createCanvas({ + backend: "raster", + width: 256, + height: 256, + useEmbeddedFonts: true, + }); + + const result = canvas.addImageWithId(imageBytes, "res://images/test-fixture-stripes"); + expect(result).not.toBe(false); + expect((result as { width: number; height: number }).width).toBeGreaterThan(0); + expect((result as { width: number; height: number }).height).toBeGreaterThan(0); + + canvas.loadScene(JSON.stringify(doc)); + + const { data } = canvas.exportNodeAs("image-rect", { + format: "PNG", + constraints: { type: "none", value: 1 }, + }); + + expectPng(data); + writePng("add-image-with-id-stripes", data); + canvas.dispose(); + }, 30_000); }); diff --git a/crates/grida-canvas-wasm/lib/bin/grida-canvas-wasm.js b/crates/grida-canvas-wasm/lib/bin/grida-canvas-wasm.js index 38bbc120a1..4920e6db16 100644 --- a/crates/grida-canvas-wasm/lib/bin/grida-canvas-wasm.js +++ b/crates/grida-canvas-wasm/lib/bin/grida-canvas-wasm.js @@ -1,2 +1,2 @@ -var createGridaCanvas=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else{}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["Lg"]();FS.ignorePermissions=false}function preMain(){}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("grida_canvas_wasm.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var exceptionCaught=[];var uncaughtExceptionCount=0;var ___cxa_begin_catch=ptr=>{var info=new ExceptionInfo(ptr);if(!info.get_caught()){info.set_caught(true);uncaughtExceptionCount--}info.set_rethrown(false);exceptionCaught.push(info);return ___cxa_get_exception_ptr(ptr)};var exceptionLast=0;var ___cxa_end_catch=()=>{_setThrew(0,0);var info=exceptionCaught.pop();___cxa_decrement_exception_refcount(info.excPtr);exceptionLast=0};class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var setTempRet0=val=>__emscripten_tempret_set(val);var findMatchingCatch=args=>{var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}for(var caughtType of args){if(caughtType===0||caughtType===thrownType){break}var adjusted_ptr_addr=info.ptr+16;if(___cxa_can_catch(caughtType,thrownType,adjusted_ptr_addr)){setTempRet0(caughtType);return thrown}}setTempRet0(thrownType);return thrown};var ___cxa_find_matching_catch_2=()=>findMatchingCatch([]);var ___cxa_find_matching_catch_3=arg0=>findMatchingCatch([arg0]);var ___cxa_find_matching_catch_4=(arg0,arg1)=>findMatchingCatch([arg0,arg1]);var ___cxa_rethrow=()=>{var info=exceptionCaught.pop();if(!info){abort("no exception to throw")}var ptr=info.excPtr;if(!info.get_rethrown()){exceptionCaught.push(info);info.set_rethrown(true);info.set_caught(false);uncaughtExceptionCount++}___cxa_increment_exception_refcount(ptr);exceptionLast=ptr;throw exceptionLast};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);___cxa_increment_exception_refcount(ptr);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};var ___cxa_uncaught_exceptions=()=>uncaughtExceptionCount;var ___resumeException=ptr=>{if(!exceptionLast){exceptionLast=ptr}throw exceptionLast};var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>crypto.getRandomValues(view)};var randomFill=view=>{(randomFill=initRandomFill())(view)};var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var zeroMemory=(ptr,size)=>HEAPU8.fill(0,ptr,ptr+size);var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var mmapAlloc=size=>{size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(ptr)zeroMemory(ptr,size);return ptr};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack=""}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,readFiles:{},ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);setattr(arg,attr)},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}for(var mount of mounts){if(mount.type.syncfs){mount.type.syncfs(mount,populate,done)}else{done(null)}}},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable)){while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS_modeStringToFlags(flags):flags;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}if(Module["logReadFiles"]&&!(flags&1)){if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){data=new Uint8Array(intArrayFromString(data,true))}if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{abort("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops)){stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}}function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var SYSCALLS={calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{return SYSCALLS.writeStat(buf,FS.fstat(fd))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);function ___syscall_getcwd(buf,size){try{if(size===0)return-28;var cwd=FS.cwd();var cwdLengthInBytes=lengthBytesUTF8(cwd)+1;if(size>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21537:case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.lstat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.writeStat(buf,nofollow?FS.lstat(path):FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_readlinkat(dirfd,path,buf,bufsize){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>abort("");var __emscripten_throw_longjmp=()=>{throw Infinity};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function __gmtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function __mmap_js(len,prot,flags,fd,offset,allocated,addr){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,offset,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffsetperformance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>3]=BigInt(nsec);return 0}var getHeapMax=()=>2147483648;var _emscripten_get_heap_max=()=>getHeapMax();var GLctx;var webgl_enable_ANGLE_instanced_arrays=ctx=>{var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=(index,divisor)=>ext["vertexAttribDivisorANGLE"](index,divisor);ctx["drawArraysInstanced"]=(mode,first,count,primcount)=>ext["drawArraysInstancedANGLE"](mode,first,count,primcount);ctx["drawElementsInstanced"]=(mode,count,type,indices,primcount)=>ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount);return 1}};var webgl_enable_OES_vertex_array_object=ctx=>{var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=()=>ext["createVertexArrayOES"]();ctx["deleteVertexArray"]=vao=>ext["deleteVertexArrayOES"](vao);ctx["bindVertexArray"]=vao=>ext["bindVertexArrayOES"](vao);ctx["isVertexArray"]=vao=>ext["isVertexArrayOES"](vao);return 1}};var webgl_enable_WEBGL_draw_buffers=ctx=>{var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=(n,bufs)=>ext["drawBuffersWEBGL"](n,bufs);return 1}};var webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"));var webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"));var webgl_enable_EXT_polygon_offset_clamp=ctx=>!!(ctx.extPolygonOffsetClamp=ctx.getExtension("EXT_polygon_offset_clamp"));var webgl_enable_EXT_clip_control=ctx=>!!(ctx.extClipControl=ctx.getExtension("EXT_clip_control"));var webgl_enable_WEBGL_polygon_mode=ctx=>!!(ctx.webglPolygonMode=ctx.getExtension("WEBGL_polygon_mode"));var webgl_enable_WEBGL_multi_draw=ctx=>!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"));var getEmscriptenSupportedExtensions=ctx=>{var supportedExtensions=["ANGLE_instanced_arrays","EXT_blend_minmax","EXT_disjoint_timer_query","EXT_frag_depth","EXT_shader_texture_lod","EXT_sRGB","OES_element_index_uint","OES_fbo_render_mipmap","OES_standard_derivatives","OES_texture_float","OES_texture_half_float","OES_texture_half_float_linear","OES_vertex_array_object","WEBGL_color_buffer_float","WEBGL_depth_texture","WEBGL_draw_buffers","EXT_color_buffer_float","EXT_conservative_depth","EXT_disjoint_timer_query_webgl2","EXT_texture_norm16","NV_shader_noperspective_interpolation","WEBGL_clip_cull_distance","EXT_clip_control","EXT_color_buffer_half_float","EXT_depth_clamp","EXT_float_blend","EXT_polygon_offset_clamp","EXT_texture_compression_bptc","EXT_texture_compression_rgtc","EXT_texture_filter_anisotropic","KHR_parallel_shader_compile","OES_texture_float_linear","WEBGL_blend_func_extended","WEBGL_compressed_texture_astc","WEBGL_compressed_texture_etc","WEBGL_compressed_texture_etc1","WEBGL_compressed_texture_s3tc","WEBGL_compressed_texture_s3tc_srgb","WEBGL_debug_renderer_info","WEBGL_debug_shaders","WEBGL_lose_context","WEBGL_multi_draw","WEBGL_polygon_mode"];return(ctx.getSupportedExtensions()||[]).filter(ext=>supportedExtensions.includes(ext))};var GL={counter:1,buffers:[],programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],stringCache:{},stringiCache:{},unpackAlignment:4,unpackRowLength:0,recordError:errorCode=>{if(!GL.lastError){GL.lastError=errorCode}},getNewId:table=>{var ret=GL.counter++;for(var i=table.length;i{for(var i=0;i>2]=id}},getSource:(shader,count,string,length)=>{var source="";for(var i=0;i>2]:undefined;source+=UTF8ToString(HEAPU32[string+i*4>>2],len)}return source},createContext:(canvas,webGLContextAttributes)=>{if(!canvas.getContextSafariWebGL2Fixed){canvas.getContextSafariWebGL2Fixed=canvas.getContext;function fixedGetContext(ver,attrs){var gl=canvas.getContextSafariWebGL2Fixed(ver,attrs);return ver=="webgl"==gl instanceof WebGLRenderingContext?gl:null}canvas.getContext=fixedGetContext}var ctx=webGLContextAttributes.majorVersion>1?canvas.getContext("webgl2",webGLContextAttributes):canvas.getContext("webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},registerContext:(ctx,webGLContextAttributes)=>{var handle=GL.getNewId(GL.contexts);var context={handle,attributes:webGLContextAttributes,version:webGLContextAttributes.majorVersion,GLctx:ctx};if(ctx.canvas)ctx.canvas.GLctxObject=context;GL.contexts[handle]=context;if(typeof webGLContextAttributes.enableExtensionsByDefault=="undefined"||webGLContextAttributes.enableExtensionsByDefault){GL.initExtensions(context)}return handle},makeContextCurrent:contextHandle=>{GL.currentContext=GL.contexts[contextHandle];Module["ctx"]=GLctx=GL.currentContext?.GLctx;return!(contextHandle&&!GLctx)},getContext:contextHandle=>GL.contexts[contextHandle],deleteContext:contextHandle=>{if(GL.currentContext===GL.contexts[contextHandle]){GL.currentContext=null}if(typeof JSEvents=="object"){JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas)}if(GL.contexts[contextHandle]?.GLctx.canvas){GL.contexts[contextHandle].GLctx.canvas.GLctxObject=undefined}GL.contexts[contextHandle]=null},initExtensions:context=>{context||=GL.currentContext;if(context.initExtensionsDone)return;context.initExtensionsDone=true;var GLctx=context.GLctx;webgl_enable_WEBGL_multi_draw(GLctx);webgl_enable_EXT_polygon_offset_clamp(GLctx);webgl_enable_EXT_clip_control(GLctx);webgl_enable_WEBGL_polygon_mode(GLctx);webgl_enable_ANGLE_instanced_arrays(GLctx);webgl_enable_OES_vertex_array_object(GLctx);webgl_enable_WEBGL_draw_buffers(GLctx);webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx);webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx);if(context.version>=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}for(var ext of getEmscriptenSupportedExtensions(GLctx)){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}}}};var _emscripten_glActiveTexture=x0=>GLctx.activeTexture(x0);var _emscripten_glAttachShader=(program,shader)=>{GLctx.attachShader(GL.programs[program],GL.shaders[shader])};var _emscripten_glBeginQuery=(target,id)=>{GLctx.beginQuery(target,GL.queries[id])};var _emscripten_glBeginQueryEXT=(target,id)=>{GLctx.disjointTimerQueryExt["beginQueryEXT"](target,GL.queries[id])};var _emscripten_glBeginTransformFeedback=x0=>GLctx.beginTransformFeedback(x0);var _emscripten_glBindAttribLocation=(program,index,name)=>{GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))};var _emscripten_glBindBuffer=(target,buffer)=>{if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])};var _emscripten_glBindBufferBase=(target,index,buffer)=>{GLctx.bindBufferBase(target,index,GL.buffers[buffer])};var _emscripten_glBindBufferRange=(target,index,buffer,offset,ptrsize)=>{GLctx.bindBufferRange(target,index,GL.buffers[buffer],offset,ptrsize)};var _emscripten_glBindFramebuffer=(target,framebuffer)=>{GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])};var _emscripten_glBindRenderbuffer=(target,renderbuffer)=>{GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])};var _emscripten_glBindSampler=(unit,sampler)=>{GLctx.bindSampler(unit,GL.samplers[sampler])};var _emscripten_glBindTexture=(target,texture)=>{GLctx.bindTexture(target,GL.textures[texture])};var _emscripten_glBindTransformFeedback=(target,id)=>{GLctx.bindTransformFeedback(target,GL.transformFeedbacks[id])};var _emscripten_glBindVertexArray=vao=>{GLctx.bindVertexArray(GL.vaos[vao])};var _glBindVertexArray=_emscripten_glBindVertexArray;var _emscripten_glBindVertexArrayOES=_glBindVertexArray;var _emscripten_glBlendColor=(x0,x1,x2,x3)=>GLctx.blendColor(x0,x1,x2,x3);var _emscripten_glBlendEquation=x0=>GLctx.blendEquation(x0);var _emscripten_glBlendEquationSeparate=(x0,x1)=>GLctx.blendEquationSeparate(x0,x1);var _emscripten_glBlendFunc=(x0,x1)=>GLctx.blendFunc(x0,x1);var _emscripten_glBlendFuncSeparate=(x0,x1,x2,x3)=>GLctx.blendFuncSeparate(x0,x1,x2,x3);var _emscripten_glBlitFramebuffer=(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)=>GLctx.blitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9);var _emscripten_glBufferData=(target,size,data,usage)=>{if(GL.currentContext.version>=2){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}return}GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)};var _emscripten_glBufferSubData=(target,offset,size,data)=>{if(GL.currentContext.version>=2){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))};var _emscripten_glCheckFramebufferStatus=x0=>GLctx.checkFramebufferStatus(x0);var _emscripten_glClear=x0=>GLctx.clear(x0);var _emscripten_glClearBufferfi=(x0,x1,x2,x3)=>GLctx.clearBufferfi(x0,x1,x2,x3);var _emscripten_glClearBufferfv=(buffer,drawbuffer,value)=>{GLctx.clearBufferfv(buffer,drawbuffer,HEAPF32,value>>2)};var _emscripten_glClearBufferiv=(buffer,drawbuffer,value)=>{GLctx.clearBufferiv(buffer,drawbuffer,HEAP32,value>>2)};var _emscripten_glClearBufferuiv=(buffer,drawbuffer,value)=>{GLctx.clearBufferuiv(buffer,drawbuffer,HEAPU32,value>>2)};var _emscripten_glClearColor=(x0,x1,x2,x3)=>GLctx.clearColor(x0,x1,x2,x3);var _emscripten_glClearDepthf=x0=>GLctx.clearDepth(x0);var _emscripten_glClearStencil=x0=>GLctx.clearStencil(x0);var _emscripten_glClientWaitSync=(sync,flags,timeout)=>{timeout=Number(timeout);return GLctx.clientWaitSync(GL.syncs[sync],flags,timeout)};var _emscripten_glClipControlEXT=(origin,depth)=>{GLctx.extClipControl["clipControlEXT"](origin,depth)};var _emscripten_glColorMask=(red,green,blue,alpha)=>{GLctx.colorMask(!!red,!!green,!!blue,!!alpha)};var _emscripten_glCompileShader=shader=>{GLctx.compileShader(GL.shaders[shader])};var _emscripten_glCompressedTexImage2D=(target,level,internalFormat,width,height,border,imageSize,data)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,imageSize,data);return}GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,HEAPU8,data,imageSize);return}GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,HEAPU8.subarray(data,data+imageSize))};var _emscripten_glCompressedTexImage3D=(target,level,internalFormat,width,height,depth,border,imageSize,data)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.compressedTexImage3D(target,level,internalFormat,width,height,depth,border,imageSize,data)}else{GLctx.compressedTexImage3D(target,level,internalFormat,width,height,depth,border,HEAPU8,data,imageSize)}};var _emscripten_glCompressedTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,imageSize,data)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data);return}GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,HEAPU8,data,imageSize);return}GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,HEAPU8.subarray(data,data+imageSize))};var _emscripten_glCompressedTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}};var _emscripten_glCopyBufferSubData=(x0,x1,x2,x3,x4)=>GLctx.copyBufferSubData(x0,x1,x2,x3,x4);var _emscripten_glCopyTexImage2D=(x0,x1,x2,x3,x4,x5,x6,x7)=>GLctx.copyTexImage2D(x0,x1,x2,x3,x4,x5,x6,x7);var _emscripten_glCopyTexSubImage2D=(x0,x1,x2,x3,x4,x5,x6,x7)=>GLctx.copyTexSubImage2D(x0,x1,x2,x3,x4,x5,x6,x7);var _emscripten_glCopyTexSubImage3D=(x0,x1,x2,x3,x4,x5,x6,x7,x8)=>GLctx.copyTexSubImage3D(x0,x1,x2,x3,x4,x5,x6,x7,x8);var _emscripten_glCreateProgram=()=>{var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id};var _emscripten_glCreateShader=shaderType=>{var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id};var _emscripten_glCullFace=x0=>GLctx.cullFace(x0);var _emscripten_glDeleteBuffers=(n,buffers)=>{for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}};var _emscripten_glDeleteFramebuffers=(n,framebuffers)=>{for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}};var _emscripten_glDeleteProgram=id=>{if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null};var _emscripten_glDeleteQueries=(n,ids)=>{for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.deleteQuery(query);GL.queries[id]=null}};var _emscripten_glDeleteQueriesEXT=(n,ids)=>{for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.disjointTimerQueryExt["deleteQueryEXT"](query);GL.queries[id]=null}};var _emscripten_glDeleteRenderbuffers=(n,renderbuffers)=>{for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}};var _emscripten_glDeleteSamplers=(n,samplers)=>{for(var i=0;i>2];var sampler=GL.samplers[id];if(!sampler)continue;GLctx.deleteSampler(sampler);sampler.name=0;GL.samplers[id]=null}};var _emscripten_glDeleteShader=id=>{if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null};var _emscripten_glDeleteSync=id=>{if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null};var _emscripten_glDeleteTextures=(n,textures)=>{for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}};var _emscripten_glDeleteTransformFeedbacks=(n,ids)=>{for(var i=0;i>2];var transformFeedback=GL.transformFeedbacks[id];if(!transformFeedback)continue;GLctx.deleteTransformFeedback(transformFeedback);transformFeedback.name=0;GL.transformFeedbacks[id]=null}};var _emscripten_glDeleteVertexArrays=(n,vaos)=>{for(var i=0;i>2];GLctx.deleteVertexArray(GL.vaos[id]);GL.vaos[id]=null}};var _glDeleteVertexArrays=_emscripten_glDeleteVertexArrays;var _emscripten_glDeleteVertexArraysOES=_glDeleteVertexArrays;var _emscripten_glDepthFunc=x0=>GLctx.depthFunc(x0);var _emscripten_glDepthMask=flag=>{GLctx.depthMask(!!flag)};var _emscripten_glDepthRangef=(x0,x1)=>GLctx.depthRange(x0,x1);var _emscripten_glDetachShader=(program,shader)=>{GLctx.detachShader(GL.programs[program],GL.shaders[shader])};var _emscripten_glDisable=x0=>GLctx.disable(x0);var _emscripten_glDisableVertexAttribArray=index=>{GLctx.disableVertexAttribArray(index)};var _emscripten_glDrawArrays=(mode,first,count)=>{GLctx.drawArrays(mode,first,count)};var _emscripten_glDrawArraysInstanced=(mode,first,count,primcount)=>{GLctx.drawArraysInstanced(mode,first,count,primcount)};var _glDrawArraysInstanced=_emscripten_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedANGLE=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedARB=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedBaseInstanceWEBGL=(mode,first,count,instanceCount,baseInstance)=>{GLctx.dibvbi["drawArraysInstancedBaseInstanceWEBGL"](mode,first,count,instanceCount,baseInstance)};var _emscripten_glDrawArraysInstancedEXT=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedNV=_glDrawArraysInstanced;var tempFixedLengthArray=[];var _emscripten_glDrawBuffers=(n,bufs)=>{var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx.drawBuffers(bufArray)};var _glDrawBuffers=_emscripten_glDrawBuffers;var _emscripten_glDrawBuffersEXT=_glDrawBuffers;var _emscripten_glDrawBuffersWEBGL=_glDrawBuffers;var _emscripten_glDrawElements=(mode,count,type,indices)=>{GLctx.drawElements(mode,count,type,indices)};var _emscripten_glDrawElementsInstanced=(mode,count,type,indices,primcount)=>{GLctx.drawElementsInstanced(mode,count,type,indices,primcount)};var _glDrawElementsInstanced=_emscripten_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedANGLE=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedARB=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedBaseVertexBaseInstanceWEBGL=(mode,count,type,offset,instanceCount,baseVertex,baseinstance)=>{GLctx.dibvbi["drawElementsInstancedBaseVertexBaseInstanceWEBGL"](mode,count,type,offset,instanceCount,baseVertex,baseinstance)};var _emscripten_glDrawElementsInstancedEXT=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedNV=_glDrawElementsInstanced;var _glDrawElements=_emscripten_glDrawElements;var _emscripten_glDrawRangeElements=(mode,start,end,count,type,indices)=>{_glDrawElements(mode,count,type,indices)};var _emscripten_glEnable=x0=>GLctx.enable(x0);var _emscripten_glEnableVertexAttribArray=index=>{GLctx.enableVertexAttribArray(index)};var _emscripten_glEndQuery=x0=>GLctx.endQuery(x0);var _emscripten_glEndQueryEXT=target=>{GLctx.disjointTimerQueryExt["endQueryEXT"](target)};var _emscripten_glEndTransformFeedback=()=>GLctx.endTransformFeedback();var _emscripten_glFenceSync=(condition,flags)=>{var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}return 0};var _emscripten_glFinish=()=>GLctx.finish();var _emscripten_glFlush=()=>GLctx.flush();var _emscripten_glFramebufferRenderbuffer=(target,attachment,renderbuffertarget,renderbuffer)=>{GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])};var _emscripten_glFramebufferTexture2D=(target,attachment,textarget,texture,level)=>{GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)};var _emscripten_glFramebufferTextureLayer=(target,attachment,texture,level,layer)=>{GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)};var _emscripten_glFrontFace=x0=>GLctx.frontFace(x0);var _emscripten_glGenBuffers=(n,buffers)=>{GL.genObject(n,buffers,"createBuffer",GL.buffers)};var _emscripten_glGenFramebuffers=(n,ids)=>{GL.genObject(n,ids,"createFramebuffer",GL.framebuffers)};var _emscripten_glGenQueries=(n,ids)=>{GL.genObject(n,ids,"createQuery",GL.queries)};var _emscripten_glGenQueriesEXT=(n,ids)=>{for(var i=0;i>2]=0;return}var id=GL.getNewId(GL.queries);query.name=id;GL.queries[id]=query;HEAP32[ids+i*4>>2]=id}};var _emscripten_glGenRenderbuffers=(n,renderbuffers)=>{GL.genObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)};var _emscripten_glGenSamplers=(n,samplers)=>{GL.genObject(n,samplers,"createSampler",GL.samplers)};var _emscripten_glGenTextures=(n,textures)=>{GL.genObject(n,textures,"createTexture",GL.textures)};var _emscripten_glGenTransformFeedbacks=(n,ids)=>{GL.genObject(n,ids,"createTransformFeedback",GL.transformFeedbacks)};var _emscripten_glGenVertexArrays=(n,arrays)=>{GL.genObject(n,arrays,"createVertexArray",GL.vaos)};var _glGenVertexArrays=_emscripten_glGenVertexArrays;var _emscripten_glGenVertexArraysOES=_glGenVertexArrays;var _emscripten_glGenerateMipmap=x0=>GLctx.generateMipmap(x0);var __glGetActiveAttribOrUniform=(funcName,program,index,bufSize,length,size,type,name)=>{program=GL.programs[program];var info=GLctx[funcName](program,index);if(info){var numBytesWrittenExclNull=name&&stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull;if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type}};var _emscripten_glGetActiveAttrib=(program,index,bufSize,length,size,type,name)=>__glGetActiveAttribOrUniform("getActiveAttrib",program,index,bufSize,length,size,type,name);var _emscripten_glGetActiveUniform=(program,index,bufSize,length,size,type,name)=>__glGetActiveAttribOrUniform("getActiveUniform",program,index,bufSize,length,size,type,name);var _emscripten_glGetActiveUniformBlockName=(program,uniformBlockIndex,bufSize,length,uniformBlockName)=>{program=GL.programs[program];var result=GLctx.getActiveUniformBlockName(program,uniformBlockIndex);if(!result)return;if(uniformBlockName&&bufSize>0){var numBytesWrittenExclNull=stringToUTF8(result,uniformBlockName,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull}else{if(length)HEAP32[length>>2]=0}};var _emscripten_glGetActiveUniformBlockiv=(program,uniformBlockIndex,pname,params)=>{if(!params){GL.recordError(1281);return}program=GL.programs[program];if(pname==35393){var name=GLctx.getActiveUniformBlockName(program,uniformBlockIndex);HEAP32[params>>2]=name.length+1;return}var result=GLctx.getActiveUniformBlockParameter(program,uniformBlockIndex,pname);if(result===null)return;if(pname==35395){for(var i=0;i>2]=result[i]}}else{HEAP32[params>>2]=result}};var _emscripten_glGetActiveUniformsiv=(program,uniformCount,uniformIndices,pname,params)=>{if(!params){GL.recordError(1281);return}if(uniformCount>0&&uniformIndices==0){GL.recordError(1281);return}program=GL.programs[program];var ids=[];for(var i=0;i>2])}var result=GLctx.getActiveUniforms(program,ids,pname);if(!result)return;var len=result.length;for(var i=0;i>2]=result[i]}};var _emscripten_glGetAttachedShaders=(program,maxCount,count,shaders)=>{var result=GLctx.getAttachedShaders(GL.programs[program]);var len=result.length;if(len>maxCount){len=maxCount}HEAP32[count>>2]=len;for(var i=0;i>2]=id}};var _emscripten_glGetAttribLocation=(program,name)=>GLctx.getAttribLocation(GL.programs[program],UTF8ToString(name));var writeI53ToI64=(ptr,num)=>{HEAPU32[ptr>>2]=num;var lower=HEAPU32[ptr>>2];HEAPU32[ptr+4>>2]=(num-lower)/4294967296};var webglGetExtensions=()=>{var exts=getEmscriptenSupportedExtensions(GLctx);exts=exts.concat(exts.map(e=>"GL_"+e));return exts};var emscriptenWebGLGet=(name_,p,type)=>{if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}ret=webglGetExtensions().length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err(`GL_INVALID_ENUM in glGet${type}v: Unknown object returned from WebGL getParameter(${name_})! (error: ${e})`);return}}break;default:GL.recordError(1280);err(`GL_INVALID_ENUM in glGet${type}v: Native code calling glGet${type}v(${name_}) and it returns ${result} of type ${typeof result}!`);return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p]=ret?1:0;break}};var _emscripten_glGetBooleanv=(name_,p)=>emscriptenWebGLGet(name_,p,4);var _emscripten_glGetBufferParameteri64v=(target,value,data)=>{if(!data){GL.recordError(1281);return}writeI53ToI64(data,GLctx.getBufferParameter(target,value))};var _emscripten_glGetBufferParameteriv=(target,value,data)=>{if(!data){GL.recordError(1281);return}HEAP32[data>>2]=GLctx.getBufferParameter(target,value)};var _emscripten_glGetError=()=>{var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error};var _emscripten_glGetFloatv=(name_,p)=>emscriptenWebGLGet(name_,p,2);var _emscripten_glGetFragDataLocation=(program,name)=>GLctx.getFragDataLocation(GL.programs[program],UTF8ToString(name));var _emscripten_glGetFramebufferAttachmentParameteriv=(target,attachment,pname,params)=>{var result=GLctx.getFramebufferAttachmentParameter(target,attachment,pname);if(result instanceof WebGLRenderbuffer||result instanceof WebGLTexture){result=result.name|0}HEAP32[params>>2]=result};var emscriptenWebGLGetIndexed=(target,index,data,type)=>{if(!data){GL.recordError(1281);return}var result=GLctx.getIndexedParameter(target,index);var ret;switch(typeof result){case"boolean":ret=result?1:0;break;case"number":ret=result;break;case"object":if(result===null){switch(target){case 35983:case 35368:ret=0;break;default:{GL.recordError(1280);return}}}else if(result instanceof WebGLBuffer){ret=result.name|0}else{GL.recordError(1280);return}break;default:GL.recordError(1280);return}switch(type){case 1:writeI53ToI64(data,ret);break;case 0:HEAP32[data>>2]=ret;break;case 2:HEAPF32[data>>2]=ret;break;case 4:HEAP8[data]=ret?1:0;break;default:abort("internal emscriptenWebGLGetIndexed() error, bad type: "+type)}};var _emscripten_glGetInteger64i_v=(target,index,data)=>emscriptenWebGLGetIndexed(target,index,data,1);var _emscripten_glGetInteger64v=(name_,p)=>{emscriptenWebGLGet(name_,p,1)};var _emscripten_glGetIntegeri_v=(target,index,data)=>emscriptenWebGLGetIndexed(target,index,data,0);var _emscripten_glGetIntegerv=(name_,p)=>emscriptenWebGLGet(name_,p,0);var _emscripten_glGetInternalformativ=(target,internalformat,pname,bufSize,params)=>{if(bufSize<0){GL.recordError(1281);return}if(!params){GL.recordError(1281);return}var ret=GLctx.getInternalformatParameter(target,internalformat,pname);if(ret===null)return;for(var i=0;i>2]=ret[i]}};var _emscripten_glGetProgramBinary=(program,bufSize,length,binaryFormat,binary)=>{GL.recordError(1282)};var _emscripten_glGetProgramInfoLog=(program,maxLength,length,infoLog)=>{var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetProgramiv=(program,pname,p)=>{if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){var numActiveUniforms=GLctx.getProgramParameter(program,35718);for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){var numActiveAttributes=GLctx.getProgramParameter(program,35721);for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){var numActiveUniformBlocks=GLctx.getProgramParameter(program,35382);for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}};var _emscripten_glGetQueryObjecti64vEXT=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param;if(GL.currentContext.version<2){param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname)}else{param=GLctx.getQueryParameter(query,pname)}var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)};var _emscripten_glGetQueryObjectivEXT=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret};var _glGetQueryObjecti64vEXT=_emscripten_glGetQueryObjecti64vEXT;var _emscripten_glGetQueryObjectui64vEXT=_glGetQueryObjecti64vEXT;var _emscripten_glGetQueryObjectuiv=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.getQueryParameter(query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret};var _glGetQueryObjectivEXT=_emscripten_glGetQueryObjectivEXT;var _emscripten_glGetQueryObjectuivEXT=_glGetQueryObjectivEXT;var _emscripten_glGetQueryiv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getQuery(target,pname)};var _emscripten_glGetQueryivEXT=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.disjointTimerQueryExt["getQueryEXT"](target,pname)};var _emscripten_glGetRenderbufferParameteriv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getRenderbufferParameter(target,pname)};var _emscripten_glGetSamplerParameterfv=(sampler,pname,params)=>{if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getSamplerParameter(GL.samplers[sampler],pname)};var _emscripten_glGetSamplerParameteriv=(sampler,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getSamplerParameter(GL.samplers[sampler],pname)};var _emscripten_glGetShaderInfoLog=(shader,maxLength,length,infoLog)=>{var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetShaderPrecisionFormat=(shaderType,precisionType,range,precision)=>{var result=GLctx.getShaderPrecisionFormat(shaderType,precisionType);HEAP32[range>>2]=result.rangeMin;HEAP32[range+4>>2]=result.rangeMax;HEAP32[precision>>2]=result.precision};var _emscripten_glGetShaderSource=(shader,bufSize,length,source)=>{var result=GLctx.getShaderSource(GL.shaders[shader]);if(!result)return;var numBytesWrittenExclNull=bufSize>0&&source?stringToUTF8(result,source,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetShaderiv=(shader,pname,p)=>{if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}};var stringToNewUTF8=str=>{var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8(str,ret,size);return ret};var _emscripten_glGetString=name_=>{var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:ret=stringToNewUTF8(webglGetExtensions().join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s?stringToNewUTF8(s):0;break;case 7938:var webGLVersion=GLctx.getParameter(7938);var glVersion=`OpenGL ES 2.0 (${webGLVersion})`;if(GL.currentContext.version>=2)glVersion=`OpenGL ES 3.0 (${webGLVersion})`;ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion=`OpenGL ES GLSL ES ${ver_num[1]} (${glslVersion})`}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret};var _emscripten_glGetStringi=(name,index)=>{if(GL.currentContext.version<2){GL.recordError(1282);return 0}var stringiCache=GL.stringiCache[name];if(stringiCache){if(index<0||index>=stringiCache.length){GL.recordError(1281);return 0}return stringiCache[index]}switch(name){case 7939:var exts=webglGetExtensions().map(stringToNewUTF8);stringiCache=GL.stringiCache[name]=exts;if(index<0||index>=stringiCache.length){GL.recordError(1281);return 0}return stringiCache[index];default:GL.recordError(1280);return 0}};var _emscripten_glGetSynciv=(sync,pname,bufSize,length,values)=>{if(bufSize<0){GL.recordError(1281);return}if(!values){GL.recordError(1281);return}var ret=GLctx.getSyncParameter(GL.syncs[sync],pname);if(ret!==null){HEAP32[values>>2]=ret;if(length)HEAP32[length>>2]=1}};var _emscripten_glGetTexParameterfv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getTexParameter(target,pname)};var _emscripten_glGetTexParameteriv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getTexParameter(target,pname)};var _emscripten_glGetTransformFeedbackVarying=(program,index,bufSize,length,size,type,name)=>{program=GL.programs[program];var info=GLctx.getTransformFeedbackVarying(program,index);if(!info)return;if(name&&bufSize>0){var numBytesWrittenExclNull=stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull}else{if(length)HEAP32[length>>2]=0}if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type};var _emscripten_glGetUniformBlockIndex=(program,uniformBlockName)=>GLctx.getUniformBlockIndex(GL.programs[program],UTF8ToString(uniformBlockName));var _emscripten_glGetUniformIndices=(program,uniformCount,uniformNames,uniformIndices)=>{if(!uniformIndices){GL.recordError(1281);return}if(uniformCount>0&&(uniformNames==0||uniformIndices==0)){GL.recordError(1281);return}program=GL.programs[program];var names=[];for(var i=0;i>2]));var result=GLctx.getUniformIndices(program,names);if(!result)return;var len=result.length;for(var i=0;i>2]=result[i]}};var jstoi_q=str=>parseInt(str);var webglGetLeftBracePos=name=>name.slice(-1)=="]"&&name.lastIndexOf("[");var webglPrepareUniformLocationsBeforeFirstUse=program=>{var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};var numActiveUniforms=GLctx.getProgramParameter(program,35718);for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j{name=UTF8ToString(name);if(program=GL.programs[program]){webglPrepareUniformLocationsBeforeFirstUse(program);var uniformLocsById=program.uniformLocsById;var arrayIndex=0;var uniformBaseName=name;var leftBrace=webglGetLeftBracePos(name);if(leftBrace>0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex{var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?`[${webglLoc}]`:""))}return webglLoc}else{GL.recordError(1282)}};var emscriptenWebGLGetUniform=(program,location,params,type)=>{if(!params){GL.recordError(1281);return}program=GL.programs[program];webglPrepareUniformLocationsBeforeFirstUse(program);var data=GLctx.getUniform(program,webglGetUniformLocation(location));if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break}}}};var _emscripten_glGetUniformfv=(program,location,params)=>{emscriptenWebGLGetUniform(program,location,params,2)};var _emscripten_glGetUniformiv=(program,location,params)=>{emscriptenWebGLGetUniform(program,location,params,0)};var _emscripten_glGetUniformuiv=(program,location,params)=>emscriptenWebGLGetUniform(program,location,params,0);var emscriptenWebGLGetVertexAttrib=(index,pname,params,type)=>{if(!params){GL.recordError(1281);return}var data=GLctx.getVertexAttrib(index,pname);if(pname==34975){HEAP32[params>>2]=data&&data["name"]}else if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break;case 5:HEAP32[params>>2]=Math.fround(data);break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break;case 5:HEAP32[params+i*4>>2]=Math.fround(data[i]);break}}}};var _emscripten_glGetVertexAttribIiv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,0)};var _glGetVertexAttribIiv=_emscripten_glGetVertexAttribIiv;var _emscripten_glGetVertexAttribIuiv=_glGetVertexAttribIiv;var _emscripten_glGetVertexAttribPointerv=(index,pname,pointer)=>{if(!pointer){GL.recordError(1281);return}HEAP32[pointer>>2]=GLctx.getVertexAttribOffset(index,pname)};var _emscripten_glGetVertexAttribfv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,2)};var _emscripten_glGetVertexAttribiv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,5)};var _emscripten_glHint=(x0,x1)=>GLctx.hint(x0,x1);var _emscripten_glInvalidateFramebuffer=(target,numAttachments,attachments)=>{var list=tempFixedLengthArray[numAttachments];for(var i=0;i>2]}GLctx.invalidateFramebuffer(target,list)};var _emscripten_glInvalidateSubFramebuffer=(target,numAttachments,attachments,x,y,width,height)=>{var list=tempFixedLengthArray[numAttachments];for(var i=0;i>2]}GLctx.invalidateSubFramebuffer(target,list,x,y,width,height)};var _emscripten_glIsBuffer=buffer=>{var b=GL.buffers[buffer];if(!b)return 0;return GLctx.isBuffer(b)};var _emscripten_glIsEnabled=x0=>GLctx.isEnabled(x0);var _emscripten_glIsFramebuffer=framebuffer=>{var fb=GL.framebuffers[framebuffer];if(!fb)return 0;return GLctx.isFramebuffer(fb)};var _emscripten_glIsProgram=program=>{program=GL.programs[program];if(!program)return 0;return GLctx.isProgram(program)};var _emscripten_glIsQuery=id=>{var query=GL.queries[id];if(!query)return 0;return GLctx.isQuery(query)};var _emscripten_glIsQueryEXT=id=>{var query=GL.queries[id];if(!query)return 0;return GLctx.disjointTimerQueryExt["isQueryEXT"](query)};var _emscripten_glIsRenderbuffer=renderbuffer=>{var rb=GL.renderbuffers[renderbuffer];if(!rb)return 0;return GLctx.isRenderbuffer(rb)};var _emscripten_glIsSampler=id=>{var sampler=GL.samplers[id];if(!sampler)return 0;return GLctx.isSampler(sampler)};var _emscripten_glIsShader=shader=>{var s=GL.shaders[shader];if(!s)return 0;return GLctx.isShader(s)};var _emscripten_glIsSync=sync=>GLctx.isSync(GL.syncs[sync]);var _emscripten_glIsTexture=id=>{var texture=GL.textures[id];if(!texture)return 0;return GLctx.isTexture(texture)};var _emscripten_glIsTransformFeedback=id=>GLctx.isTransformFeedback(GL.transformFeedbacks[id]);var _emscripten_glIsVertexArray=array=>{var vao=GL.vaos[array];if(!vao)return 0;return GLctx.isVertexArray(vao)};var _glIsVertexArray=_emscripten_glIsVertexArray;var _emscripten_glIsVertexArrayOES=_glIsVertexArray;var _emscripten_glLineWidth=x0=>GLctx.lineWidth(x0);var _emscripten_glLinkProgram=program=>{program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}};var _emscripten_glMultiDrawArraysInstancedBaseInstanceWEBGL=(mode,firsts,counts,instanceCounts,baseInstances,drawCount)=>{GLctx.mdibvbi["multiDrawArraysInstancedBaseInstanceWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,HEAP32,instanceCounts>>2,HEAPU32,baseInstances>>2,drawCount)};var _emscripten_glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL=(mode,counts,type,offsets,instanceCounts,baseVertices,baseInstances,drawCount)=>{GLctx.mdibvbi["multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,HEAP32,instanceCounts>>2,HEAP32,baseVertices>>2,HEAPU32,baseInstances>>2,drawCount)};var _emscripten_glPauseTransformFeedback=()=>GLctx.pauseTransformFeedback();var _emscripten_glPixelStorei=(pname,param)=>{if(pname==3317){GL.unpackAlignment=param}else if(pname==3314){GL.unpackRowLength=param}GLctx.pixelStorei(pname,param)};var _emscripten_glPolygonModeWEBGL=(face,mode)=>{GLctx.webglPolygonMode["polygonModeWEBGL"](face,mode)};var _emscripten_glPolygonOffset=(x0,x1)=>GLctx.polygonOffset(x0,x1);var _emscripten_glPolygonOffsetClampEXT=(factor,units,clamp)=>{GLctx.extPolygonOffsetClamp["polygonOffsetClampEXT"](factor,units,clamp)};var _emscripten_glProgramBinary=(program,binaryFormat,binary,length)=>{GL.recordError(1280)};var _emscripten_glProgramParameteri=(program,pname,value)=>{GL.recordError(1280)};var _emscripten_glQueryCounterEXT=(id,target)=>{GLctx.disjointTimerQueryExt["queryCounterEXT"](GL.queries[id],target)};var _emscripten_glReadBuffer=x0=>GLctx.readBuffer(x0);var computeUnpackAlignedImageSize=(width,height,sizePerPixel)=>{function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=(GL.unpackRowLength||width)*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,GL.unpackAlignment);return height*alignedRowSize};var colorChannelsInGlTextureFormat=format=>{var colorChannels={5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4};return colorChannels[format-6402]||1};var heapObjectForWebGLType=type=>{type-=5120;if(type==0)return HEAP8;if(type==1)return HEAPU8;if(type==2)return HEAP16;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922||type==28520||type==30779||type==30782)return HEAPU32;return HEAPU16};var toTypedArrayIndex=(pointer,heap)=>pointer>>>31-Math.clz32(heap.BYTES_PER_ELEMENT);var emscriptenWebGLGetTexPixelData=(type,format,width,height,pixels,internalFormat)=>{var heap=heapObjectForWebGLType(type);var sizePerPixel=colorChannelsInGlTextureFormat(format)*heap.BYTES_PER_ELEMENT;var bytes=computeUnpackAlignedImageSize(width,height,sizePerPixel);return heap.subarray(toTypedArrayIndex(pixels,heap),toTypedArrayIndex(pixels+bytes,heap))};var _emscripten_glReadPixels=(x,y,width,height,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels);return}var heap=heapObjectForWebGLType(type);var target=toTypedArrayIndex(pixels,heap);GLctx.readPixels(x,y,width,height,format,type,heap,target);return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)};var _emscripten_glReleaseShaderCompiler=()=>{};var _emscripten_glRenderbufferStorage=(x0,x1,x2,x3)=>GLctx.renderbufferStorage(x0,x1,x2,x3);var _emscripten_glRenderbufferStorageMultisample=(x0,x1,x2,x3,x4)=>GLctx.renderbufferStorageMultisample(x0,x1,x2,x3,x4);var _emscripten_glResumeTransformFeedback=()=>GLctx.resumeTransformFeedback();var _emscripten_glSampleCoverage=(value,invert)=>{GLctx.sampleCoverage(value,!!invert)};var _emscripten_glSamplerParameterf=(sampler,pname,param)=>{GLctx.samplerParameterf(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameterfv=(sampler,pname,params)=>{var param=HEAPF32[params>>2];GLctx.samplerParameterf(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameteri=(sampler,pname,param)=>{GLctx.samplerParameteri(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameteriv=(sampler,pname,params)=>{var param=HEAP32[params>>2];GLctx.samplerParameteri(GL.samplers[sampler],pname,param)};var _emscripten_glScissor=(x0,x1,x2,x3)=>GLctx.scissor(x0,x1,x2,x3);var _emscripten_glShaderBinary=(count,shaders,binaryformat,binary,length)=>{GL.recordError(1280)};var _emscripten_glShaderSource=(shader,count,string,length)=>{var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)};var _emscripten_glStencilFunc=(x0,x1,x2)=>GLctx.stencilFunc(x0,x1,x2);var _emscripten_glStencilFuncSeparate=(x0,x1,x2,x3)=>GLctx.stencilFuncSeparate(x0,x1,x2,x3);var _emscripten_glStencilMask=x0=>GLctx.stencilMask(x0);var _emscripten_glStencilMaskSeparate=(x0,x1)=>GLctx.stencilMaskSeparate(x0,x1);var _emscripten_glStencilOp=(x0,x1,x2)=>GLctx.stencilOp(x0,x1,x2);var _emscripten_glStencilOpSeparate=(x0,x1,x2,x3)=>GLctx.stencilOpSeparate(x0,x1,x2,x3);var _emscripten_glTexImage2D=(target,level,internalFormat,width,height,border,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels);return}if(pixels){var heap=heapObjectForWebGLType(type);var index=toTypedArrayIndex(pixels,heap);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,index);return}}var pixelData=pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null;GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixelData)};var _emscripten_glTexImage3D=(target,level,internalFormat,width,height,depth,border,format,type,pixels)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,heap,toTypedArrayIndex(pixels,heap))}else{GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,null)}};var _emscripten_glTexParameterf=(x0,x1,x2)=>GLctx.texParameterf(x0,x1,x2);var _emscripten_glTexParameterfv=(target,pname,params)=>{var param=HEAPF32[params>>2];GLctx.texParameterf(target,pname,param)};var _emscripten_glTexParameteri=(x0,x1,x2)=>GLctx.texParameteri(x0,x1,x2);var _emscripten_glTexParameteriv=(target,pname,params)=>{var param=HEAP32[params>>2];GLctx.texParameteri(target,pname,param)};var _emscripten_glTexStorage2D=(x0,x1,x2,x3,x4)=>GLctx.texStorage2D(x0,x1,x2,x3,x4);var _emscripten_glTexStorage3D=(x0,x1,x2,x3,x4,x5)=>GLctx.texStorage3D(x0,x1,x2,x3,x4,x5);var _emscripten_glTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels);return}if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,heap,toTypedArrayIndex(pixels,heap));return}}var pixelData=pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0):null;GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)};var _emscripten_glTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,toTypedArrayIndex(pixels,heap))}else{GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}};var _emscripten_glTransformFeedbackVaryings=(program,count,varyings,bufferMode)=>{program=GL.programs[program];var vars=[];for(var i=0;i>2]));GLctx.transformFeedbackVaryings(program,vars,bufferMode)};var _emscripten_glUniform1f=(location,v0)=>{GLctx.uniform1f(webglGetUniformLocation(location),v0)};var miniTempWebGLFloatBuffers=[];var _emscripten_glUniform1fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform1fv(webglGetUniformLocation(location),HEAPF32,value>>2,count);return}if(count<=288){var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform1i=(location,v0)=>{GLctx.uniform1i(webglGetUniformLocation(location),v0)};var miniTempWebGLIntBuffers=[];var _emscripten_glUniform1iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count);return}if(count<=288){var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform1ui=(location,v0)=>{GLctx.uniform1ui(webglGetUniformLocation(location),v0)};var _emscripten_glUniform1uiv=(location,count,value)=>{count&&GLctx.uniform1uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count)};var _emscripten_glUniform2f=(location,v0,v1)=>{GLctx.uniform2f(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2);return}if(count<=144){count*=2;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform2i=(location,v0,v1)=>{GLctx.uniform2i(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2);return}if(count<=144){count*=2;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform2ui=(location,v0,v1)=>{GLctx.uniform2ui(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2uiv=(location,count,value)=>{count&&GLctx.uniform2uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*2)};var _emscripten_glUniform3f=(location,v0,v1,v2)=>{GLctx.uniform3f(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3);return}if(count<=96){count*=3;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform3i=(location,v0,v1,v2)=>{GLctx.uniform3i(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform3iv(webglGetUniformLocation(location),HEAP32,value>>2,count*3);return}if(count<=96){count*=3;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform3ui=(location,v0,v1,v2)=>{GLctx.uniform3ui(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3uiv=(location,count,value)=>{count&&GLctx.uniform3uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*3)};var _emscripten_glUniform4f=(location,v0,v1,v2,v3)=>{GLctx.uniform4f(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4);return}if(count<=72){var view=miniTempWebGLFloatBuffers[4*count];var heap=HEAPF32;value=value>>2;count*=4;for(var i=0;i>2,value+count*16>>2)}GLctx.uniform4fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform4i=(location,v0,v1,v2,v3)=>{GLctx.uniform4i(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform4iv(webglGetUniformLocation(location),HEAP32,value>>2,count*4);return}if(count<=72){count*=4;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2];view[i+3]=HEAP32[value+(4*i+12)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform4ui=(location,v0,v1,v2,v3)=>{GLctx.uniform4ui(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4uiv=(location,count,value)=>{count&&GLctx.uniform4uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*4)};var _emscripten_glUniformBlockBinding=(program,uniformBlockIndex,uniformBlockBinding)=>{program=GL.programs[program];GLctx.uniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding)};var _emscripten_glUniformMatrix2fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*4);return}if(count<=72){count*=4;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix2x3fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix2x3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*6)};var _emscripten_glUniformMatrix2x4fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix2x4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*8)};var _emscripten_glUniformMatrix3fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9);return}if(count<=32){count*=9;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix3x2fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix3x2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*6)};var _emscripten_glUniformMatrix3x4fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix3x4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*12)};var _emscripten_glUniformMatrix4fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16);return}if(count<=18){var view=miniTempWebGLFloatBuffers[16*count];var heap=HEAPF32;value=value>>2;count*=16;for(var i=0;i>2,value+count*64>>2)}GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix4x2fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix4x2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*8)};var _emscripten_glUniformMatrix4x3fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix4x3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*12)};var _emscripten_glUseProgram=program=>{program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program};var _emscripten_glValidateProgram=program=>{GLctx.validateProgram(GL.programs[program])};var _emscripten_glVertexAttrib1f=(x0,x1)=>GLctx.vertexAttrib1f(x0,x1);var _emscripten_glVertexAttrib1fv=(index,v)=>{GLctx.vertexAttrib1f(index,HEAPF32[v>>2])};var _emscripten_glVertexAttrib2f=(x0,x1,x2)=>GLctx.vertexAttrib2f(x0,x1,x2);var _emscripten_glVertexAttrib2fv=(index,v)=>{GLctx.vertexAttrib2f(index,HEAPF32[v>>2],HEAPF32[v+4>>2])};var _emscripten_glVertexAttrib3f=(x0,x1,x2,x3)=>GLctx.vertexAttrib3f(x0,x1,x2,x3);var _emscripten_glVertexAttrib3fv=(index,v)=>{GLctx.vertexAttrib3f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2])};var _emscripten_glVertexAttrib4f=(x0,x1,x2,x3,x4)=>GLctx.vertexAttrib4f(x0,x1,x2,x3,x4);var _emscripten_glVertexAttrib4fv=(index,v)=>{GLctx.vertexAttrib4f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2],HEAPF32[v+12>>2])};var _emscripten_glVertexAttribDivisor=(index,divisor)=>{GLctx.vertexAttribDivisor(index,divisor)};var _glVertexAttribDivisor=_emscripten_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorANGLE=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorARB=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorEXT=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorNV=_glVertexAttribDivisor;var _emscripten_glVertexAttribI4i=(x0,x1,x2,x3,x4)=>GLctx.vertexAttribI4i(x0,x1,x2,x3,x4);var _emscripten_glVertexAttribI4iv=(index,v)=>{GLctx.vertexAttribI4i(index,HEAP32[v>>2],HEAP32[v+4>>2],HEAP32[v+8>>2],HEAP32[v+12>>2])};var _emscripten_glVertexAttribI4ui=(x0,x1,x2,x3,x4)=>GLctx.vertexAttribI4ui(x0,x1,x2,x3,x4);var _emscripten_glVertexAttribI4uiv=(index,v)=>{GLctx.vertexAttribI4ui(index,HEAPU32[v>>2],HEAPU32[v+4>>2],HEAPU32[v+8>>2],HEAPU32[v+12>>2])};var _emscripten_glVertexAttribIPointer=(index,size,type,stride,ptr)=>{GLctx.vertexAttribIPointer(index,size,type,stride,ptr)};var _emscripten_glVertexAttribPointer=(index,size,type,normalized,stride,ptr)=>{GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)};var _emscripten_glViewport=(x0,x1,x2,x3)=>GLctx.viewport(x0,x1,x2,x3);var _emscripten_glWaitSync=(sync,flags,timeout)=>{timeout=Number(timeout);GLctx.waitSync(GL.syncs[sync],flags,timeout)};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var _emscripten_request_animation_frame_loop=(cb,userData)=>{function tick(timeStamp){if(getWasmTableEntry(cb)(timeStamp,userData)){requestAnimationFrame(tick)}}return requestAnimationFrame(tick)};var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var _glGetIntegerv=_emscripten_glGetIntegerv;var _glGetString=_emscripten_glGetString;var _glGetStringi=_emscripten_glGetStringi;var _llvm_eh_typeid_for=type=>type;function _random_get(buffer,size){try{randomFill(HEAPU8.subarray(buffer,buffer+size));return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();for(let i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var miniTempWebGLFloatBuffersStorage=new Float32Array(288);for(var i=0;i<=288;++i){miniTempWebGLFloatBuffers[i]=miniTempWebGLFloatBuffersStorage.subarray(0,i)}var miniTempWebGLIntBuffersStorage=new Int32Array(288);for(var i=0;i<=288;++i){miniTempWebGLIntBuffers[i]=miniTempWebGLIntBuffersStorage.subarray(0,i)}{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["preloadPlugins"])preloadPlugins=Module["preloadPlugins"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;Module["GL"]=GL;var _malloc,_add_font,_add_image,_allocate,_apply_scene_transactions,_command,_deallocate,_destroy,_devtools_rendering_set_show_fps_meter,_devtools_rendering_set_show_hit_testing,_devtools_rendering_set_show_ruler,_devtools_rendering_set_show_stats,_devtools_rendering_set_show_tiles,_export_node_as,_get_default_fallback_fonts,_get_image_bytes,_get_image_size,_get_node_absolute_bounding_box,_get_node_id_from_point,_get_node_ids_from_envelope,_get_node_ids_from_point,_grida_fonts_analyze_family,_grida_fonts_free,_grida_fonts_parse_font,_grida_markdown_to_html,_grida_svg_optimize,_grida_svg_pack,_has_missing_fonts,_highlight_strokes,_init,_init_with_backend,_list_available_fonts,_list_missing_fonts,_load_benchmark_scene,_load_dummy_scene,_load_scene_json,_pointer_move,_redraw,_resize_surface,_runtime_renderer_set_cache_tile,_runtime_renderer_set_outline_mode,_runtime_renderer_set_pixel_preview_scale,_runtime_renderer_set_pixel_preview_stable,_runtime_renderer_set_render_policy_flags,_set_debug,_set_default_fallback_fonts,_set_main_camera_transform,_set_verbose,_tick,_to_vector_network,_toggle_debug,_main,_emscripten_builtin_memalign,_setThrew,__emscripten_tempret_set,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,___cxa_decrement_exception_refcount,___cxa_increment_exception_refcount,___cxa_can_catch,___cxa_get_exception_ptr,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){_malloc=wasmExports["Mg"];_add_font=Module["_add_font"]=wasmExports["Og"];_add_image=Module["_add_image"]=wasmExports["Pg"];_allocate=Module["_allocate"]=wasmExports["Qg"];_apply_scene_transactions=Module["_apply_scene_transactions"]=wasmExports["Rg"];_command=Module["_command"]=wasmExports["Sg"];_deallocate=Module["_deallocate"]=wasmExports["Tg"];_destroy=Module["_destroy"]=wasmExports["Ug"];_devtools_rendering_set_show_fps_meter=Module["_devtools_rendering_set_show_fps_meter"]=wasmExports["Vg"];_devtools_rendering_set_show_hit_testing=Module["_devtools_rendering_set_show_hit_testing"]=wasmExports["Wg"];_devtools_rendering_set_show_ruler=Module["_devtools_rendering_set_show_ruler"]=wasmExports["Xg"];_devtools_rendering_set_show_stats=Module["_devtools_rendering_set_show_stats"]=wasmExports["Yg"];_devtools_rendering_set_show_tiles=Module["_devtools_rendering_set_show_tiles"]=wasmExports["Zg"];_export_node_as=Module["_export_node_as"]=wasmExports["_g"];_get_default_fallback_fonts=Module["_get_default_fallback_fonts"]=wasmExports["$g"];_get_image_bytes=Module["_get_image_bytes"]=wasmExports["ah"];_get_image_size=Module["_get_image_size"]=wasmExports["bh"];_get_node_absolute_bounding_box=Module["_get_node_absolute_bounding_box"]=wasmExports["ch"];_get_node_id_from_point=Module["_get_node_id_from_point"]=wasmExports["dh"];_get_node_ids_from_envelope=Module["_get_node_ids_from_envelope"]=wasmExports["eh"];_get_node_ids_from_point=Module["_get_node_ids_from_point"]=wasmExports["fh"];_grida_fonts_analyze_family=Module["_grida_fonts_analyze_family"]=wasmExports["gh"];_grida_fonts_free=Module["_grida_fonts_free"]=wasmExports["hh"];_grida_fonts_parse_font=Module["_grida_fonts_parse_font"]=wasmExports["ih"];_grida_markdown_to_html=Module["_grida_markdown_to_html"]=wasmExports["jh"];_grida_svg_optimize=Module["_grida_svg_optimize"]=wasmExports["kh"];_grida_svg_pack=Module["_grida_svg_pack"]=wasmExports["lh"];_has_missing_fonts=Module["_has_missing_fonts"]=wasmExports["mh"];_highlight_strokes=Module["_highlight_strokes"]=wasmExports["nh"];_init=Module["_init"]=wasmExports["oh"];_init_with_backend=Module["_init_with_backend"]=wasmExports["ph"];_list_available_fonts=Module["_list_available_fonts"]=wasmExports["qh"];_list_missing_fonts=Module["_list_missing_fonts"]=wasmExports["rh"];_load_benchmark_scene=Module["_load_benchmark_scene"]=wasmExports["sh"];_load_dummy_scene=Module["_load_dummy_scene"]=wasmExports["th"];_load_scene_json=Module["_load_scene_json"]=wasmExports["uh"];_pointer_move=Module["_pointer_move"]=wasmExports["vh"];_redraw=Module["_redraw"]=wasmExports["wh"];_resize_surface=Module["_resize_surface"]=wasmExports["xh"];_runtime_renderer_set_cache_tile=Module["_runtime_renderer_set_cache_tile"]=wasmExports["yh"];_runtime_renderer_set_outline_mode=Module["_runtime_renderer_set_outline_mode"]=wasmExports["zh"];_runtime_renderer_set_pixel_preview_scale=Module["_runtime_renderer_set_pixel_preview_scale"]=wasmExports["Ah"];_runtime_renderer_set_pixel_preview_stable=Module["_runtime_renderer_set_pixel_preview_stable"]=wasmExports["Bh"];_runtime_renderer_set_render_policy_flags=Module["_runtime_renderer_set_render_policy_flags"]=wasmExports["Ch"];_set_debug=Module["_set_debug"]=wasmExports["Dh"];_set_default_fallback_fonts=Module["_set_default_fallback_fonts"]=wasmExports["Eh"];_set_main_camera_transform=Module["_set_main_camera_transform"]=wasmExports["Fh"];_set_verbose=Module["_set_verbose"]=wasmExports["Gh"];_tick=Module["_tick"]=wasmExports["Hh"];_to_vector_network=Module["_to_vector_network"]=wasmExports["Ih"];_toggle_debug=Module["_toggle_debug"]=wasmExports["Jh"];_main=Module["_main"]=wasmExports["Kh"];_emscripten_builtin_memalign=wasmExports["Lh"];_setThrew=wasmExports["Mh"];__emscripten_tempret_set=wasmExports["Nh"];__emscripten_stack_restore=wasmExports["Oh"];__emscripten_stack_alloc=wasmExports["Ph"];_emscripten_stack_get_current=wasmExports["Qh"];___cxa_decrement_exception_refcount=wasmExports["Rh"];___cxa_increment_exception_refcount=wasmExports["Sh"];___cxa_can_catch=wasmExports["Th"];___cxa_get_exception_ptr=wasmExports["Uh"];memory=wasmMemory=wasmExports["Kg"];__indirect_function_table=wasmTable=wasmExports["Ng"]}var wasmImports={F:___cxa_begin_catch,L:___cxa_end_catch,a:___cxa_find_matching_catch_2,n:___cxa_find_matching_catch_3,ba:___cxa_find_matching_catch_4,za:___cxa_rethrow,G:___cxa_throw,db:___cxa_uncaught_exceptions,e:___resumeException,Ca:___syscall_fcntl64,xb:___syscall_fstat64,tb:___syscall_getcwd,kb:___syscall_getdents64,yb:___syscall_ioctl,ub:___syscall_lstat64,vb:___syscall_newfstatat,Da:___syscall_openat,jb:___syscall_readlinkat,wb:___syscall_stat64,Bb:__abort_js,fb:__emscripten_throw_longjmp,ob:__gmtime_js,mb:__mmap_js,nb:__munmap_js,Cb:__tzset_js,Ab:_clock_time_get,zb:_emscripten_date_now,ib:_emscripten_get_heap_max,$:_emscripten_get_now,Df:_emscripten_glActiveTexture,Ef:_emscripten_glAttachShader,ge:_emscripten_glBeginQuery,ae:_emscripten_glBeginQueryEXT,Hc:_emscripten_glBeginTransformFeedback,Ff:_emscripten_glBindAttribLocation,Gf:_emscripten_glBindBuffer,Ec:_emscripten_glBindBufferBase,Fc:_emscripten_glBindBufferRange,Ee:_emscripten_glBindFramebuffer,Fe:_emscripten_glBindRenderbuffer,me:_emscripten_glBindSampler,Hf:_emscripten_glBindTexture,Ub:_emscripten_glBindTransformFeedback,_e:_emscripten_glBindVertexArray,bf:_emscripten_glBindVertexArrayOES,If:_emscripten_glBlendColor,Jf:_emscripten_glBlendEquation,Md:_emscripten_glBlendEquationSeparate,Kf:_emscripten_glBlendFunc,Ld:_emscripten_glBlendFuncSeparate,ye:_emscripten_glBlitFramebuffer,Lf:_emscripten_glBufferData,Mf:_emscripten_glBufferSubData,Ge:_emscripten_glCheckFramebufferStatus,Of:_emscripten_glClear,hc:_emscripten_glClearBufferfi,ic:_emscripten_glClearBufferfv,kc:_emscripten_glClearBufferiv,jc:_emscripten_glClearBufferuiv,Pf:_emscripten_glClearColor,Kd:_emscripten_glClearDepthf,Qf:_emscripten_glClearStencil,ve:_emscripten_glClientWaitSync,bd:_emscripten_glClipControlEXT,Rf:_emscripten_glColorMask,Sf:_emscripten_glCompileShader,Tf:_emscripten_glCompressedTexImage2D,Uc:_emscripten_glCompressedTexImage3D,Uf:_emscripten_glCompressedTexSubImage2D,Tc:_emscripten_glCompressedTexSubImage3D,xe:_emscripten_glCopyBufferSubData,Jd:_emscripten_glCopyTexImage2D,Vf:_emscripten_glCopyTexSubImage2D,Vc:_emscripten_glCopyTexSubImage3D,Wf:_emscripten_glCreateProgram,Xf:_emscripten_glCreateShader,Yf:_emscripten_glCullFace,Zf:_emscripten_glDeleteBuffers,He:_emscripten_glDeleteFramebuffers,_f:_emscripten_glDeleteProgram,he:_emscripten_glDeleteQueries,be:_emscripten_glDeleteQueriesEXT,Ie:_emscripten_glDeleteRenderbuffers,ne:_emscripten_glDeleteSamplers,$f:_emscripten_glDeleteShader,we:_emscripten_glDeleteSync,ag:_emscripten_glDeleteTextures,Tb:_emscripten_glDeleteTransformFeedbacks,$e:_emscripten_glDeleteVertexArrays,cf:_emscripten_glDeleteVertexArraysOES,Id:_emscripten_glDepthFunc,bg:_emscripten_glDepthMask,Hd:_emscripten_glDepthRangef,Gd:_emscripten_glDetachShader,cg:_emscripten_glDisable,dg:_emscripten_glDisableVertexAttribArray,eg:_emscripten_glDrawArrays,Ye:_emscripten_glDrawArraysInstanced,Pd:_emscripten_glDrawArraysInstancedANGLE,Fb:_emscripten_glDrawArraysInstancedARB,Ve:_emscripten_glDrawArraysInstancedBaseInstanceWEBGL,_c:_emscripten_glDrawArraysInstancedEXT,Gb:_emscripten_glDrawArraysInstancedNV,Te:_emscripten_glDrawBuffers,Yc:_emscripten_glDrawBuffersEXT,Qd:_emscripten_glDrawBuffersWEBGL,fg:_emscripten_glDrawElements,Ze:_emscripten_glDrawElementsInstanced,Od:_emscripten_glDrawElementsInstancedANGLE,Db:_emscripten_glDrawElementsInstancedARB,We:_emscripten_glDrawElementsInstancedBaseVertexBaseInstanceWEBGL,Eb:_emscripten_glDrawElementsInstancedEXT,Zc:_emscripten_glDrawElementsInstancedNV,Ne:_emscripten_glDrawRangeElements,gg:_emscripten_glEnable,hg:_emscripten_glEnableVertexAttribArray,ie:_emscripten_glEndQuery,ce:_emscripten_glEndQueryEXT,Gc:_emscripten_glEndTransformFeedback,se:_emscripten_glFenceSync,ig:_emscripten_glFinish,jg:_emscripten_glFlush,Je:_emscripten_glFramebufferRenderbuffer,Ke:_emscripten_glFramebufferTexture2D,Kc:_emscripten_glFramebufferTextureLayer,kg:_emscripten_glFrontFace,lg:_emscripten_glGenBuffers,Le:_emscripten_glGenFramebuffers,je:_emscripten_glGenQueries,de:_emscripten_glGenQueriesEXT,Me:_emscripten_glGenRenderbuffers,oe:_emscripten_glGenSamplers,mg:_emscripten_glGenTextures,Sb:_emscripten_glGenTransformFeedbacks,Xe:_emscripten_glGenVertexArrays,df:_emscripten_glGenVertexArraysOES,Ae:_emscripten_glGenerateMipmap,Fd:_emscripten_glGetActiveAttrib,Ed:_emscripten_glGetActiveUniform,cc:_emscripten_glGetActiveUniformBlockName,dc:_emscripten_glGetActiveUniformBlockiv,fc:_emscripten_glGetActiveUniformsiv,Dd:_emscripten_glGetAttachedShaders,Cd:_emscripten_glGetAttribLocation,Bd:_emscripten_glGetBooleanv,Zb:_emscripten_glGetBufferParameteri64v,ng:_emscripten_glGetBufferParameteriv,og:_emscripten_glGetError,pg:_emscripten_glGetFloatv,uc:_emscripten_glGetFragDataLocation,Be:_emscripten_glGetFramebufferAttachmentParameteriv,_b:_emscripten_glGetInteger64i_v,ac:_emscripten_glGetInteger64v,Ic:_emscripten_glGetIntegeri_v,qg:_emscripten_glGetIntegerv,Jb:_emscripten_glGetInternalformativ,Nb:_emscripten_glGetProgramBinary,rg:_emscripten_glGetProgramInfoLog,sg:_emscripten_glGetProgramiv,Zd:_emscripten_glGetQueryObjecti64vEXT,Sd:_emscripten_glGetQueryObjectivEXT,_d:_emscripten_glGetQueryObjectui64vEXT,ke:_emscripten_glGetQueryObjectuiv,ee:_emscripten_glGetQueryObjectuivEXT,le:_emscripten_glGetQueryiv,fe:_emscripten_glGetQueryivEXT,Ce:_emscripten_glGetRenderbufferParameteriv,Vb:_emscripten_glGetSamplerParameterfv,Wb:_emscripten_glGetSamplerParameteriv,tg:_emscripten_glGetShaderInfoLog,Wd:_emscripten_glGetShaderPrecisionFormat,Ad:_emscripten_glGetShaderSource,ug:_emscripten_glGetShaderiv,vg:_emscripten_glGetString,af:_emscripten_glGetStringi,$b:_emscripten_glGetSynciv,zd:_emscripten_glGetTexParameterfv,yd:_emscripten_glGetTexParameteriv,Cc:_emscripten_glGetTransformFeedbackVarying,ec:_emscripten_glGetUniformBlockIndex,gc:_emscripten_glGetUniformIndices,wg:_emscripten_glGetUniformLocation,xd:_emscripten_glGetUniformfv,wd:_emscripten_glGetUniformiv,vc:_emscripten_glGetUniformuiv,Bc:_emscripten_glGetVertexAttribIiv,Ac:_emscripten_glGetVertexAttribIuiv,td:_emscripten_glGetVertexAttribPointerv,vd:_emscripten_glGetVertexAttribfv,ud:_emscripten_glGetVertexAttribiv,sd:_emscripten_glHint,Xd:_emscripten_glInvalidateFramebuffer,Yd:_emscripten_glInvalidateSubFramebuffer,rd:_emscripten_glIsBuffer,qd:_emscripten_glIsEnabled,pd:_emscripten_glIsFramebuffer,od:_emscripten_glIsProgram,Sc:_emscripten_glIsQuery,Td:_emscripten_glIsQueryEXT,nd:_emscripten_glIsRenderbuffer,Yb:_emscripten_glIsSampler,md:_emscripten_glIsShader,te:_emscripten_glIsSync,xg:_emscripten_glIsTexture,Rb:_emscripten_glIsTransformFeedback,Jc:_emscripten_glIsVertexArray,Rd:_emscripten_glIsVertexArrayOES,yg:_emscripten_glLineWidth,zg:_emscripten_glLinkProgram,Re:_emscripten_glMultiDrawArraysInstancedBaseInstanceWEBGL,Se:_emscripten_glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL,Qb:_emscripten_glPauseTransformFeedback,Ag:_emscripten_glPixelStorei,ad:_emscripten_glPolygonModeWEBGL,ld:_emscripten_glPolygonOffset,cd:_emscripten_glPolygonOffsetClampEXT,Mb:_emscripten_glProgramBinary,Lb:_emscripten_glProgramParameteri,$d:_emscripten_glQueryCounterEXT,Ue:_emscripten_glReadBuffer,Bg:_emscripten_glReadPixels,kd:_emscripten_glReleaseShaderCompiler,De:_emscripten_glRenderbufferStorage,ze:_emscripten_glRenderbufferStorageMultisample,Ob:_emscripten_glResumeTransformFeedback,jd:_emscripten_glSampleCoverage,pe:_emscripten_glSamplerParameterf,Xb:_emscripten_glSamplerParameterfv,qe:_emscripten_glSamplerParameteri,re:_emscripten_glSamplerParameteriv,Cg:_emscripten_glScissor,id:_emscripten_glShaderBinary,Dg:_emscripten_glShaderSource,Eg:_emscripten_glStencilFunc,Fg:_emscripten_glStencilFuncSeparate,Gg:_emscripten_glStencilMask,Hg:_emscripten_glStencilMaskSeparate,Ig:_emscripten_glStencilOp,Jg:_emscripten_glStencilOpSeparate,Ia:_emscripten_glTexImage2D,Xc:_emscripten_glTexImage3D,Ja:_emscripten_glTexParameterf,Ka:_emscripten_glTexParameterfv,La:_emscripten_glTexParameteri,Ma:_emscripten_glTexParameteriv,Oe:_emscripten_glTexStorage2D,Kb:_emscripten_glTexStorage3D,Na:_emscripten_glTexSubImage2D,Wc:_emscripten_glTexSubImage3D,Dc:_emscripten_glTransformFeedbackVaryings,Oa:_emscripten_glUniform1f,Pa:_emscripten_glUniform1fv,zf:_emscripten_glUniform1i,Af:_emscripten_glUniform1iv,tc:_emscripten_glUniform1ui,oc:_emscripten_glUniform1uiv,Bf:_emscripten_glUniform2f,Cf:_emscripten_glUniform2fv,yf:_emscripten_glUniform2i,xf:_emscripten_glUniform2iv,rc:_emscripten_glUniform2ui,nc:_emscripten_glUniform2uiv,wf:_emscripten_glUniform3f,vf:_emscripten_glUniform3fv,uf:_emscripten_glUniform3i,tf:_emscripten_glUniform3iv,qc:_emscripten_glUniform3ui,mc:_emscripten_glUniform3uiv,sf:_emscripten_glUniform4f,rf:_emscripten_glUniform4fv,ef:_emscripten_glUniform4i,ff:_emscripten_glUniform4iv,pc:_emscripten_glUniform4ui,lc:_emscripten_glUniform4uiv,bc:_emscripten_glUniformBlockBinding,gf:_emscripten_glUniformMatrix2fv,Rc:_emscripten_glUniformMatrix2x3fv,Pc:_emscripten_glUniformMatrix2x4fv,hf:_emscripten_glUniformMatrix3fv,Qc:_emscripten_glUniformMatrix3x2fv,Mc:_emscripten_glUniformMatrix3x4fv,jf:_emscripten_glUniformMatrix4fv,Oc:_emscripten_glUniformMatrix4x2fv,Lc:_emscripten_glUniformMatrix4x3fv,kf:_emscripten_glUseProgram,hd:_emscripten_glValidateProgram,lf:_emscripten_glVertexAttrib1f,gd:_emscripten_glVertexAttrib1fv,fd:_emscripten_glVertexAttrib2f,mf:_emscripten_glVertexAttrib2fv,ed:_emscripten_glVertexAttrib3f,nf:_emscripten_glVertexAttrib3fv,dd:_emscripten_glVertexAttrib4f,of:_emscripten_glVertexAttrib4fv,Pe:_emscripten_glVertexAttribDivisor,Nd:_emscripten_glVertexAttribDivisorANGLE,Hb:_emscripten_glVertexAttribDivisorARB,$c:_emscripten_glVertexAttribDivisorEXT,Ib:_emscripten_glVertexAttribDivisorNV,zc:_emscripten_glVertexAttribI4i,xc:_emscripten_glVertexAttribI4iv,yc:_emscripten_glVertexAttribI4ui,wc:_emscripten_glVertexAttribI4uiv,Qe:_emscripten_glVertexAttribIPointer,pf:_emscripten_glVertexAttribPointer,qf:_emscripten_glViewport,ue:_emscripten_glWaitSync,_a:_emscripten_request_animation_frame_loop,gb:_emscripten_resize_heap,qb:_environ_get,rb:_environ_sizes_get,Sa:_exit,aa:_fd_close,lb:_fd_pread,Aa:_fd_read,pb:_fd_seek,ga:_fd_write,Qa:_glGetIntegerv,la:_glGetString,Ra:_glGetStringi,Ud:invoke_dd,Vd:invoke_dddd,xa:invoke_diii,Va:invoke_fdiiii,Ua:invoke_fdiiiii,Ta:invoke_fii,ya:invoke_fiii,s:invoke_fiiidi,S:invoke_fiiif,t:invoke_fiiiidi,r:invoke_i,j:invoke_ii,qa:invoke_iif,hb:invoke_iiffi,na:invoke_iiffiii,h:invoke_iii,Ga:invoke_iiiffii,f:invoke_iiii,l:invoke_iiiii,cb:invoke_iiiiid,y:invoke_iiiiii,x:invoke_iiiiiii,D:invoke_iiiiiiii,q:invoke_iiiiiiiii,ma:invoke_iiiiiiiiii,Y:invoke_iiiiiiiiiiii,ja:invoke_iiiiiiiiiiiifiii,pa:invoke_iijj,eb:invoke_j,da:invoke_ji,V:invoke_jiii,Z:invoke_jiiii,H:invoke_jjji,k:invoke_v,Nf:invoke_vff,b:invoke_vi,Q:invoke_vid,P:invoke_vif,u:invoke_viff,B:invoke_viffff,U:invoke_vifffff,Wa:invoke_viffffff,A:invoke_viffi,ea:invoke_viffiiiiiii,c:invoke_vii,Za:invoke_viidii,M:invoke_viif,E:invoke_viiff,wa:invoke_viififii,v:invoke_viifiiifi,d:invoke_viii,ka:invoke_viiif,z:invoke_viiiffi,I:invoke_viiiffiffii,Nc:invoke_viiifi,J:invoke_viiififiiiiiiiiiiii,i:invoke_viiii,Ya:invoke_viiiidididii,ha:invoke_viiiif,Ea:invoke_viiiiff,ta:invoke_viiiiffi,Ba:invoke_viiiifi,g:invoke_viiiii,Pb:invoke_viiiiif,Fa:invoke_viiiiiff,Xa:invoke_viiiiiffiii,$a:invoke_viiiiifi,m:invoke_viiiiii,p:invoke_viiiiiii,N:invoke_viiiiiiii,T:invoke_viiiiiiiii,K:invoke_viiiiiiiiii,oa:invoke_viiiiiiiiiii,X:invoke_viiiiiiiiiiiiiii,Ha:invoke_viiiiiji,ab:invoke_viiiijjiiiiff,O:invoke_viiij,w:invoke_viiijii,ca:invoke_viij,o:invoke_viiji,fa:invoke_viijiff,W:invoke_viijiiiif,ra:invoke_viijj,R:invoke_vij,va:invoke_vijff,bb:invoke_viji,C:invoke_vijii,ua:invoke_vijiifi,sa:invoke_vijiii,_:invoke_vijjjj,sc:invoke_vjii,ia:_llvm_eh_typeid_for,sb:_random_get};function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_v(index){var sp=stackSave();try{getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ji(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_viiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiij(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vij(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vff(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viij(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiji(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiff(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiif(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiji(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiffii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiff(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vid(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiifiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vif(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffi(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiifi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiff(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vjii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiffi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viifiiifi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiif(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiif(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiifi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiffi(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viififii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijiff(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijff(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viji(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiijjiiiiff(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijiifi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiffi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiifi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijj(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiififiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijiiiif(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiffiffii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iif(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jjji(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_iijj(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viif(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiffiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viff(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vifffff(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fiiidi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viidii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiidididii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fiiiidi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiffiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiijii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffff(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffffff(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fiiif(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fdiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fdiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_dddd(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_dd(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijjjj(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_j(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_iiiiid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_fiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_diii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function callMain(args=[]){var entryFunction=_main;args.unshift(thisProgram);var argc=args.length;var argv=stackAlloc((argc+1)*4);var argv_ptr=argv;for(var arg of args){HEAPU32[argv_ptr>>2]=stringToUTF8OnStack(arg);argv_ptr+=4}HEAPU32[argv_ptr>>2]=0;try{var ret=entryFunction(argc,argv);exitJS(ret,true);return ret}catch(e){return handleException(e)}}function run(args=arguments_){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();var noInitialRun=Module["noInitialRun"]||false;if(!noInitialRun)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +var createGridaCanvas=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else{}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["Lg"]();FS.ignorePermissions=false}function preMain(){}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("grida_canvas_wasm.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var exceptionCaught=[];var uncaughtExceptionCount=0;var ___cxa_begin_catch=ptr=>{var info=new ExceptionInfo(ptr);if(!info.get_caught()){info.set_caught(true);uncaughtExceptionCount--}info.set_rethrown(false);exceptionCaught.push(info);return ___cxa_get_exception_ptr(ptr)};var exceptionLast=0;var ___cxa_end_catch=()=>{_setThrew(0,0);var info=exceptionCaught.pop();___cxa_decrement_exception_refcount(info.excPtr);exceptionLast=0};class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var setTempRet0=val=>__emscripten_tempret_set(val);var findMatchingCatch=args=>{var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}for(var caughtType of args){if(caughtType===0||caughtType===thrownType){break}var adjusted_ptr_addr=info.ptr+16;if(___cxa_can_catch(caughtType,thrownType,adjusted_ptr_addr)){setTempRet0(caughtType);return thrown}}setTempRet0(thrownType);return thrown};var ___cxa_find_matching_catch_2=()=>findMatchingCatch([]);var ___cxa_find_matching_catch_3=arg0=>findMatchingCatch([arg0]);var ___cxa_find_matching_catch_4=(arg0,arg1)=>findMatchingCatch([arg0,arg1]);var ___cxa_rethrow=()=>{var info=exceptionCaught.pop();if(!info){abort("no exception to throw")}var ptr=info.excPtr;if(!info.get_rethrown()){exceptionCaught.push(info);info.set_rethrown(true);info.set_caught(false);uncaughtExceptionCount++}___cxa_increment_exception_refcount(ptr);exceptionLast=ptr;throw exceptionLast};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);___cxa_increment_exception_refcount(ptr);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};var ___cxa_uncaught_exceptions=()=>uncaughtExceptionCount;var ___resumeException=ptr=>{if(!exceptionLast){exceptionLast=ptr}throw exceptionLast};var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>crypto.getRandomValues(view)};var randomFill=view=>{(randomFill=initRandomFill())(view)};var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var zeroMemory=(ptr,size)=>HEAPU8.fill(0,ptr,ptr+size);var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var mmapAlloc=size=>{size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(ptr)zeroMemory(ptr,size);return ptr};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack=""}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,readFiles:{},ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);setattr(arg,attr)},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}for(var mount of mounts){if(mount.type.syncfs){mount.type.syncfs(mount,populate,done)}else{done(null)}}},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable)){while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS_modeStringToFlags(flags):flags;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}if(Module["logReadFiles"]&&!(flags&1)){if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){data=new Uint8Array(intArrayFromString(data,true))}if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{abort("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops)){stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}}function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var SYSCALLS={calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{return SYSCALLS.writeStat(buf,FS.fstat(fd))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);function ___syscall_getcwd(buf,size){try{if(size===0)return-28;var cwd=FS.cwd();var cwdLengthInBytes=lengthBytesUTF8(cwd)+1;if(size>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21537:case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.lstat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.writeStat(buf,nofollow?FS.lstat(path):FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_readlinkat(dirfd,path,buf,bufsize){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>abort("");var __emscripten_throw_longjmp=()=>{throw Infinity};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function __gmtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function __mmap_js(len,prot,flags,fd,offset,allocated,addr){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,offset,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffsetperformance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>3]=BigInt(nsec);return 0}var getHeapMax=()=>2147483648;var _emscripten_get_heap_max=()=>getHeapMax();var GLctx;var webgl_enable_ANGLE_instanced_arrays=ctx=>{var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=(index,divisor)=>ext["vertexAttribDivisorANGLE"](index,divisor);ctx["drawArraysInstanced"]=(mode,first,count,primcount)=>ext["drawArraysInstancedANGLE"](mode,first,count,primcount);ctx["drawElementsInstanced"]=(mode,count,type,indices,primcount)=>ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount);return 1}};var webgl_enable_OES_vertex_array_object=ctx=>{var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=()=>ext["createVertexArrayOES"]();ctx["deleteVertexArray"]=vao=>ext["deleteVertexArrayOES"](vao);ctx["bindVertexArray"]=vao=>ext["bindVertexArrayOES"](vao);ctx["isVertexArray"]=vao=>ext["isVertexArrayOES"](vao);return 1}};var webgl_enable_WEBGL_draw_buffers=ctx=>{var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=(n,bufs)=>ext["drawBuffersWEBGL"](n,bufs);return 1}};var webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"));var webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"));var webgl_enable_EXT_polygon_offset_clamp=ctx=>!!(ctx.extPolygonOffsetClamp=ctx.getExtension("EXT_polygon_offset_clamp"));var webgl_enable_EXT_clip_control=ctx=>!!(ctx.extClipControl=ctx.getExtension("EXT_clip_control"));var webgl_enable_WEBGL_polygon_mode=ctx=>!!(ctx.webglPolygonMode=ctx.getExtension("WEBGL_polygon_mode"));var webgl_enable_WEBGL_multi_draw=ctx=>!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"));var getEmscriptenSupportedExtensions=ctx=>{var supportedExtensions=["ANGLE_instanced_arrays","EXT_blend_minmax","EXT_disjoint_timer_query","EXT_frag_depth","EXT_shader_texture_lod","EXT_sRGB","OES_element_index_uint","OES_fbo_render_mipmap","OES_standard_derivatives","OES_texture_float","OES_texture_half_float","OES_texture_half_float_linear","OES_vertex_array_object","WEBGL_color_buffer_float","WEBGL_depth_texture","WEBGL_draw_buffers","EXT_color_buffer_float","EXT_conservative_depth","EXT_disjoint_timer_query_webgl2","EXT_texture_norm16","NV_shader_noperspective_interpolation","WEBGL_clip_cull_distance","EXT_clip_control","EXT_color_buffer_half_float","EXT_depth_clamp","EXT_float_blend","EXT_polygon_offset_clamp","EXT_texture_compression_bptc","EXT_texture_compression_rgtc","EXT_texture_filter_anisotropic","KHR_parallel_shader_compile","OES_texture_float_linear","WEBGL_blend_func_extended","WEBGL_compressed_texture_astc","WEBGL_compressed_texture_etc","WEBGL_compressed_texture_etc1","WEBGL_compressed_texture_s3tc","WEBGL_compressed_texture_s3tc_srgb","WEBGL_debug_renderer_info","WEBGL_debug_shaders","WEBGL_lose_context","WEBGL_multi_draw","WEBGL_polygon_mode"];return(ctx.getSupportedExtensions()||[]).filter(ext=>supportedExtensions.includes(ext))};var GL={counter:1,buffers:[],programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],stringCache:{},stringiCache:{},unpackAlignment:4,unpackRowLength:0,recordError:errorCode=>{if(!GL.lastError){GL.lastError=errorCode}},getNewId:table=>{var ret=GL.counter++;for(var i=table.length;i{for(var i=0;i>2]=id}},getSource:(shader,count,string,length)=>{var source="";for(var i=0;i>2]:undefined;source+=UTF8ToString(HEAPU32[string+i*4>>2],len)}return source},createContext:(canvas,webGLContextAttributes)=>{if(!canvas.getContextSafariWebGL2Fixed){canvas.getContextSafariWebGL2Fixed=canvas.getContext;function fixedGetContext(ver,attrs){var gl=canvas.getContextSafariWebGL2Fixed(ver,attrs);return ver=="webgl"==gl instanceof WebGLRenderingContext?gl:null}canvas.getContext=fixedGetContext}var ctx=webGLContextAttributes.majorVersion>1?canvas.getContext("webgl2",webGLContextAttributes):canvas.getContext("webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},registerContext:(ctx,webGLContextAttributes)=>{var handle=GL.getNewId(GL.contexts);var context={handle,attributes:webGLContextAttributes,version:webGLContextAttributes.majorVersion,GLctx:ctx};if(ctx.canvas)ctx.canvas.GLctxObject=context;GL.contexts[handle]=context;if(typeof webGLContextAttributes.enableExtensionsByDefault=="undefined"||webGLContextAttributes.enableExtensionsByDefault){GL.initExtensions(context)}return handle},makeContextCurrent:contextHandle=>{GL.currentContext=GL.contexts[contextHandle];Module["ctx"]=GLctx=GL.currentContext?.GLctx;return!(contextHandle&&!GLctx)},getContext:contextHandle=>GL.contexts[contextHandle],deleteContext:contextHandle=>{if(GL.currentContext===GL.contexts[contextHandle]){GL.currentContext=null}if(typeof JSEvents=="object"){JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas)}if(GL.contexts[contextHandle]?.GLctx.canvas){GL.contexts[contextHandle].GLctx.canvas.GLctxObject=undefined}GL.contexts[contextHandle]=null},initExtensions:context=>{context||=GL.currentContext;if(context.initExtensionsDone)return;context.initExtensionsDone=true;var GLctx=context.GLctx;webgl_enable_WEBGL_multi_draw(GLctx);webgl_enable_EXT_polygon_offset_clamp(GLctx);webgl_enable_EXT_clip_control(GLctx);webgl_enable_WEBGL_polygon_mode(GLctx);webgl_enable_ANGLE_instanced_arrays(GLctx);webgl_enable_OES_vertex_array_object(GLctx);webgl_enable_WEBGL_draw_buffers(GLctx);webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx);webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx);if(context.version>=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}for(var ext of getEmscriptenSupportedExtensions(GLctx)){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}}}};var _emscripten_glActiveTexture=x0=>GLctx.activeTexture(x0);var _emscripten_glAttachShader=(program,shader)=>{GLctx.attachShader(GL.programs[program],GL.shaders[shader])};var _emscripten_glBeginQuery=(target,id)=>{GLctx.beginQuery(target,GL.queries[id])};var _emscripten_glBeginQueryEXT=(target,id)=>{GLctx.disjointTimerQueryExt["beginQueryEXT"](target,GL.queries[id])};var _emscripten_glBeginTransformFeedback=x0=>GLctx.beginTransformFeedback(x0);var _emscripten_glBindAttribLocation=(program,index,name)=>{GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))};var _emscripten_glBindBuffer=(target,buffer)=>{if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])};var _emscripten_glBindBufferBase=(target,index,buffer)=>{GLctx.bindBufferBase(target,index,GL.buffers[buffer])};var _emscripten_glBindBufferRange=(target,index,buffer,offset,ptrsize)=>{GLctx.bindBufferRange(target,index,GL.buffers[buffer],offset,ptrsize)};var _emscripten_glBindFramebuffer=(target,framebuffer)=>{GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])};var _emscripten_glBindRenderbuffer=(target,renderbuffer)=>{GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])};var _emscripten_glBindSampler=(unit,sampler)=>{GLctx.bindSampler(unit,GL.samplers[sampler])};var _emscripten_glBindTexture=(target,texture)=>{GLctx.bindTexture(target,GL.textures[texture])};var _emscripten_glBindTransformFeedback=(target,id)=>{GLctx.bindTransformFeedback(target,GL.transformFeedbacks[id])};var _emscripten_glBindVertexArray=vao=>{GLctx.bindVertexArray(GL.vaos[vao])};var _glBindVertexArray=_emscripten_glBindVertexArray;var _emscripten_glBindVertexArrayOES=_glBindVertexArray;var _emscripten_glBlendColor=(x0,x1,x2,x3)=>GLctx.blendColor(x0,x1,x2,x3);var _emscripten_glBlendEquation=x0=>GLctx.blendEquation(x0);var _emscripten_glBlendEquationSeparate=(x0,x1)=>GLctx.blendEquationSeparate(x0,x1);var _emscripten_glBlendFunc=(x0,x1)=>GLctx.blendFunc(x0,x1);var _emscripten_glBlendFuncSeparate=(x0,x1,x2,x3)=>GLctx.blendFuncSeparate(x0,x1,x2,x3);var _emscripten_glBlitFramebuffer=(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)=>GLctx.blitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9);var _emscripten_glBufferData=(target,size,data,usage)=>{if(GL.currentContext.version>=2){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}return}GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)};var _emscripten_glBufferSubData=(target,offset,size,data)=>{if(GL.currentContext.version>=2){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))};var _emscripten_glCheckFramebufferStatus=x0=>GLctx.checkFramebufferStatus(x0);var _emscripten_glClear=x0=>GLctx.clear(x0);var _emscripten_glClearBufferfi=(x0,x1,x2,x3)=>GLctx.clearBufferfi(x0,x1,x2,x3);var _emscripten_glClearBufferfv=(buffer,drawbuffer,value)=>{GLctx.clearBufferfv(buffer,drawbuffer,HEAPF32,value>>2)};var _emscripten_glClearBufferiv=(buffer,drawbuffer,value)=>{GLctx.clearBufferiv(buffer,drawbuffer,HEAP32,value>>2)};var _emscripten_glClearBufferuiv=(buffer,drawbuffer,value)=>{GLctx.clearBufferuiv(buffer,drawbuffer,HEAPU32,value>>2)};var _emscripten_glClearColor=(x0,x1,x2,x3)=>GLctx.clearColor(x0,x1,x2,x3);var _emscripten_glClearDepthf=x0=>GLctx.clearDepth(x0);var _emscripten_glClearStencil=x0=>GLctx.clearStencil(x0);var _emscripten_glClientWaitSync=(sync,flags,timeout)=>{timeout=Number(timeout);return GLctx.clientWaitSync(GL.syncs[sync],flags,timeout)};var _emscripten_glClipControlEXT=(origin,depth)=>{GLctx.extClipControl["clipControlEXT"](origin,depth)};var _emscripten_glColorMask=(red,green,blue,alpha)=>{GLctx.colorMask(!!red,!!green,!!blue,!!alpha)};var _emscripten_glCompileShader=shader=>{GLctx.compileShader(GL.shaders[shader])};var _emscripten_glCompressedTexImage2D=(target,level,internalFormat,width,height,border,imageSize,data)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,imageSize,data);return}GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,HEAPU8,data,imageSize);return}GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,HEAPU8.subarray(data,data+imageSize))};var _emscripten_glCompressedTexImage3D=(target,level,internalFormat,width,height,depth,border,imageSize,data)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.compressedTexImage3D(target,level,internalFormat,width,height,depth,border,imageSize,data)}else{GLctx.compressedTexImage3D(target,level,internalFormat,width,height,depth,border,HEAPU8,data,imageSize)}};var _emscripten_glCompressedTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,imageSize,data)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data);return}GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,HEAPU8,data,imageSize);return}GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,HEAPU8.subarray(data,data+imageSize))};var _emscripten_glCompressedTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}};var _emscripten_glCopyBufferSubData=(x0,x1,x2,x3,x4)=>GLctx.copyBufferSubData(x0,x1,x2,x3,x4);var _emscripten_glCopyTexImage2D=(x0,x1,x2,x3,x4,x5,x6,x7)=>GLctx.copyTexImage2D(x0,x1,x2,x3,x4,x5,x6,x7);var _emscripten_glCopyTexSubImage2D=(x0,x1,x2,x3,x4,x5,x6,x7)=>GLctx.copyTexSubImage2D(x0,x1,x2,x3,x4,x5,x6,x7);var _emscripten_glCopyTexSubImage3D=(x0,x1,x2,x3,x4,x5,x6,x7,x8)=>GLctx.copyTexSubImage3D(x0,x1,x2,x3,x4,x5,x6,x7,x8);var _emscripten_glCreateProgram=()=>{var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id};var _emscripten_glCreateShader=shaderType=>{var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id};var _emscripten_glCullFace=x0=>GLctx.cullFace(x0);var _emscripten_glDeleteBuffers=(n,buffers)=>{for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}};var _emscripten_glDeleteFramebuffers=(n,framebuffers)=>{for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}};var _emscripten_glDeleteProgram=id=>{if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null};var _emscripten_glDeleteQueries=(n,ids)=>{for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.deleteQuery(query);GL.queries[id]=null}};var _emscripten_glDeleteQueriesEXT=(n,ids)=>{for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.disjointTimerQueryExt["deleteQueryEXT"](query);GL.queries[id]=null}};var _emscripten_glDeleteRenderbuffers=(n,renderbuffers)=>{for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}};var _emscripten_glDeleteSamplers=(n,samplers)=>{for(var i=0;i>2];var sampler=GL.samplers[id];if(!sampler)continue;GLctx.deleteSampler(sampler);sampler.name=0;GL.samplers[id]=null}};var _emscripten_glDeleteShader=id=>{if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null};var _emscripten_glDeleteSync=id=>{if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null};var _emscripten_glDeleteTextures=(n,textures)=>{for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}};var _emscripten_glDeleteTransformFeedbacks=(n,ids)=>{for(var i=0;i>2];var transformFeedback=GL.transformFeedbacks[id];if(!transformFeedback)continue;GLctx.deleteTransformFeedback(transformFeedback);transformFeedback.name=0;GL.transformFeedbacks[id]=null}};var _emscripten_glDeleteVertexArrays=(n,vaos)=>{for(var i=0;i>2];GLctx.deleteVertexArray(GL.vaos[id]);GL.vaos[id]=null}};var _glDeleteVertexArrays=_emscripten_glDeleteVertexArrays;var _emscripten_glDeleteVertexArraysOES=_glDeleteVertexArrays;var _emscripten_glDepthFunc=x0=>GLctx.depthFunc(x0);var _emscripten_glDepthMask=flag=>{GLctx.depthMask(!!flag)};var _emscripten_glDepthRangef=(x0,x1)=>GLctx.depthRange(x0,x1);var _emscripten_glDetachShader=(program,shader)=>{GLctx.detachShader(GL.programs[program],GL.shaders[shader])};var _emscripten_glDisable=x0=>GLctx.disable(x0);var _emscripten_glDisableVertexAttribArray=index=>{GLctx.disableVertexAttribArray(index)};var _emscripten_glDrawArrays=(mode,first,count)=>{GLctx.drawArrays(mode,first,count)};var _emscripten_glDrawArraysInstanced=(mode,first,count,primcount)=>{GLctx.drawArraysInstanced(mode,first,count,primcount)};var _glDrawArraysInstanced=_emscripten_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedANGLE=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedARB=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedBaseInstanceWEBGL=(mode,first,count,instanceCount,baseInstance)=>{GLctx.dibvbi["drawArraysInstancedBaseInstanceWEBGL"](mode,first,count,instanceCount,baseInstance)};var _emscripten_glDrawArraysInstancedEXT=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedNV=_glDrawArraysInstanced;var tempFixedLengthArray=[];var _emscripten_glDrawBuffers=(n,bufs)=>{var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx.drawBuffers(bufArray)};var _glDrawBuffers=_emscripten_glDrawBuffers;var _emscripten_glDrawBuffersEXT=_glDrawBuffers;var _emscripten_glDrawBuffersWEBGL=_glDrawBuffers;var _emscripten_glDrawElements=(mode,count,type,indices)=>{GLctx.drawElements(mode,count,type,indices)};var _emscripten_glDrawElementsInstanced=(mode,count,type,indices,primcount)=>{GLctx.drawElementsInstanced(mode,count,type,indices,primcount)};var _glDrawElementsInstanced=_emscripten_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedANGLE=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedARB=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedBaseVertexBaseInstanceWEBGL=(mode,count,type,offset,instanceCount,baseVertex,baseinstance)=>{GLctx.dibvbi["drawElementsInstancedBaseVertexBaseInstanceWEBGL"](mode,count,type,offset,instanceCount,baseVertex,baseinstance)};var _emscripten_glDrawElementsInstancedEXT=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedNV=_glDrawElementsInstanced;var _glDrawElements=_emscripten_glDrawElements;var _emscripten_glDrawRangeElements=(mode,start,end,count,type,indices)=>{_glDrawElements(mode,count,type,indices)};var _emscripten_glEnable=x0=>GLctx.enable(x0);var _emscripten_glEnableVertexAttribArray=index=>{GLctx.enableVertexAttribArray(index)};var _emscripten_glEndQuery=x0=>GLctx.endQuery(x0);var _emscripten_glEndQueryEXT=target=>{GLctx.disjointTimerQueryExt["endQueryEXT"](target)};var _emscripten_glEndTransformFeedback=()=>GLctx.endTransformFeedback();var _emscripten_glFenceSync=(condition,flags)=>{var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}return 0};var _emscripten_glFinish=()=>GLctx.finish();var _emscripten_glFlush=()=>GLctx.flush();var _emscripten_glFramebufferRenderbuffer=(target,attachment,renderbuffertarget,renderbuffer)=>{GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])};var _emscripten_glFramebufferTexture2D=(target,attachment,textarget,texture,level)=>{GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)};var _emscripten_glFramebufferTextureLayer=(target,attachment,texture,level,layer)=>{GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)};var _emscripten_glFrontFace=x0=>GLctx.frontFace(x0);var _emscripten_glGenBuffers=(n,buffers)=>{GL.genObject(n,buffers,"createBuffer",GL.buffers)};var _emscripten_glGenFramebuffers=(n,ids)=>{GL.genObject(n,ids,"createFramebuffer",GL.framebuffers)};var _emscripten_glGenQueries=(n,ids)=>{GL.genObject(n,ids,"createQuery",GL.queries)};var _emscripten_glGenQueriesEXT=(n,ids)=>{for(var i=0;i>2]=0;return}var id=GL.getNewId(GL.queries);query.name=id;GL.queries[id]=query;HEAP32[ids+i*4>>2]=id}};var _emscripten_glGenRenderbuffers=(n,renderbuffers)=>{GL.genObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)};var _emscripten_glGenSamplers=(n,samplers)=>{GL.genObject(n,samplers,"createSampler",GL.samplers)};var _emscripten_glGenTextures=(n,textures)=>{GL.genObject(n,textures,"createTexture",GL.textures)};var _emscripten_glGenTransformFeedbacks=(n,ids)=>{GL.genObject(n,ids,"createTransformFeedback",GL.transformFeedbacks)};var _emscripten_glGenVertexArrays=(n,arrays)=>{GL.genObject(n,arrays,"createVertexArray",GL.vaos)};var _glGenVertexArrays=_emscripten_glGenVertexArrays;var _emscripten_glGenVertexArraysOES=_glGenVertexArrays;var _emscripten_glGenerateMipmap=x0=>GLctx.generateMipmap(x0);var __glGetActiveAttribOrUniform=(funcName,program,index,bufSize,length,size,type,name)=>{program=GL.programs[program];var info=GLctx[funcName](program,index);if(info){var numBytesWrittenExclNull=name&&stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull;if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type}};var _emscripten_glGetActiveAttrib=(program,index,bufSize,length,size,type,name)=>__glGetActiveAttribOrUniform("getActiveAttrib",program,index,bufSize,length,size,type,name);var _emscripten_glGetActiveUniform=(program,index,bufSize,length,size,type,name)=>__glGetActiveAttribOrUniform("getActiveUniform",program,index,bufSize,length,size,type,name);var _emscripten_glGetActiveUniformBlockName=(program,uniformBlockIndex,bufSize,length,uniformBlockName)=>{program=GL.programs[program];var result=GLctx.getActiveUniformBlockName(program,uniformBlockIndex);if(!result)return;if(uniformBlockName&&bufSize>0){var numBytesWrittenExclNull=stringToUTF8(result,uniformBlockName,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull}else{if(length)HEAP32[length>>2]=0}};var _emscripten_glGetActiveUniformBlockiv=(program,uniformBlockIndex,pname,params)=>{if(!params){GL.recordError(1281);return}program=GL.programs[program];if(pname==35393){var name=GLctx.getActiveUniformBlockName(program,uniformBlockIndex);HEAP32[params>>2]=name.length+1;return}var result=GLctx.getActiveUniformBlockParameter(program,uniformBlockIndex,pname);if(result===null)return;if(pname==35395){for(var i=0;i>2]=result[i]}}else{HEAP32[params>>2]=result}};var _emscripten_glGetActiveUniformsiv=(program,uniformCount,uniformIndices,pname,params)=>{if(!params){GL.recordError(1281);return}if(uniformCount>0&&uniformIndices==0){GL.recordError(1281);return}program=GL.programs[program];var ids=[];for(var i=0;i>2])}var result=GLctx.getActiveUniforms(program,ids,pname);if(!result)return;var len=result.length;for(var i=0;i>2]=result[i]}};var _emscripten_glGetAttachedShaders=(program,maxCount,count,shaders)=>{var result=GLctx.getAttachedShaders(GL.programs[program]);var len=result.length;if(len>maxCount){len=maxCount}HEAP32[count>>2]=len;for(var i=0;i>2]=id}};var _emscripten_glGetAttribLocation=(program,name)=>GLctx.getAttribLocation(GL.programs[program],UTF8ToString(name));var writeI53ToI64=(ptr,num)=>{HEAPU32[ptr>>2]=num;var lower=HEAPU32[ptr>>2];HEAPU32[ptr+4>>2]=(num-lower)/4294967296};var webglGetExtensions=()=>{var exts=getEmscriptenSupportedExtensions(GLctx);exts=exts.concat(exts.map(e=>"GL_"+e));return exts};var emscriptenWebGLGet=(name_,p,type)=>{if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}ret=webglGetExtensions().length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err(`GL_INVALID_ENUM in glGet${type}v: Unknown object returned from WebGL getParameter(${name_})! (error: ${e})`);return}}break;default:GL.recordError(1280);err(`GL_INVALID_ENUM in glGet${type}v: Native code calling glGet${type}v(${name_}) and it returns ${result} of type ${typeof result}!`);return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p]=ret?1:0;break}};var _emscripten_glGetBooleanv=(name_,p)=>emscriptenWebGLGet(name_,p,4);var _emscripten_glGetBufferParameteri64v=(target,value,data)=>{if(!data){GL.recordError(1281);return}writeI53ToI64(data,GLctx.getBufferParameter(target,value))};var _emscripten_glGetBufferParameteriv=(target,value,data)=>{if(!data){GL.recordError(1281);return}HEAP32[data>>2]=GLctx.getBufferParameter(target,value)};var _emscripten_glGetError=()=>{var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error};var _emscripten_glGetFloatv=(name_,p)=>emscriptenWebGLGet(name_,p,2);var _emscripten_glGetFragDataLocation=(program,name)=>GLctx.getFragDataLocation(GL.programs[program],UTF8ToString(name));var _emscripten_glGetFramebufferAttachmentParameteriv=(target,attachment,pname,params)=>{var result=GLctx.getFramebufferAttachmentParameter(target,attachment,pname);if(result instanceof WebGLRenderbuffer||result instanceof WebGLTexture){result=result.name|0}HEAP32[params>>2]=result};var emscriptenWebGLGetIndexed=(target,index,data,type)=>{if(!data){GL.recordError(1281);return}var result=GLctx.getIndexedParameter(target,index);var ret;switch(typeof result){case"boolean":ret=result?1:0;break;case"number":ret=result;break;case"object":if(result===null){switch(target){case 35983:case 35368:ret=0;break;default:{GL.recordError(1280);return}}}else if(result instanceof WebGLBuffer){ret=result.name|0}else{GL.recordError(1280);return}break;default:GL.recordError(1280);return}switch(type){case 1:writeI53ToI64(data,ret);break;case 0:HEAP32[data>>2]=ret;break;case 2:HEAPF32[data>>2]=ret;break;case 4:HEAP8[data]=ret?1:0;break;default:abort("internal emscriptenWebGLGetIndexed() error, bad type: "+type)}};var _emscripten_glGetInteger64i_v=(target,index,data)=>emscriptenWebGLGetIndexed(target,index,data,1);var _emscripten_glGetInteger64v=(name_,p)=>{emscriptenWebGLGet(name_,p,1)};var _emscripten_glGetIntegeri_v=(target,index,data)=>emscriptenWebGLGetIndexed(target,index,data,0);var _emscripten_glGetIntegerv=(name_,p)=>emscriptenWebGLGet(name_,p,0);var _emscripten_glGetInternalformativ=(target,internalformat,pname,bufSize,params)=>{if(bufSize<0){GL.recordError(1281);return}if(!params){GL.recordError(1281);return}var ret=GLctx.getInternalformatParameter(target,internalformat,pname);if(ret===null)return;for(var i=0;i>2]=ret[i]}};var _emscripten_glGetProgramBinary=(program,bufSize,length,binaryFormat,binary)=>{GL.recordError(1282)};var _emscripten_glGetProgramInfoLog=(program,maxLength,length,infoLog)=>{var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetProgramiv=(program,pname,p)=>{if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){var numActiveUniforms=GLctx.getProgramParameter(program,35718);for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){var numActiveAttributes=GLctx.getProgramParameter(program,35721);for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){var numActiveUniformBlocks=GLctx.getProgramParameter(program,35382);for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}};var _emscripten_glGetQueryObjecti64vEXT=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param;if(GL.currentContext.version<2){param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname)}else{param=GLctx.getQueryParameter(query,pname)}var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)};var _emscripten_glGetQueryObjectivEXT=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret};var _glGetQueryObjecti64vEXT=_emscripten_glGetQueryObjecti64vEXT;var _emscripten_glGetQueryObjectui64vEXT=_glGetQueryObjecti64vEXT;var _emscripten_glGetQueryObjectuiv=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.getQueryParameter(query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret};var _glGetQueryObjectivEXT=_emscripten_glGetQueryObjectivEXT;var _emscripten_glGetQueryObjectuivEXT=_glGetQueryObjectivEXT;var _emscripten_glGetQueryiv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getQuery(target,pname)};var _emscripten_glGetQueryivEXT=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.disjointTimerQueryExt["getQueryEXT"](target,pname)};var _emscripten_glGetRenderbufferParameteriv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getRenderbufferParameter(target,pname)};var _emscripten_glGetSamplerParameterfv=(sampler,pname,params)=>{if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getSamplerParameter(GL.samplers[sampler],pname)};var _emscripten_glGetSamplerParameteriv=(sampler,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getSamplerParameter(GL.samplers[sampler],pname)};var _emscripten_glGetShaderInfoLog=(shader,maxLength,length,infoLog)=>{var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetShaderPrecisionFormat=(shaderType,precisionType,range,precision)=>{var result=GLctx.getShaderPrecisionFormat(shaderType,precisionType);HEAP32[range>>2]=result.rangeMin;HEAP32[range+4>>2]=result.rangeMax;HEAP32[precision>>2]=result.precision};var _emscripten_glGetShaderSource=(shader,bufSize,length,source)=>{var result=GLctx.getShaderSource(GL.shaders[shader]);if(!result)return;var numBytesWrittenExclNull=bufSize>0&&source?stringToUTF8(result,source,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetShaderiv=(shader,pname,p)=>{if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}};var stringToNewUTF8=str=>{var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8(str,ret,size);return ret};var _emscripten_glGetString=name_=>{var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:ret=stringToNewUTF8(webglGetExtensions().join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s?stringToNewUTF8(s):0;break;case 7938:var webGLVersion=GLctx.getParameter(7938);var glVersion=`OpenGL ES 2.0 (${webGLVersion})`;if(GL.currentContext.version>=2)glVersion=`OpenGL ES 3.0 (${webGLVersion})`;ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion=`OpenGL ES GLSL ES ${ver_num[1]} (${glslVersion})`}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret};var _emscripten_glGetStringi=(name,index)=>{if(GL.currentContext.version<2){GL.recordError(1282);return 0}var stringiCache=GL.stringiCache[name];if(stringiCache){if(index<0||index>=stringiCache.length){GL.recordError(1281);return 0}return stringiCache[index]}switch(name){case 7939:var exts=webglGetExtensions().map(stringToNewUTF8);stringiCache=GL.stringiCache[name]=exts;if(index<0||index>=stringiCache.length){GL.recordError(1281);return 0}return stringiCache[index];default:GL.recordError(1280);return 0}};var _emscripten_glGetSynciv=(sync,pname,bufSize,length,values)=>{if(bufSize<0){GL.recordError(1281);return}if(!values){GL.recordError(1281);return}var ret=GLctx.getSyncParameter(GL.syncs[sync],pname);if(ret!==null){HEAP32[values>>2]=ret;if(length)HEAP32[length>>2]=1}};var _emscripten_glGetTexParameterfv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getTexParameter(target,pname)};var _emscripten_glGetTexParameteriv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getTexParameter(target,pname)};var _emscripten_glGetTransformFeedbackVarying=(program,index,bufSize,length,size,type,name)=>{program=GL.programs[program];var info=GLctx.getTransformFeedbackVarying(program,index);if(!info)return;if(name&&bufSize>0){var numBytesWrittenExclNull=stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull}else{if(length)HEAP32[length>>2]=0}if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type};var _emscripten_glGetUniformBlockIndex=(program,uniformBlockName)=>GLctx.getUniformBlockIndex(GL.programs[program],UTF8ToString(uniformBlockName));var _emscripten_glGetUniformIndices=(program,uniformCount,uniformNames,uniformIndices)=>{if(!uniformIndices){GL.recordError(1281);return}if(uniformCount>0&&(uniformNames==0||uniformIndices==0)){GL.recordError(1281);return}program=GL.programs[program];var names=[];for(var i=0;i>2]));var result=GLctx.getUniformIndices(program,names);if(!result)return;var len=result.length;for(var i=0;i>2]=result[i]}};var jstoi_q=str=>parseInt(str);var webglGetLeftBracePos=name=>name.slice(-1)=="]"&&name.lastIndexOf("[");var webglPrepareUniformLocationsBeforeFirstUse=program=>{var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};var numActiveUniforms=GLctx.getProgramParameter(program,35718);for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j{name=UTF8ToString(name);if(program=GL.programs[program]){webglPrepareUniformLocationsBeforeFirstUse(program);var uniformLocsById=program.uniformLocsById;var arrayIndex=0;var uniformBaseName=name;var leftBrace=webglGetLeftBracePos(name);if(leftBrace>0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex{var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?`[${webglLoc}]`:""))}return webglLoc}else{GL.recordError(1282)}};var emscriptenWebGLGetUniform=(program,location,params,type)=>{if(!params){GL.recordError(1281);return}program=GL.programs[program];webglPrepareUniformLocationsBeforeFirstUse(program);var data=GLctx.getUniform(program,webglGetUniformLocation(location));if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break}}}};var _emscripten_glGetUniformfv=(program,location,params)=>{emscriptenWebGLGetUniform(program,location,params,2)};var _emscripten_glGetUniformiv=(program,location,params)=>{emscriptenWebGLGetUniform(program,location,params,0)};var _emscripten_glGetUniformuiv=(program,location,params)=>emscriptenWebGLGetUniform(program,location,params,0);var emscriptenWebGLGetVertexAttrib=(index,pname,params,type)=>{if(!params){GL.recordError(1281);return}var data=GLctx.getVertexAttrib(index,pname);if(pname==34975){HEAP32[params>>2]=data&&data["name"]}else if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break;case 5:HEAP32[params>>2]=Math.fround(data);break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break;case 5:HEAP32[params+i*4>>2]=Math.fround(data[i]);break}}}};var _emscripten_glGetVertexAttribIiv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,0)};var _glGetVertexAttribIiv=_emscripten_glGetVertexAttribIiv;var _emscripten_glGetVertexAttribIuiv=_glGetVertexAttribIiv;var _emscripten_glGetVertexAttribPointerv=(index,pname,pointer)=>{if(!pointer){GL.recordError(1281);return}HEAP32[pointer>>2]=GLctx.getVertexAttribOffset(index,pname)};var _emscripten_glGetVertexAttribfv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,2)};var _emscripten_glGetVertexAttribiv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,5)};var _emscripten_glHint=(x0,x1)=>GLctx.hint(x0,x1);var _emscripten_glInvalidateFramebuffer=(target,numAttachments,attachments)=>{var list=tempFixedLengthArray[numAttachments];for(var i=0;i>2]}GLctx.invalidateFramebuffer(target,list)};var _emscripten_glInvalidateSubFramebuffer=(target,numAttachments,attachments,x,y,width,height)=>{var list=tempFixedLengthArray[numAttachments];for(var i=0;i>2]}GLctx.invalidateSubFramebuffer(target,list,x,y,width,height)};var _emscripten_glIsBuffer=buffer=>{var b=GL.buffers[buffer];if(!b)return 0;return GLctx.isBuffer(b)};var _emscripten_glIsEnabled=x0=>GLctx.isEnabled(x0);var _emscripten_glIsFramebuffer=framebuffer=>{var fb=GL.framebuffers[framebuffer];if(!fb)return 0;return GLctx.isFramebuffer(fb)};var _emscripten_glIsProgram=program=>{program=GL.programs[program];if(!program)return 0;return GLctx.isProgram(program)};var _emscripten_glIsQuery=id=>{var query=GL.queries[id];if(!query)return 0;return GLctx.isQuery(query)};var _emscripten_glIsQueryEXT=id=>{var query=GL.queries[id];if(!query)return 0;return GLctx.disjointTimerQueryExt["isQueryEXT"](query)};var _emscripten_glIsRenderbuffer=renderbuffer=>{var rb=GL.renderbuffers[renderbuffer];if(!rb)return 0;return GLctx.isRenderbuffer(rb)};var _emscripten_glIsSampler=id=>{var sampler=GL.samplers[id];if(!sampler)return 0;return GLctx.isSampler(sampler)};var _emscripten_glIsShader=shader=>{var s=GL.shaders[shader];if(!s)return 0;return GLctx.isShader(s)};var _emscripten_glIsSync=sync=>GLctx.isSync(GL.syncs[sync]);var _emscripten_glIsTexture=id=>{var texture=GL.textures[id];if(!texture)return 0;return GLctx.isTexture(texture)};var _emscripten_glIsTransformFeedback=id=>GLctx.isTransformFeedback(GL.transformFeedbacks[id]);var _emscripten_glIsVertexArray=array=>{var vao=GL.vaos[array];if(!vao)return 0;return GLctx.isVertexArray(vao)};var _glIsVertexArray=_emscripten_glIsVertexArray;var _emscripten_glIsVertexArrayOES=_glIsVertexArray;var _emscripten_glLineWidth=x0=>GLctx.lineWidth(x0);var _emscripten_glLinkProgram=program=>{program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}};var _emscripten_glMultiDrawArraysInstancedBaseInstanceWEBGL=(mode,firsts,counts,instanceCounts,baseInstances,drawCount)=>{GLctx.mdibvbi["multiDrawArraysInstancedBaseInstanceWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,HEAP32,instanceCounts>>2,HEAPU32,baseInstances>>2,drawCount)};var _emscripten_glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL=(mode,counts,type,offsets,instanceCounts,baseVertices,baseInstances,drawCount)=>{GLctx.mdibvbi["multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,HEAP32,instanceCounts>>2,HEAP32,baseVertices>>2,HEAPU32,baseInstances>>2,drawCount)};var _emscripten_glPauseTransformFeedback=()=>GLctx.pauseTransformFeedback();var _emscripten_glPixelStorei=(pname,param)=>{if(pname==3317){GL.unpackAlignment=param}else if(pname==3314){GL.unpackRowLength=param}GLctx.pixelStorei(pname,param)};var _emscripten_glPolygonModeWEBGL=(face,mode)=>{GLctx.webglPolygonMode["polygonModeWEBGL"](face,mode)};var _emscripten_glPolygonOffset=(x0,x1)=>GLctx.polygonOffset(x0,x1);var _emscripten_glPolygonOffsetClampEXT=(factor,units,clamp)=>{GLctx.extPolygonOffsetClamp["polygonOffsetClampEXT"](factor,units,clamp)};var _emscripten_glProgramBinary=(program,binaryFormat,binary,length)=>{GL.recordError(1280)};var _emscripten_glProgramParameteri=(program,pname,value)=>{GL.recordError(1280)};var _emscripten_glQueryCounterEXT=(id,target)=>{GLctx.disjointTimerQueryExt["queryCounterEXT"](GL.queries[id],target)};var _emscripten_glReadBuffer=x0=>GLctx.readBuffer(x0);var computeUnpackAlignedImageSize=(width,height,sizePerPixel)=>{function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=(GL.unpackRowLength||width)*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,GL.unpackAlignment);return height*alignedRowSize};var colorChannelsInGlTextureFormat=format=>{var colorChannels={5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4};return colorChannels[format-6402]||1};var heapObjectForWebGLType=type=>{type-=5120;if(type==0)return HEAP8;if(type==1)return HEAPU8;if(type==2)return HEAP16;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922||type==28520||type==30779||type==30782)return HEAPU32;return HEAPU16};var toTypedArrayIndex=(pointer,heap)=>pointer>>>31-Math.clz32(heap.BYTES_PER_ELEMENT);var emscriptenWebGLGetTexPixelData=(type,format,width,height,pixels,internalFormat)=>{var heap=heapObjectForWebGLType(type);var sizePerPixel=colorChannelsInGlTextureFormat(format)*heap.BYTES_PER_ELEMENT;var bytes=computeUnpackAlignedImageSize(width,height,sizePerPixel);return heap.subarray(toTypedArrayIndex(pixels,heap),toTypedArrayIndex(pixels+bytes,heap))};var _emscripten_glReadPixels=(x,y,width,height,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels);return}var heap=heapObjectForWebGLType(type);var target=toTypedArrayIndex(pixels,heap);GLctx.readPixels(x,y,width,height,format,type,heap,target);return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)};var _emscripten_glReleaseShaderCompiler=()=>{};var _emscripten_glRenderbufferStorage=(x0,x1,x2,x3)=>GLctx.renderbufferStorage(x0,x1,x2,x3);var _emscripten_glRenderbufferStorageMultisample=(x0,x1,x2,x3,x4)=>GLctx.renderbufferStorageMultisample(x0,x1,x2,x3,x4);var _emscripten_glResumeTransformFeedback=()=>GLctx.resumeTransformFeedback();var _emscripten_glSampleCoverage=(value,invert)=>{GLctx.sampleCoverage(value,!!invert)};var _emscripten_glSamplerParameterf=(sampler,pname,param)=>{GLctx.samplerParameterf(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameterfv=(sampler,pname,params)=>{var param=HEAPF32[params>>2];GLctx.samplerParameterf(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameteri=(sampler,pname,param)=>{GLctx.samplerParameteri(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameteriv=(sampler,pname,params)=>{var param=HEAP32[params>>2];GLctx.samplerParameteri(GL.samplers[sampler],pname,param)};var _emscripten_glScissor=(x0,x1,x2,x3)=>GLctx.scissor(x0,x1,x2,x3);var _emscripten_glShaderBinary=(count,shaders,binaryformat,binary,length)=>{GL.recordError(1280)};var _emscripten_glShaderSource=(shader,count,string,length)=>{var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)};var _emscripten_glStencilFunc=(x0,x1,x2)=>GLctx.stencilFunc(x0,x1,x2);var _emscripten_glStencilFuncSeparate=(x0,x1,x2,x3)=>GLctx.stencilFuncSeparate(x0,x1,x2,x3);var _emscripten_glStencilMask=x0=>GLctx.stencilMask(x0);var _emscripten_glStencilMaskSeparate=(x0,x1)=>GLctx.stencilMaskSeparate(x0,x1);var _emscripten_glStencilOp=(x0,x1,x2)=>GLctx.stencilOp(x0,x1,x2);var _emscripten_glStencilOpSeparate=(x0,x1,x2,x3)=>GLctx.stencilOpSeparate(x0,x1,x2,x3);var _emscripten_glTexImage2D=(target,level,internalFormat,width,height,border,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels);return}if(pixels){var heap=heapObjectForWebGLType(type);var index=toTypedArrayIndex(pixels,heap);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,index);return}}var pixelData=pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null;GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixelData)};var _emscripten_glTexImage3D=(target,level,internalFormat,width,height,depth,border,format,type,pixels)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,heap,toTypedArrayIndex(pixels,heap))}else{GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,null)}};var _emscripten_glTexParameterf=(x0,x1,x2)=>GLctx.texParameterf(x0,x1,x2);var _emscripten_glTexParameterfv=(target,pname,params)=>{var param=HEAPF32[params>>2];GLctx.texParameterf(target,pname,param)};var _emscripten_glTexParameteri=(x0,x1,x2)=>GLctx.texParameteri(x0,x1,x2);var _emscripten_glTexParameteriv=(target,pname,params)=>{var param=HEAP32[params>>2];GLctx.texParameteri(target,pname,param)};var _emscripten_glTexStorage2D=(x0,x1,x2,x3,x4)=>GLctx.texStorage2D(x0,x1,x2,x3,x4);var _emscripten_glTexStorage3D=(x0,x1,x2,x3,x4,x5)=>GLctx.texStorage3D(x0,x1,x2,x3,x4,x5);var _emscripten_glTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels);return}if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,heap,toTypedArrayIndex(pixels,heap));return}}var pixelData=pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0):null;GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)};var _emscripten_glTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,toTypedArrayIndex(pixels,heap))}else{GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}};var _emscripten_glTransformFeedbackVaryings=(program,count,varyings,bufferMode)=>{program=GL.programs[program];var vars=[];for(var i=0;i>2]));GLctx.transformFeedbackVaryings(program,vars,bufferMode)};var _emscripten_glUniform1f=(location,v0)=>{GLctx.uniform1f(webglGetUniformLocation(location),v0)};var miniTempWebGLFloatBuffers=[];var _emscripten_glUniform1fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform1fv(webglGetUniformLocation(location),HEAPF32,value>>2,count);return}if(count<=288){var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform1i=(location,v0)=>{GLctx.uniform1i(webglGetUniformLocation(location),v0)};var miniTempWebGLIntBuffers=[];var _emscripten_glUniform1iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count);return}if(count<=288){var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform1ui=(location,v0)=>{GLctx.uniform1ui(webglGetUniformLocation(location),v0)};var _emscripten_glUniform1uiv=(location,count,value)=>{count&&GLctx.uniform1uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count)};var _emscripten_glUniform2f=(location,v0,v1)=>{GLctx.uniform2f(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2);return}if(count<=144){count*=2;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform2i=(location,v0,v1)=>{GLctx.uniform2i(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2);return}if(count<=144){count*=2;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform2ui=(location,v0,v1)=>{GLctx.uniform2ui(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2uiv=(location,count,value)=>{count&&GLctx.uniform2uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*2)};var _emscripten_glUniform3f=(location,v0,v1,v2)=>{GLctx.uniform3f(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3);return}if(count<=96){count*=3;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform3i=(location,v0,v1,v2)=>{GLctx.uniform3i(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform3iv(webglGetUniformLocation(location),HEAP32,value>>2,count*3);return}if(count<=96){count*=3;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform3ui=(location,v0,v1,v2)=>{GLctx.uniform3ui(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3uiv=(location,count,value)=>{count&&GLctx.uniform3uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*3)};var _emscripten_glUniform4f=(location,v0,v1,v2,v3)=>{GLctx.uniform4f(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4);return}if(count<=72){var view=miniTempWebGLFloatBuffers[4*count];var heap=HEAPF32;value=value>>2;count*=4;for(var i=0;i>2,value+count*16>>2)}GLctx.uniform4fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform4i=(location,v0,v1,v2,v3)=>{GLctx.uniform4i(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform4iv(webglGetUniformLocation(location),HEAP32,value>>2,count*4);return}if(count<=72){count*=4;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2];view[i+3]=HEAP32[value+(4*i+12)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform4ui=(location,v0,v1,v2,v3)=>{GLctx.uniform4ui(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4uiv=(location,count,value)=>{count&&GLctx.uniform4uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*4)};var _emscripten_glUniformBlockBinding=(program,uniformBlockIndex,uniformBlockBinding)=>{program=GL.programs[program];GLctx.uniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding)};var _emscripten_glUniformMatrix2fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*4);return}if(count<=72){count*=4;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix2x3fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix2x3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*6)};var _emscripten_glUniformMatrix2x4fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix2x4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*8)};var _emscripten_glUniformMatrix3fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9);return}if(count<=32){count*=9;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix3x2fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix3x2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*6)};var _emscripten_glUniformMatrix3x4fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix3x4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*12)};var _emscripten_glUniformMatrix4fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16);return}if(count<=18){var view=miniTempWebGLFloatBuffers[16*count];var heap=HEAPF32;value=value>>2;count*=16;for(var i=0;i>2,value+count*64>>2)}GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix4x2fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix4x2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*8)};var _emscripten_glUniformMatrix4x3fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix4x3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*12)};var _emscripten_glUseProgram=program=>{program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program};var _emscripten_glValidateProgram=program=>{GLctx.validateProgram(GL.programs[program])};var _emscripten_glVertexAttrib1f=(x0,x1)=>GLctx.vertexAttrib1f(x0,x1);var _emscripten_glVertexAttrib1fv=(index,v)=>{GLctx.vertexAttrib1f(index,HEAPF32[v>>2])};var _emscripten_glVertexAttrib2f=(x0,x1,x2)=>GLctx.vertexAttrib2f(x0,x1,x2);var _emscripten_glVertexAttrib2fv=(index,v)=>{GLctx.vertexAttrib2f(index,HEAPF32[v>>2],HEAPF32[v+4>>2])};var _emscripten_glVertexAttrib3f=(x0,x1,x2,x3)=>GLctx.vertexAttrib3f(x0,x1,x2,x3);var _emscripten_glVertexAttrib3fv=(index,v)=>{GLctx.vertexAttrib3f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2])};var _emscripten_glVertexAttrib4f=(x0,x1,x2,x3,x4)=>GLctx.vertexAttrib4f(x0,x1,x2,x3,x4);var _emscripten_glVertexAttrib4fv=(index,v)=>{GLctx.vertexAttrib4f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2],HEAPF32[v+12>>2])};var _emscripten_glVertexAttribDivisor=(index,divisor)=>{GLctx.vertexAttribDivisor(index,divisor)};var _glVertexAttribDivisor=_emscripten_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorANGLE=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorARB=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorEXT=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorNV=_glVertexAttribDivisor;var _emscripten_glVertexAttribI4i=(x0,x1,x2,x3,x4)=>GLctx.vertexAttribI4i(x0,x1,x2,x3,x4);var _emscripten_glVertexAttribI4iv=(index,v)=>{GLctx.vertexAttribI4i(index,HEAP32[v>>2],HEAP32[v+4>>2],HEAP32[v+8>>2],HEAP32[v+12>>2])};var _emscripten_glVertexAttribI4ui=(x0,x1,x2,x3,x4)=>GLctx.vertexAttribI4ui(x0,x1,x2,x3,x4);var _emscripten_glVertexAttribI4uiv=(index,v)=>{GLctx.vertexAttribI4ui(index,HEAPU32[v>>2],HEAPU32[v+4>>2],HEAPU32[v+8>>2],HEAPU32[v+12>>2])};var _emscripten_glVertexAttribIPointer=(index,size,type,stride,ptr)=>{GLctx.vertexAttribIPointer(index,size,type,stride,ptr)};var _emscripten_glVertexAttribPointer=(index,size,type,normalized,stride,ptr)=>{GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)};var _emscripten_glViewport=(x0,x1,x2,x3)=>GLctx.viewport(x0,x1,x2,x3);var _emscripten_glWaitSync=(sync,flags,timeout)=>{timeout=Number(timeout);GLctx.waitSync(GL.syncs[sync],flags,timeout)};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var _emscripten_request_animation_frame_loop=(cb,userData)=>{function tick(timeStamp){if(getWasmTableEntry(cb)(timeStamp,userData)){requestAnimationFrame(tick)}}return requestAnimationFrame(tick)};var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var _glGetIntegerv=_emscripten_glGetIntegerv;var _glGetString=_emscripten_glGetString;var _glGetStringi=_emscripten_glGetStringi;var _llvm_eh_typeid_for=type=>type;function _random_get(buffer,size){try{randomFill(HEAPU8.subarray(buffer,buffer+size));return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();for(let i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var miniTempWebGLFloatBuffersStorage=new Float32Array(288);for(var i=0;i<=288;++i){miniTempWebGLFloatBuffers[i]=miniTempWebGLFloatBuffersStorage.subarray(0,i)}var miniTempWebGLIntBuffersStorage=new Int32Array(288);for(var i=0;i<=288;++i){miniTempWebGLIntBuffers[i]=miniTempWebGLIntBuffersStorage.subarray(0,i)}{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["preloadPlugins"])preloadPlugins=Module["preloadPlugins"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;Module["GL"]=GL;var _malloc,_add_font,_add_image,_add_image_with_rid,_allocate,_apply_scene_transactions,_command,_deallocate,_destroy,_devtools_rendering_set_show_fps_meter,_devtools_rendering_set_show_hit_testing,_devtools_rendering_set_show_ruler,_devtools_rendering_set_show_stats,_devtools_rendering_set_show_tiles,_export_node_as,_get_default_fallback_fonts,_get_image_bytes,_get_image_size,_get_node_absolute_bounding_box,_get_node_id_from_point,_get_node_ids_from_envelope,_get_node_ids_from_point,_grida_fonts_analyze_family,_grida_fonts_free,_grida_fonts_parse_font,_grida_markdown_to_html,_grida_svg_optimize,_grida_svg_pack,_has_missing_fonts,_highlight_strokes,_init,_init_with_backend,_list_available_fonts,_list_missing_fonts,_load_benchmark_scene,_load_dummy_scene,_load_scene_json,_pointer_move,_redraw,_resize_surface,_runtime_renderer_set_cache_tile,_runtime_renderer_set_outline_mode,_runtime_renderer_set_pixel_preview_scale,_runtime_renderer_set_pixel_preview_stable,_runtime_renderer_set_render_policy_flags,_set_debug,_set_default_fallback_fonts,_set_main_camera_transform,_set_verbose,_tick,_to_vector_network,_toggle_debug,_main,_emscripten_builtin_memalign,_setThrew,__emscripten_tempret_set,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,___cxa_decrement_exception_refcount,___cxa_increment_exception_refcount,___cxa_can_catch,___cxa_get_exception_ptr,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){_malloc=wasmExports["Mg"];_add_font=Module["_add_font"]=wasmExports["Og"];_add_image=Module["_add_image"]=wasmExports["Pg"];_add_image_with_rid=Module["_add_image_with_rid"]=wasmExports["Qg"];_allocate=Module["_allocate"]=wasmExports["Rg"];_apply_scene_transactions=Module["_apply_scene_transactions"]=wasmExports["Sg"];_command=Module["_command"]=wasmExports["Tg"];_deallocate=Module["_deallocate"]=wasmExports["Ug"];_destroy=Module["_destroy"]=wasmExports["Vg"];_devtools_rendering_set_show_fps_meter=Module["_devtools_rendering_set_show_fps_meter"]=wasmExports["Wg"];_devtools_rendering_set_show_hit_testing=Module["_devtools_rendering_set_show_hit_testing"]=wasmExports["Xg"];_devtools_rendering_set_show_ruler=Module["_devtools_rendering_set_show_ruler"]=wasmExports["Yg"];_devtools_rendering_set_show_stats=Module["_devtools_rendering_set_show_stats"]=wasmExports["Zg"];_devtools_rendering_set_show_tiles=Module["_devtools_rendering_set_show_tiles"]=wasmExports["_g"];_export_node_as=Module["_export_node_as"]=wasmExports["$g"];_get_default_fallback_fonts=Module["_get_default_fallback_fonts"]=wasmExports["ah"];_get_image_bytes=Module["_get_image_bytes"]=wasmExports["bh"];_get_image_size=Module["_get_image_size"]=wasmExports["ch"];_get_node_absolute_bounding_box=Module["_get_node_absolute_bounding_box"]=wasmExports["dh"];_get_node_id_from_point=Module["_get_node_id_from_point"]=wasmExports["eh"];_get_node_ids_from_envelope=Module["_get_node_ids_from_envelope"]=wasmExports["fh"];_get_node_ids_from_point=Module["_get_node_ids_from_point"]=wasmExports["gh"];_grida_fonts_analyze_family=Module["_grida_fonts_analyze_family"]=wasmExports["hh"];_grida_fonts_free=Module["_grida_fonts_free"]=wasmExports["ih"];_grida_fonts_parse_font=Module["_grida_fonts_parse_font"]=wasmExports["jh"];_grida_markdown_to_html=Module["_grida_markdown_to_html"]=wasmExports["kh"];_grida_svg_optimize=Module["_grida_svg_optimize"]=wasmExports["lh"];_grida_svg_pack=Module["_grida_svg_pack"]=wasmExports["mh"];_has_missing_fonts=Module["_has_missing_fonts"]=wasmExports["nh"];_highlight_strokes=Module["_highlight_strokes"]=wasmExports["oh"];_init=Module["_init"]=wasmExports["ph"];_init_with_backend=Module["_init_with_backend"]=wasmExports["qh"];_list_available_fonts=Module["_list_available_fonts"]=wasmExports["rh"];_list_missing_fonts=Module["_list_missing_fonts"]=wasmExports["sh"];_load_benchmark_scene=Module["_load_benchmark_scene"]=wasmExports["th"];_load_dummy_scene=Module["_load_dummy_scene"]=wasmExports["uh"];_load_scene_json=Module["_load_scene_json"]=wasmExports["vh"];_pointer_move=Module["_pointer_move"]=wasmExports["wh"];_redraw=Module["_redraw"]=wasmExports["xh"];_resize_surface=Module["_resize_surface"]=wasmExports["yh"];_runtime_renderer_set_cache_tile=Module["_runtime_renderer_set_cache_tile"]=wasmExports["zh"];_runtime_renderer_set_outline_mode=Module["_runtime_renderer_set_outline_mode"]=wasmExports["Ah"];_runtime_renderer_set_pixel_preview_scale=Module["_runtime_renderer_set_pixel_preview_scale"]=wasmExports["Bh"];_runtime_renderer_set_pixel_preview_stable=Module["_runtime_renderer_set_pixel_preview_stable"]=wasmExports["Ch"];_runtime_renderer_set_render_policy_flags=Module["_runtime_renderer_set_render_policy_flags"]=wasmExports["Dh"];_set_debug=Module["_set_debug"]=wasmExports["Eh"];_set_default_fallback_fonts=Module["_set_default_fallback_fonts"]=wasmExports["Fh"];_set_main_camera_transform=Module["_set_main_camera_transform"]=wasmExports["Gh"];_set_verbose=Module["_set_verbose"]=wasmExports["Hh"];_tick=Module["_tick"]=wasmExports["Ih"];_to_vector_network=Module["_to_vector_network"]=wasmExports["Jh"];_toggle_debug=Module["_toggle_debug"]=wasmExports["Kh"];_main=Module["_main"]=wasmExports["Lh"];_emscripten_builtin_memalign=wasmExports["Mh"];_setThrew=wasmExports["Nh"];__emscripten_tempret_set=wasmExports["Oh"];__emscripten_stack_restore=wasmExports["Ph"];__emscripten_stack_alloc=wasmExports["Qh"];_emscripten_stack_get_current=wasmExports["Rh"];___cxa_decrement_exception_refcount=wasmExports["Sh"];___cxa_increment_exception_refcount=wasmExports["Th"];___cxa_can_catch=wasmExports["Uh"];___cxa_get_exception_ptr=wasmExports["Vh"];memory=wasmMemory=wasmExports["Kg"];__indirect_function_table=wasmTable=wasmExports["Ng"]}var wasmImports={F:___cxa_begin_catch,M:___cxa_end_catch,a:___cxa_find_matching_catch_2,n:___cxa_find_matching_catch_3,ca:___cxa_find_matching_catch_4,za:___cxa_rethrow,G:___cxa_throw,db:___cxa_uncaught_exceptions,e:___resumeException,Ca:___syscall_fcntl64,xb:___syscall_fstat64,tb:___syscall_getcwd,kb:___syscall_getdents64,yb:___syscall_ioctl,ub:___syscall_lstat64,vb:___syscall_newfstatat,Da:___syscall_openat,jb:___syscall_readlinkat,wb:___syscall_stat64,Bb:__abort_js,fb:__emscripten_throw_longjmp,ob:__gmtime_js,mb:__mmap_js,nb:__munmap_js,Cb:__tzset_js,Ab:_clock_time_get,zb:_emscripten_date_now,ib:_emscripten_get_heap_max,aa:_emscripten_get_now,Df:_emscripten_glActiveTexture,Ef:_emscripten_glAttachShader,ge:_emscripten_glBeginQuery,ae:_emscripten_glBeginQueryEXT,Hc:_emscripten_glBeginTransformFeedback,Ff:_emscripten_glBindAttribLocation,Gf:_emscripten_glBindBuffer,Ec:_emscripten_glBindBufferBase,Fc:_emscripten_glBindBufferRange,Ee:_emscripten_glBindFramebuffer,Fe:_emscripten_glBindRenderbuffer,me:_emscripten_glBindSampler,Hf:_emscripten_glBindTexture,Ub:_emscripten_glBindTransformFeedback,_e:_emscripten_glBindVertexArray,bf:_emscripten_glBindVertexArrayOES,If:_emscripten_glBlendColor,Jf:_emscripten_glBlendEquation,Md:_emscripten_glBlendEquationSeparate,Kf:_emscripten_glBlendFunc,Ld:_emscripten_glBlendFuncSeparate,ye:_emscripten_glBlitFramebuffer,Lf:_emscripten_glBufferData,Mf:_emscripten_glBufferSubData,Ge:_emscripten_glCheckFramebufferStatus,Of:_emscripten_glClear,hc:_emscripten_glClearBufferfi,ic:_emscripten_glClearBufferfv,kc:_emscripten_glClearBufferiv,jc:_emscripten_glClearBufferuiv,Pf:_emscripten_glClearColor,Kd:_emscripten_glClearDepthf,Qf:_emscripten_glClearStencil,ve:_emscripten_glClientWaitSync,bd:_emscripten_glClipControlEXT,Rf:_emscripten_glColorMask,Sf:_emscripten_glCompileShader,Tf:_emscripten_glCompressedTexImage2D,Uc:_emscripten_glCompressedTexImage3D,Uf:_emscripten_glCompressedTexSubImage2D,Tc:_emscripten_glCompressedTexSubImage3D,xe:_emscripten_glCopyBufferSubData,Jd:_emscripten_glCopyTexImage2D,Vf:_emscripten_glCopyTexSubImage2D,Vc:_emscripten_glCopyTexSubImage3D,Wf:_emscripten_glCreateProgram,Xf:_emscripten_glCreateShader,Yf:_emscripten_glCullFace,Zf:_emscripten_glDeleteBuffers,He:_emscripten_glDeleteFramebuffers,_f:_emscripten_glDeleteProgram,he:_emscripten_glDeleteQueries,be:_emscripten_glDeleteQueriesEXT,Ie:_emscripten_glDeleteRenderbuffers,ne:_emscripten_glDeleteSamplers,$f:_emscripten_glDeleteShader,we:_emscripten_glDeleteSync,ag:_emscripten_glDeleteTextures,Tb:_emscripten_glDeleteTransformFeedbacks,$e:_emscripten_glDeleteVertexArrays,cf:_emscripten_glDeleteVertexArraysOES,Id:_emscripten_glDepthFunc,bg:_emscripten_glDepthMask,Hd:_emscripten_glDepthRangef,Gd:_emscripten_glDetachShader,cg:_emscripten_glDisable,dg:_emscripten_glDisableVertexAttribArray,eg:_emscripten_glDrawArrays,Ye:_emscripten_glDrawArraysInstanced,Pd:_emscripten_glDrawArraysInstancedANGLE,Fb:_emscripten_glDrawArraysInstancedARB,Ve:_emscripten_glDrawArraysInstancedBaseInstanceWEBGL,_c:_emscripten_glDrawArraysInstancedEXT,Gb:_emscripten_glDrawArraysInstancedNV,Te:_emscripten_glDrawBuffers,Yc:_emscripten_glDrawBuffersEXT,Qd:_emscripten_glDrawBuffersWEBGL,fg:_emscripten_glDrawElements,Ze:_emscripten_glDrawElementsInstanced,Od:_emscripten_glDrawElementsInstancedANGLE,Db:_emscripten_glDrawElementsInstancedARB,We:_emscripten_glDrawElementsInstancedBaseVertexBaseInstanceWEBGL,Eb:_emscripten_glDrawElementsInstancedEXT,Zc:_emscripten_glDrawElementsInstancedNV,Ne:_emscripten_glDrawRangeElements,gg:_emscripten_glEnable,hg:_emscripten_glEnableVertexAttribArray,ie:_emscripten_glEndQuery,ce:_emscripten_glEndQueryEXT,Gc:_emscripten_glEndTransformFeedback,se:_emscripten_glFenceSync,ig:_emscripten_glFinish,jg:_emscripten_glFlush,Je:_emscripten_glFramebufferRenderbuffer,Ke:_emscripten_glFramebufferTexture2D,Kc:_emscripten_glFramebufferTextureLayer,kg:_emscripten_glFrontFace,lg:_emscripten_glGenBuffers,Le:_emscripten_glGenFramebuffers,je:_emscripten_glGenQueries,de:_emscripten_glGenQueriesEXT,Me:_emscripten_glGenRenderbuffers,oe:_emscripten_glGenSamplers,mg:_emscripten_glGenTextures,Sb:_emscripten_glGenTransformFeedbacks,Xe:_emscripten_glGenVertexArrays,df:_emscripten_glGenVertexArraysOES,Ae:_emscripten_glGenerateMipmap,Fd:_emscripten_glGetActiveAttrib,Ed:_emscripten_glGetActiveUniform,cc:_emscripten_glGetActiveUniformBlockName,dc:_emscripten_glGetActiveUniformBlockiv,fc:_emscripten_glGetActiveUniformsiv,Dd:_emscripten_glGetAttachedShaders,Cd:_emscripten_glGetAttribLocation,Bd:_emscripten_glGetBooleanv,Zb:_emscripten_glGetBufferParameteri64v,ng:_emscripten_glGetBufferParameteriv,og:_emscripten_glGetError,pg:_emscripten_glGetFloatv,uc:_emscripten_glGetFragDataLocation,Be:_emscripten_glGetFramebufferAttachmentParameteriv,_b:_emscripten_glGetInteger64i_v,ac:_emscripten_glGetInteger64v,Ic:_emscripten_glGetIntegeri_v,qg:_emscripten_glGetIntegerv,Jb:_emscripten_glGetInternalformativ,Nb:_emscripten_glGetProgramBinary,rg:_emscripten_glGetProgramInfoLog,sg:_emscripten_glGetProgramiv,Zd:_emscripten_glGetQueryObjecti64vEXT,Sd:_emscripten_glGetQueryObjectivEXT,_d:_emscripten_glGetQueryObjectui64vEXT,ke:_emscripten_glGetQueryObjectuiv,ee:_emscripten_glGetQueryObjectuivEXT,le:_emscripten_glGetQueryiv,fe:_emscripten_glGetQueryivEXT,Ce:_emscripten_glGetRenderbufferParameteriv,Vb:_emscripten_glGetSamplerParameterfv,Wb:_emscripten_glGetSamplerParameteriv,tg:_emscripten_glGetShaderInfoLog,Wd:_emscripten_glGetShaderPrecisionFormat,Ad:_emscripten_glGetShaderSource,ug:_emscripten_glGetShaderiv,vg:_emscripten_glGetString,af:_emscripten_glGetStringi,$b:_emscripten_glGetSynciv,zd:_emscripten_glGetTexParameterfv,yd:_emscripten_glGetTexParameteriv,Cc:_emscripten_glGetTransformFeedbackVarying,ec:_emscripten_glGetUniformBlockIndex,gc:_emscripten_glGetUniformIndices,wg:_emscripten_glGetUniformLocation,xd:_emscripten_glGetUniformfv,wd:_emscripten_glGetUniformiv,vc:_emscripten_glGetUniformuiv,Bc:_emscripten_glGetVertexAttribIiv,Ac:_emscripten_glGetVertexAttribIuiv,td:_emscripten_glGetVertexAttribPointerv,vd:_emscripten_glGetVertexAttribfv,ud:_emscripten_glGetVertexAttribiv,sd:_emscripten_glHint,Xd:_emscripten_glInvalidateFramebuffer,Yd:_emscripten_glInvalidateSubFramebuffer,rd:_emscripten_glIsBuffer,qd:_emscripten_glIsEnabled,pd:_emscripten_glIsFramebuffer,od:_emscripten_glIsProgram,Sc:_emscripten_glIsQuery,Td:_emscripten_glIsQueryEXT,nd:_emscripten_glIsRenderbuffer,Yb:_emscripten_glIsSampler,md:_emscripten_glIsShader,te:_emscripten_glIsSync,xg:_emscripten_glIsTexture,Rb:_emscripten_glIsTransformFeedback,Jc:_emscripten_glIsVertexArray,Rd:_emscripten_glIsVertexArrayOES,yg:_emscripten_glLineWidth,zg:_emscripten_glLinkProgram,Re:_emscripten_glMultiDrawArraysInstancedBaseInstanceWEBGL,Se:_emscripten_glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL,Qb:_emscripten_glPauseTransformFeedback,Ag:_emscripten_glPixelStorei,ad:_emscripten_glPolygonModeWEBGL,ld:_emscripten_glPolygonOffset,cd:_emscripten_glPolygonOffsetClampEXT,Mb:_emscripten_glProgramBinary,Lb:_emscripten_glProgramParameteri,$d:_emscripten_glQueryCounterEXT,Ue:_emscripten_glReadBuffer,Bg:_emscripten_glReadPixels,kd:_emscripten_glReleaseShaderCompiler,De:_emscripten_glRenderbufferStorage,ze:_emscripten_glRenderbufferStorageMultisample,Ob:_emscripten_glResumeTransformFeedback,jd:_emscripten_glSampleCoverage,pe:_emscripten_glSamplerParameterf,Xb:_emscripten_glSamplerParameterfv,qe:_emscripten_glSamplerParameteri,re:_emscripten_glSamplerParameteriv,Cg:_emscripten_glScissor,id:_emscripten_glShaderBinary,Dg:_emscripten_glShaderSource,Eg:_emscripten_glStencilFunc,Fg:_emscripten_glStencilFuncSeparate,Gg:_emscripten_glStencilMask,Hg:_emscripten_glStencilMaskSeparate,Ig:_emscripten_glStencilOp,Jg:_emscripten_glStencilOpSeparate,Ia:_emscripten_glTexImage2D,Xc:_emscripten_glTexImage3D,Ja:_emscripten_glTexParameterf,Ka:_emscripten_glTexParameterfv,La:_emscripten_glTexParameteri,Ma:_emscripten_glTexParameteriv,Oe:_emscripten_glTexStorage2D,Kb:_emscripten_glTexStorage3D,Na:_emscripten_glTexSubImage2D,Wc:_emscripten_glTexSubImage3D,Dc:_emscripten_glTransformFeedbackVaryings,Oa:_emscripten_glUniform1f,Pa:_emscripten_glUniform1fv,zf:_emscripten_glUniform1i,Af:_emscripten_glUniform1iv,tc:_emscripten_glUniform1ui,oc:_emscripten_glUniform1uiv,Bf:_emscripten_glUniform2f,Cf:_emscripten_glUniform2fv,yf:_emscripten_glUniform2i,xf:_emscripten_glUniform2iv,rc:_emscripten_glUniform2ui,nc:_emscripten_glUniform2uiv,wf:_emscripten_glUniform3f,vf:_emscripten_glUniform3fv,uf:_emscripten_glUniform3i,tf:_emscripten_glUniform3iv,qc:_emscripten_glUniform3ui,mc:_emscripten_glUniform3uiv,sf:_emscripten_glUniform4f,rf:_emscripten_glUniform4fv,ef:_emscripten_glUniform4i,ff:_emscripten_glUniform4iv,pc:_emscripten_glUniform4ui,lc:_emscripten_glUniform4uiv,bc:_emscripten_glUniformBlockBinding,gf:_emscripten_glUniformMatrix2fv,Rc:_emscripten_glUniformMatrix2x3fv,Pc:_emscripten_glUniformMatrix2x4fv,hf:_emscripten_glUniformMatrix3fv,Qc:_emscripten_glUniformMatrix3x2fv,Mc:_emscripten_glUniformMatrix3x4fv,jf:_emscripten_glUniformMatrix4fv,Oc:_emscripten_glUniformMatrix4x2fv,Lc:_emscripten_glUniformMatrix4x3fv,kf:_emscripten_glUseProgram,hd:_emscripten_glValidateProgram,lf:_emscripten_glVertexAttrib1f,gd:_emscripten_glVertexAttrib1fv,fd:_emscripten_glVertexAttrib2f,mf:_emscripten_glVertexAttrib2fv,ed:_emscripten_glVertexAttrib3f,nf:_emscripten_glVertexAttrib3fv,dd:_emscripten_glVertexAttrib4f,of:_emscripten_glVertexAttrib4fv,Pe:_emscripten_glVertexAttribDivisor,Nd:_emscripten_glVertexAttribDivisorANGLE,Hb:_emscripten_glVertexAttribDivisorARB,$c:_emscripten_glVertexAttribDivisorEXT,Ib:_emscripten_glVertexAttribDivisorNV,zc:_emscripten_glVertexAttribI4i,xc:_emscripten_glVertexAttribI4iv,yc:_emscripten_glVertexAttribI4ui,wc:_emscripten_glVertexAttribI4uiv,Qe:_emscripten_glVertexAttribIPointer,pf:_emscripten_glVertexAttribPointer,qf:_emscripten_glViewport,ue:_emscripten_glWaitSync,_a:_emscripten_request_animation_frame_loop,gb:_emscripten_resize_heap,qb:_environ_get,rb:_environ_sizes_get,Sa:_exit,ba:_fd_close,lb:_fd_pread,Aa:_fd_read,pb:_fd_seek,ga:_fd_write,Qa:_glGetIntegerv,la:_glGetString,Ra:_glGetStringi,Ud:invoke_dd,Vd:invoke_dddd,xa:invoke_diii,Va:invoke_fdiiii,Ua:invoke_fdiiiii,Ta:invoke_fii,ya:invoke_fiii,s:invoke_fiiidi,S:invoke_fiiif,t:invoke_fiiiidi,r:invoke_i,j:invoke_ii,qa:invoke_iif,hb:invoke_iiffi,na:invoke_iiffiii,h:invoke_iii,Ga:invoke_iiiffii,f:invoke_iiii,l:invoke_iiiii,cb:invoke_iiiiid,y:invoke_iiiiii,x:invoke_iiiiiii,D:invoke_iiiiiiii,q:invoke_iiiiiiiii,ma:invoke_iiiiiiiiii,Z:invoke_iiiiiiiiiiii,ja:invoke_iiiiiiiiiiiifiii,pa:invoke_iijj,eb:invoke_j,T:invoke_ji,W:invoke_jiii,_:invoke_jiiii,H:invoke_jjji,k:invoke_v,Nf:invoke_vff,b:invoke_vi,Q:invoke_vid,P:invoke_vif,u:invoke_viff,B:invoke_viffff,V:invoke_vifffff,Wa:invoke_viffffff,A:invoke_viffi,ea:invoke_viffiiiiiii,c:invoke_vii,Za:invoke_viidii,N:invoke_viif,E:invoke_viiff,wa:invoke_viififii,v:invoke_viifiiifi,d:invoke_viii,ka:invoke_viiif,z:invoke_viiiffi,I:invoke_viiiffiffii,Nc:invoke_viiifi,J:invoke_viiififiiiiiiiiiiii,i:invoke_viiii,Ya:invoke_viiiidididii,ha:invoke_viiiif,Ea:invoke_viiiiff,ta:invoke_viiiiffi,Ba:invoke_viiiifi,g:invoke_viiiii,Pb:invoke_viiiiif,Fa:invoke_viiiiiff,Xa:invoke_viiiiiffiii,$a:invoke_viiiiifi,m:invoke_viiiiii,p:invoke_viiiiiii,O:invoke_viiiiiiii,U:invoke_viiiiiiiii,L:invoke_viiiiiiiiii,oa:invoke_viiiiiiiiiii,Y:invoke_viiiiiiiiiiiiiii,Ha:invoke_viiiiiji,ab:invoke_viiiijjiiiiff,K:invoke_viiij,w:invoke_viiijii,da:invoke_viij,o:invoke_viiji,fa:invoke_viijiff,X:invoke_viijiiiif,ra:invoke_viijj,R:invoke_vij,va:invoke_vijff,bb:invoke_viji,C:invoke_vijii,ua:invoke_vijiifi,sa:invoke_vijiii,$:invoke_vijjjj,sc:invoke_vjii,ia:_llvm_eh_typeid_for,sb:_random_get};function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_v(index){var sp=stackSave();try{getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ji(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_viiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiij(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vij(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vff(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viij(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiji(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiff(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiif(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiji(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiffii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiff(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vid(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiifiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vif(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffi(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiifi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiff(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vjii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiffi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viifiiifi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiif(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiif(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiifi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiffi(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viififii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijiff(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijff(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viji(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiijjiiiiff(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijiifi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiffi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiifi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijj(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiififiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viijiiiif(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiffiffii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iif(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jjji(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_iijj(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viif(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiffiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viff(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vifffff(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fiiidi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viidii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiidididii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fiiiidi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiffiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiijii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffff(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffffff(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fiiif(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fdiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fdiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viffiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_dddd(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_dd(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vijjjj(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_j(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_iiiiid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0);return 0n}}function invoke_fiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_diii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function callMain(args=[]){var entryFunction=_main;args.unshift(thisProgram);var argc=args.length;var argv=stackAlloc((argc+1)*4);var argv_ptr=argv;for(var arg of args){HEAPU32[argv_ptr>>2]=stringToUTF8OnStack(arg);argv_ptr+=4}HEAPU32[argv_ptr>>2]=0;try{var ret=entryFunction(argc,argv);exitJS(ret,true);return ret}catch(e){return handleException(e)}}function run(args=arguments_){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();var noInitialRun=Module["noInitialRun"]||false;if(!noInitialRun)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} ;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=createGridaCanvas;module.exports.default=createGridaCanvas}else if(typeof define==="function"&&define["amd"])define([],()=>createGridaCanvas); diff --git a/crates/grida-canvas-wasm/lib/bin/grida_canvas_wasm.wasm b/crates/grida-canvas-wasm/lib/bin/grida_canvas_wasm.wasm index dc2c119f23..6554c9ba48 100755 --- a/crates/grida-canvas-wasm/lib/bin/grida_canvas_wasm.wasm +++ b/crates/grida-canvas-wasm/lib/bin/grida_canvas_wasm.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d2b2e1ea6a31e345188e59b06297e8cd04ac2da172f4424b28c97698db5a272 -size 12843871 +oid sha256:5f06878d460fab4e85c4d25682667db4927f1203aa1cbf2ab4b2b01b0ffb6522 +size 12848424 diff --git a/crates/grida-canvas-wasm/lib/index.ts b/crates/grida-canvas-wasm/lib/index.ts index c028a6022f..c3f37826cd 100644 --- a/crates/grida-canvas-wasm/lib/index.ts +++ b/crates/grida-canvas-wasm/lib/index.ts @@ -1,8 +1,17 @@ import createGridaCanvas from "./grida-canvas-wasm"; import { version as _version } from "../package.json"; -import { Scene } from "./modules/canvas"; +import { + Scene, + type CreateImageResourceResult, + type AddImageWithIdResult, +} from "./modules/canvas"; import { svgtypes } from "./modules/svg-bindings"; -export { type Scene, type svgtypes }; +export { + type Scene, + type svgtypes, + type CreateImageResourceResult, + type AddImageWithIdResult, +}; export const version = _version; export interface GridaCanvasModuleInitOptions { @@ -227,6 +236,39 @@ export class Canvas { this._scene.addFont(family, bytes); } + /** + * Set the default fallback font families. Order matters for script fallback (e.g. CJK). + */ + setFallbackFonts(fonts: string[]) { + this._scene.setFallbackFonts(fonts); + } + + /** + * Get the current default fallback font families. + */ + getFallbackFonts(): string[] { + return this._scene.getFallbackFonts(); + } + + /** + * Register image bytes with content-addressed RID (e.g. mem://<hash>). + * Use when you do not need a stable logical identifier. + */ + addImage(data: Uint8Array): CreateImageResourceResult | false { + return this._scene.addImage(data); + } + + /** + * Register image bytes with an explicit logical RID (e.g. res://images/logo.png). + * Use when you need stable, document-mapped identifiers. + */ + addImageWithId( + data: Uint8Array, + rid: string + ): AddImageWithIdResult | false { + return this._scene.addImageWithId(data, rid); + } + exportNodeAs(id: string, format: types.ExportAs): { data: Uint8Array } { return this._scene.exportNodeAs(id, format); } diff --git a/crates/grida-canvas-wasm/lib/modules/canvas-bindings.d.ts b/crates/grida-canvas-wasm/lib/modules/canvas-bindings.d.ts index 7ba95785d4..1bd1dca308 100644 --- a/crates/grida-canvas-wasm/lib/modules/canvas-bindings.d.ts +++ b/crates/grida-canvas-wasm/lib/modules/canvas-bindings.d.ts @@ -63,6 +63,13 @@ declare namespace canvas { data_ptr: number, data_len: number ): Ptr; + _add_image_with_rid( + state: GridaCanvasApplicationPtr, + data_ptr: number, + data_len: number, + rid_ptr: number, + rid_len: number + ): Ptr; _get_image_bytes( state: GridaCanvasApplicationPtr, ref_ptr: number, diff --git a/crates/grida-canvas-wasm/lib/modules/canvas.ts b/crates/grida-canvas-wasm/lib/modules/canvas.ts index ba76d94fd8..88559aec00 100644 --- a/crates/grida-canvas-wasm/lib/modules/canvas.ts +++ b/crates/grida-canvas-wasm/lib/modules/canvas.ts @@ -20,6 +20,12 @@ export interface CreateImageResourceResult { type: string; } +export interface AddImageWithIdResult { + width: number; + height: number; + type: string; +} + export interface TransactionApplyReport { success: boolean; applied: number; @@ -156,6 +162,31 @@ export class Scene { } } + addImageWithId( + data: Uint8Array, + rid: string + ): AddImageWithIdResult | false { + this._assertAlive(); + const [dataPtr, dataLen] = ffi.allocBytes(this.module, data); + const [ridPtr, ridLen] = this._alloc_string(rid); + const out = this.module._add_image_with_rid( + this.appptr, + dataPtr, + dataLen, + ridPtr, + ridLen - 1 + ); + ffi.free(this.module, dataPtr, dataLen); + this._free_string(ridPtr, ridLen); + if (out === 0) return false; + const txt = ffi.readLenPrefixedString(this.module, out); + try { + return JSON.parse(txt) as AddImageWithIdResult; + } catch { + return false; + } + } + getImageBytes(ref: string): Uint8Array | null { this._assertAlive(); const [ptr, len] = this._alloc_string(ref); diff --git a/crates/grida-canvas-wasm/package.json b/crates/grida-canvas-wasm/package.json index 4270619035..33715ce0ab 100644 --- a/crates/grida-canvas-wasm/package.json +++ b/crates/grida-canvas-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@grida/canvas-wasm", - "version": "0.90.0-canary.5", + "version": "0.90.0-canary.8", "private": false, "description": "WASM bindings for Grida Canvas", "keywords": [ diff --git a/crates/grida-canvas-wasm/src/wasm_application.rs b/crates/grida-canvas-wasm/src/wasm_application.rs index 72854f51a5..7320d6d9b3 100644 --- a/crates/grida-canvas-wasm/src/wasm_application.rs +++ b/crates/grida-canvas-wasm/src/wasm_application.rs @@ -223,6 +223,42 @@ pub unsafe extern "C" fn add_image( std::ptr::null() } +#[derive(Serialize)] +pub struct AddImageWithRidResult { + pub width: u32, + pub height: u32, + #[serde(rename = "type")] + pub r#type: String, +} + +#[no_mangle] +/// js::_add_image_with_rid +pub unsafe extern "C" fn add_image_with_rid( + app: *mut UnknownTargetApplication, + data_ptr: *const u8, + data_len: usize, + rid_ptr: *const u8, + rid_len: usize, +) -> *const u8 { + if let (Some(app), Some(rid)) = ( + app.as_mut(), + __str_from_ptr_len(rid_ptr, rid_len), + ) { + let data = std::slice::from_raw_parts(data_ptr, data_len); + if let Some((width, height, r#type)) = app.add_image_with_rid(data, &rid) { + let result = AddImageWithRidResult { + width, + height, + r#type, + }; + if let Ok(json) = serde_json::to_string(&result) { + return alloc_len_prefixed(json.as_bytes()); + } + } + } + std::ptr::null() +} + #[no_mangle] /// js::_get_image_bytes pub unsafe extern "C" fn get_image_bytes( diff --git a/crates/grida-canvas/examples/tool_io_grida.rs b/crates/grida-canvas/examples/tool_io_grida.rs index 5266fe0e4a..f290d4599b 100644 --- a/crates/grida-canvas/examples/tool_io_grida.rs +++ b/crates/grida-canvas/examples/tool_io_grida.rs @@ -56,6 +56,7 @@ fn main() { cg::io::io_grida::JSONNode::Group(_) => "group", cg::io::io_grida::JSONNode::Container(_) => "container", cg::io::io_grida::JSONNode::Vector(_) => "vector", + cg::io::io_grida::JSONNode::Path(_) => "path", cg::io::io_grida::JSONNode::Ellipse(_) => "ellipse", cg::io::io_grida::JSONNode::Rectangle(_) => "rectangle", cg::io::io_grida::JSONNode::RegularPolygon(_) => "polygon", diff --git a/crates/grida-canvas/src/io/id_converter.rs b/crates/grida-canvas/src/io/id_converter.rs index 0d2832ac60..d3caa79d4a 100644 --- a/crates/grida-canvas/src/io/id_converter.rs +++ b/crates/grida-canvas/src/io/id_converter.rs @@ -138,6 +138,7 @@ impl IdConverter { JSONNode::Group(group) => Node::Group(GroupNodeRec::from(group)), JSONNode::Container(container) => Node::Container(ContainerNodeRec::from(container)), JSONNode::Vector(vector) => Node::from(JSONNode::Vector(vector)), + JSONNode::Path(path) => Node::from(JSONNode::Path(path)), JSONNode::Ellipse(ellipse) => Node::from(JSONNode::Ellipse(ellipse)), JSONNode::Rectangle(rectangle) => Node::from(JSONNode::Rectangle(rectangle)), JSONNode::RegularPolygon(polygon) => Node::from(JSONNode::RegularPolygon(polygon)), diff --git a/crates/grida-canvas/src/io/io_grida.rs b/crates/grida-canvas/src/io/io_grida.rs index a03d9c183a..b2035dd0ca 100644 --- a/crates/grida-canvas/src/io/io_grida.rs +++ b/crates/grida-canvas/src/io/io_grida.rs @@ -870,6 +870,8 @@ pub enum JSONNode { Container(JSONContainerNode), #[serde(rename = "vector")] Vector(JSONVectorNode), + #[serde(rename = "path")] + Path(JSONPathNode), #[serde(rename = "ellipse")] Ellipse(JSONEllipseNode), #[serde(rename = "rectangle")] @@ -1158,6 +1160,15 @@ pub struct JSONVectorNode { pub vector_network: Option, } +#[derive(Debug, Deserialize)] +pub struct JSONPathNode { + #[serde(flatten)] + pub base: JSONUnknownNodeProperties, + + #[serde(rename = "data")] + pub data: String, +} + #[derive(Debug, Deserialize)] pub struct JSONEllipseNode { #[serde(flatten)] @@ -1837,6 +1848,54 @@ impl From for Node { } } +impl From for Node { + fn from(node: JSONPathNode) -> Self { + let stroke_width: SingularStrokeWidth = build_unknown_stroke_width(&node.base).into(); + + let transform = AffineTransform::from_box_center( + node.base.layout_inset_left.unwrap_or(0.0), + node.base.layout_inset_top.unwrap_or(0.0), + node.base.width.length(0.0), + node.base.height.length(0.0), + node.base.rotation, + ); + + Node::Path(PathNodeRec { + active: node.base.active, + opacity: node.base.opacity, + blend_mode: node.base.blend_mode.into(), + mask: node.base.mask.map(|m| m.into()), + effects: merge_effects( + node.base.fe_shadows, + node.base.fe_blur, + node.base.fe_backdrop_blur, + node.base.fe_liquid_glass, + node.base.fe_noises, + ), + transform, + fills: merge_paints(node.base.fill, node.base.fill_paints), + data: node.data, + strokes: merge_paints(node.base.stroke, node.base.stroke_paints), + stroke_style: StrokeStyle { + stroke_align: node.base.stroke_align.unwrap_or(StrokeAlign::Inside), + stroke_cap: node.base.stroke_cap.unwrap_or_default(), + stroke_join: node.base.stroke_join.unwrap_or_default(), + stroke_miter_limit: node.base.stroke_miter_limit.unwrap_or_default(), + stroke_dash_array: node.base.stroke_dash_array.map(StrokeDashArray::from), + }, + stroke_width, + layout_child: Some(LayoutChildStyle { + layout_positioning: node + .base + .layout_positioning + .map(|position| position.into()) + .unwrap_or_default(), + layout_grow: 0.0, + }), + }) + } +} + impl From for Node { fn from(node: JSONVectorNode) -> Self { let transform = AffineTransform::from_box_center( @@ -1949,6 +2008,7 @@ impl From for Node { JSONNode::Container(container) => Node::Container(container.into()), JSONNode::TextSpan(text) => Node::TextSpan(text.into()), JSONNode::Vector(vector) => vector.into(), + JSONNode::Path(path) => path.into(), JSONNode::Ellipse(ellipse) => ellipse.into(), JSONNode::Rectangle(rectangle) => rectangle.into(), JSONNode::RegularPolygon(rpolygon) => rpolygon.into(), @@ -2412,6 +2472,41 @@ mod tests { } } + #[test] + fn deserialize_path_node() { + let json = r#"{ + "id": "path-1", + "name": "Path node", + "type": "path", + "data": "M0 0 L100 0 L100 100 L0 100 Z", + "layout_inset_left": 10.0, + "layout_inset_top": 20.0, + "layout_target_width": 100.0, + "layout_target_height": 100.0, + "fill": {"type": "solid", "color": {"r": 1, "g": 0, "b": 0, "a": 1.0}} + }"#; + + let json_node: JSONNode = + serde_json::from_str(json).expect("failed to deserialize PathNode"); + + match &json_node { + JSONNode::Path(path_node) => { + assert_eq!(path_node.base.id, "path-1"); + assert_eq!(path_node.base.name, Some("Path node".to_string())); + assert_eq!(path_node.data, "M0 0 L100 0 L100 100 L0 100 Z"); + } + _ => panic!("Expected Path node"), + } + + let grid_node = Node::from(json_node); + match &grid_node { + Node::Path(path_rec) => { + assert_eq!(path_rec.data, "M0 0 L100 0 L100 100 L0 100 Z"); + } + _ => panic!("Expected Node::Path"), + } + } + #[test] fn deserialize_json_vector_network() { // Test with a simple vector network diff --git a/crates/grida-canvas/src/resources/README.md b/crates/grida-canvas/src/resources/README.md index f7411000d3..3759ad7afc 100644 --- a/crates/grida-canvas/src/resources/README.md +++ b/crates/grida-canvas/src/resources/README.md @@ -10,4 +10,9 @@ This module exposes: - **`ByteStore`** – content-addressed blob store using SeaHash. - **`ResourceIndex`** – mapping between RIDs and blob hashes. +**Image registration** (Renderer API): + +- `add_image(bytes)` → content-addressed; stores under `res://images/` (SeaHash of bytes). +- `add_image_with_rid(bytes, rid)` → logical RID; caller specifies `rid`, which must start with `res://` or `system://`. + Suitable for WASM or embedded builds where reliable file systems are unavailable. diff --git a/crates/grida-canvas/src/runtime/scene.rs b/crates/grida-canvas/src/runtime/scene.rs index a22ab4468f..fac6da1dde 100644 --- a/crates/grida-canvas/src/runtime/scene.rs +++ b/crates/grida-canvas/src/runtime/scene.rs @@ -338,6 +338,21 @@ impl Renderer { (hash_str, rid, width, height, r#type) } + /// Register image bytes under a caller-specified RID (res:// or system://). + /// Implements the res:// logical-identifier path per feat-resources Level 1. + /// Returns metadata or None if rid is invalid (must start with res:// or system://). + pub fn add_image_with_rid(&mut self, bytes: &[u8], rid: &str) -> Option<(u32, u32, String)> { + let rid = rid.trim(); + if !rid.starts_with("res://") && !rid.starts_with("system://") { + return None; + } + let hash = resources::hash_bytes(bytes); + self.resources.insert(rid, bytes.to_vec()); + let (width, height) = self.images.insert(rid.to_string(), hash)?; + let r#type = detect_image_mime(bytes).to_string(); + Some((width, height, r#type)) + } + pub fn get_image_bytes(&self, id: &str) -> Option> { let rid = normalize_image_id(id); self.resources.get(&rid) diff --git a/crates/grida-canvas/src/window/application.rs b/crates/grida-canvas/src/window/application.rs index 82872fdf24..eaef79ffa2 100644 --- a/crates/grida-canvas/src/window/application.rs +++ b/crates/grida-canvas/src/window/application.rs @@ -874,6 +874,11 @@ impl UnknownTargetApplication { self.renderer.add_image(data) } + /// Register image bytes under a caller-specified RID (res:// or system://). + pub fn add_image_with_rid(&mut self, data: &[u8], rid: &str) -> Option<(u32, u32, String)> { + self.renderer.add_image_with_rid(data, rid) + } + /// Perform a redraw and print diagnostic information. pub fn redraw(&mut self) { let now = self.clock.now() + self.last_frame_time.elapsed().as_secs_f64() * 1000.0; diff --git a/crates/grida-canvas/src/window/application_emscripten.rs b/crates/grida-canvas/src/window/application_emscripten.rs index 6f57551c59..a1ba35a000 100644 --- a/crates/grida-canvas/src/window/application_emscripten.rs +++ b/crates/grida-canvas/src/window/application_emscripten.rs @@ -388,6 +388,11 @@ impl EmscriptenApplication { self.base.renderer.add_image(data) } + /// Register image bytes under a caller-specified RID (res:// or system://). + pub fn add_image_with_rid(&mut self, data: &[u8], rid: &str) -> Option<(u32, u32, String)> { + self.base.renderer.add_image_with_rid(data, rid) + } + pub fn get_image_bytes(&self, id: &str) -> Option> { self.base.get_image_bytes(id) } diff --git a/crates/grida-canvas/tests/resources_image_rid.rs b/crates/grida-canvas/tests/resources_image_rid.rs new file mode 100644 index 0000000000..30e0ef7ce6 --- /dev/null +++ b/crates/grida-canvas/tests/resources_image_rid.rs @@ -0,0 +1,117 @@ +//! Integration tests for add_image_with_rid (custom RID image registration). +//! Verifies the feat-resources Level 1 res:// logical-identifier path. + +use cg::cg::prelude::*; +use cg::node::factory::NodeFactory; +use cg::node::scene_graph::{Parent, SceneGraph}; +use cg::node::schema::*; +use cg::runtime::camera::Camera2D; +use cg::runtime::scene::{Backend, Renderer, RendererOptions}; +use math2::{box_fit::BoxFit, rect::Rectangle, transform::AffineTransform}; + +const IMAGE_DATA: &[u8] = include_bytes!("../../../fixtures/images/4k.jpg"); + +#[test] +fn add_image_with_rid_succeeds_and_retrieval_works() { + let mut renderer = Renderer::new_with_options( + Backend::new_from_raster(64, 64), + None, + Camera2D::new_from_bounds(Rectangle::from_xywh(0.0, 0.0, 64.0, 64.0)), + RendererOptions { + use_embedded_fonts: true, + }, + ); + + let rid = "res://images/test-logo"; + let result = renderer.add_image_with_rid(IMAGE_DATA, rid); + assert!(result.is_some(), "add_image_with_rid should succeed"); + let (width, height, _) = result.unwrap(); + assert!(width > 0 && height > 0, "image dimensions should be positive"); + + let bytes = renderer.get_image_bytes(rid); + assert!(bytes.is_some(), "get_image_bytes by full RID should return bytes"); + assert_eq!(bytes.as_ref().unwrap(), IMAGE_DATA); + + let bytes_normalized = renderer.get_image_bytes("test-logo"); + assert!( + bytes_normalized.is_some(), + "get_image_bytes by bare suffix should normalize to res://images/" + ); + assert_eq!(bytes_normalized.as_ref().unwrap(), IMAGE_DATA); +} + +#[test] +fn add_image_with_rid_rejects_invalid_rid() { + let mut renderer = Renderer::new_with_options( + Backend::new_from_raster(64, 64), + None, + Camera2D::new_from_bounds(Rectangle::from_xywh(0.0, 0.0, 64.0, 64.0)), + RendererOptions { + use_embedded_fonts: true, + }, + ); + + assert!( + renderer.add_image_with_rid(IMAGE_DATA, "invalid-no-prefix").is_none(), + "rid without res:// or system:// should be rejected" + ); + assert!( + renderer.add_image_with_rid(IMAGE_DATA, "mem://abc123").is_none(), + "mem:// is content-addressed; custom RID should use res:// or system://" + ); +} + +#[test] +fn add_image_with_rid_scene_renders() { + let nf = NodeFactory::new(); + let mut graph = SceneGraph::new(); + + let rid = "res://images/custom-test-image"; + let mut rect = nf.create_rectangle_node(); + rect.transform = AffineTransform::new(0.0, 0.0, 0.0); + rect.size = Size { + width: 64.0, + height: 64.0, + }; + rect.fills = Paints::new([Paint::Image(ImagePaint { + active: true, + image: ResourceRef::RID(rid.to_string()), + quarter_turns: 0, + alignement: Alignment::CENTER, + fit: ImagePaintFit::Fit(BoxFit::Cover), + opacity: 1.0, + blend_mode: BlendMode::Normal, + filters: ImageFilters::default(), + })]); + + graph.append_child(Node::Rectangle(rect), Parent::Root); + + let scene = Scene { + name: "Image RID Test".into(), + graph, + background_color: Some(CGColor::from_rgba(255, 255, 255, 255)), + }; + + let mut renderer = Renderer::new_with_options( + Backend::new_from_raster(64, 64), + None, + Camera2D::new_from_bounds(Rectangle::from_xywh(0.0, 0.0, 64.0, 64.0)), + RendererOptions { + use_embedded_fonts: true, + }, + ); + + let result = renderer.add_image_with_rid(IMAGE_DATA, rid); + assert!(result.is_some(), "must register image before load_scene"); + + renderer.load_scene(scene); + let surface = unsafe { &mut *renderer.backend.get_surface() }; + let canvas = surface.canvas(); + renderer.render_to_canvas(canvas, 64.0, 64.0); + + let snapshot = surface.image_snapshot(); + let data = snapshot + .encode(None, skia_safe::EncodedImageFormat::PNG, None) + .unwrap(); + assert!(!data.as_bytes().is_empty(), "render should produce output"); +} diff --git a/docs/packages/@grida/refig/index.md b/docs/packages/@grida/refig/index.md new file mode 100644 index 0000000000..3145ec4e92 --- /dev/null +++ b/docs/packages/@grida/refig/index.md @@ -0,0 +1,82 @@ +--- +title: refig +--- + +### `@grida/refig` (refig) + +**refig** is a headless Figma renderer: render Figma documents to **PNG, JPEG, WebP, PDF, or SVG** in **Node.js (no browser required)** or directly in the **browser**. + +It’s built for **deterministic exports** (CI-friendly), **offline rendering** (from `.fig`), and **high-throughput previews** (thumbnails, snapshots, asset pipelines). + +- **npm package**: [`@grida/refig`](https://www.npmjs.com/package/@grida/refig) (technical reference, API, and CLI usage) + +--- + +### What it does (and doesn’t) + +- **Renders pixels (and vector outputs)** from a Figma document + a target node id +- **Does not** fetch from the Figma API for you (bring your own auth + HTTP client) +- **Does not** do design-to-code (HTML/CSS/Flutter). It exports images/doc bytes. + +--- + +### Inputs you can render + +- **`.fig` files** (offline): great for air-gapped builds and reproducible rendering +- **Figma REST API file JSON** (`GET /v1/files/:key`): great when you already have an API ingestion layer + +If your design uses **IMAGE fills**: + +- With **`.fig`** input, image bytes are embedded in the file. +- With **REST JSON**, the document references image hashes; you provide the bytes (commonly by downloading image fills once and reusing them). + +--- + +### Two ways to use it + +- **Library**: `FigmaDocument` + `FigmaRenderer` (Node and browser entrypoints) +- **CLI**: `refig` for scripting and batch exports + +--- + +### Quick start (Node.js) + +```ts +import { writeFileSync } from "node:fs"; +import { FigmaDocument, FigmaRenderer } from "@grida/refig"; + +const doc = FigmaDocument.fromFile("./design.fig"); // .fig or REST JSON .json +const renderer = new FigmaRenderer(doc); + +const { data } = await renderer.render("1:23", { format: "png", scale: 2 }); +writeFileSync("out.png", data); + +renderer.dispose(); +``` + +--- + +### Quick start (CLI) + +```sh +# Render a single node +npx @grida/refig ./design.fig --node "1:23" --out ./out.png + +# Export everything that has Figma export presets configured +npx @grida/refig ./design.fig --export-all --out ./exports +``` + +--- + +### Why teams use refig + +- **Deterministic exports** in CI (avoid flaky UI automation) +- **Offline rendering** from `.fig` archives +- **Faster previews** than API-driven “export images” workflows in high-volume pipelines +- **One renderer** for Node and browser (use the browser entrypoint for in-app previews) + +--- + +### Learn more + +- **Package & reference docs on npm**: [`@grida/refig`](https://www.npmjs.com/package/@grida/refig) diff --git a/docs/wg/feat-resources/index.md b/docs/wg/feat-resources/index.md index 605fbf6b4b..77b36817c2 100644 --- a/docs/wg/feat-resources/index.md +++ b/docs/wg/feat-resources/index.md @@ -40,29 +40,49 @@ This document proposes a high-level architecture for resource management. Designed for quick delivery. May use more memory than needed, with limited eviction, and occasional instability under heavy load is acceptable. -- **Identity & Resolution:** +#### 1.1 Formal Definitions + +**RID syntax:** + +- `res://` — logical identifier; path is an opaque string (e.g. `images/logo.png`, `images/`) +- `mem://` — content-addressed; hex16 = 16 lowercase hex digits (SeaHash) +- `system://` — built-in resources; reserved namespace + +**Image resolution rules:** + +- If `id` starts with `res://` or `system://` → use as-is for lookup +- Otherwise → treat as bare suffix and resolve as `res://images/` +**Registration modes:** + +| API | RID assignment | Use case | +| -------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------- | +| `add_image(bytes)` | `res://images/` | Content-addressed; caller receives assigned RID | +| `add_image_with_rid(bytes, rid)` | `rid` (caller-specified) | Logical; external document mapping (e.g. images/logo.png -> res://images/logo.png) | + +**Preconditions for `add_image_with_rid`:** + +- `rid` must start with `res://` or `system://` +- Overwriting an existing RID replaces the mapping (document behavior) + +- **Identity & Resolution:** - Support `res://` (simple table) and `mem://` (content hash). - `data:` MAY be supported for testing. - **ByteStore:** - - Store blobs by content hash (see [Fast Hashing - `hash-nch`](../feat-hash-nch/index.md)). - Fixed memory budget is RECOMMENDED, but eviction MAY be skipped. - **Fonts:** - - Create a `Typeface` directly from bytes. - Caller’s bytes can be dropped after creation. - **Images:** - - Keep encoded bytes. - Decode lazily via renderer. - Placeholder required on decode failure. - **Eviction:** - - Manual removal APIs (`remove(id)`) are sufficient. - Automatic eviction is optional. @@ -76,37 +96,31 @@ Designed for quick delivery. May use more memory than needed, with limited evict A production-grade design, with proper caching, eviction, and memory pressure handling. - **Identity & Resolution:** - - Support `res://`, `mem://`, `data:`; MAY add `pack://` and `https://`. - Maintain a `ResourceIndex` (RID ↔ blob). - Content-addressed dedupe across documents. - **ByteStore:** - - Use content hashes (see [Fast Hashing - `hash-nch`](../feat-hash-nch/index.md)). - Implement LRU (or similar) with configurable budget. - Support pin/unpin. - Provide `createUrl(bytes) → mem://...` and `revokeUrl(url)` APIs. - **Decoders & Realizers:** - - Separate decoding (bytes → decoded asset) from realization (decoded → renderer object). - Maintain independent budgets for decoded and realized layers. - Use composite cache keys (e.g., blob hash + scale bucket). - **Fonts:** - - Cache typefaces by blob hash and parameters. - Bytes MAY be dropped after creating the typeface, unless zero-copy mode is used (in which case bytes MUST be pinned). - **Images:** - - Prefer lazy decode (keep encoded bytes). - Manage decoded pixels (CPU) separately from realized GPU textures. - Evict decoded and realized objects under pressure. - **Eviction & Pressure:** - - Eviction order: Realized → Decoded → Bytes. - Respond to memory pressure signals. - Expose `onEvicted` events with reason (`memory-pressure`, `manual`, etc.). diff --git a/editor/grida-canvas-hosted/playground/playground.tsx b/editor/grida-canvas-hosted/playground/playground.tsx index 8d502ae0e2..88eff490dd 100644 --- a/editor/grida-canvas-hosted/playground/playground.tsx +++ b/editor/grida-canvas-hosted/playground/playground.tsx @@ -468,16 +468,14 @@ export default function CanvasPlayground({ if (bytes && !cancelled) { const loadedDocument = io.GRID.decode(bytes); - // Load images from OPFS (images/.) const images: Record = {}; const names = await opfs.listImages(); for (const name of names) { const base = name.split("/").pop() ?? name; - const hash = base.includes(".") ? base.split(".")[0]! : base; + const ref = base.includes(".") ? base.split(".")[0]! : base; try { - const img = await opfs.readImage(name); - images[hash] = img; - } catch (e) { + images[ref] = await opfs.readImage(name); + } catch { // Ignore per-file errors; we still load the document. } } @@ -490,11 +488,8 @@ export default function CanvasPlayground({ "opfs" ); - // Load image assets into the WASM runtime. - // This is safe to call before mount; the editor will defer and apply after mount. - const imageCount = Object.keys(images).length; - if (imageCount > 0) { - instance.loadImagesToWasmSurface(images); + if (Object.keys(images).length > 0) { + instance.loadImages(images); } setDocumentReady(true); diff --git a/editor/grida-canvas-hosted/playground/uxhost-menu.tsx b/editor/grida-canvas-hosted/playground/uxhost-menu.tsx index 8b32520a4c..8251122fa9 100644 --- a/editor/grida-canvas-hosted/playground/uxhost-menu.tsx +++ b/editor/grida-canvas-hosted/playground/uxhost-menu.tsx @@ -156,6 +156,12 @@ export function PlaygroundMenuContent({ key={importFromJson.refreshkey} {...importFromJson.props} onImport={(file) => { + if ( + file.assets?.images && + Object.keys(file.assets.images).length > 0 + ) { + instance.loadImages(file.assets.images); + } instance.commands.reset( editor.state.init({ editable: true, @@ -168,16 +174,41 @@ export function PlaygroundMenuContent({ { - instance.surface.insert({ - document: iofigma.restful.factory.document( + onImport={async (res) => { + const images = res.images ?? {}; + const context: iofigma.restful.factory.FactoryContext = { + gradient_id_generator: () => v4(), + resolve_image_src: (ref) => + ref in images ? `res://images/${ref}` : null, + }; + const { document: gridaDoc, imageRefsUsed } = + iofigma.restful.factory.document( res.document as any, - res.images, - { - gradient_id_generator: () => v4(), + images, + context + ); + + const refToBytes: Record = {}; + for (const ref of imageRefsUsed) { + if (!(ref in images)) continue; + const url = images[ref]; + if (!url) continue; + try { + const resp = await fetch(url); + if (resp.ok) { + const buf = await resp.arrayBuffer(); + refToBytes[ref] = new Uint8Array(buf); } - ), - }); + } catch (e) { + console.warn(`Failed to fetch image for ref ${ref}`, e); + } + } + + if (Object.keys(refToBytes).length > 0) { + instance.loadImages(refToBytes); + } + + instance.surface.insert({ document: gridaDoc }); }} onImportFig={async (result) => { const iofigma = await import("@grida/io-figma"); @@ -199,9 +230,12 @@ export function PlaygroundMenuContent({ }); if (page.rootNodes.length > 0) { - const packedDoc = FigImporter.convertPageToScene(page, { - gradient_id_generator: () => v4(), - }); + const { document: packedDoc } = FigImporter.convertPageToScene( + page, + { + gradient_id_generator: () => v4(), + } + ); instance.surface.insert({ document: packedDoc }); } } diff --git a/editor/grida-canvas-react-renderer-dom/nodes/node.tsx b/editor/grida-canvas-react-renderer-dom/nodes/node.tsx index f16ccef066..057d1e49e1 100644 --- a/editor/grida-canvas-react-renderer-dom/nodes/node.tsx +++ b/editor/grida-canvas-react-renderer-dom/nodes/node.tsx @@ -225,6 +225,7 @@ const fillings = { instance: "none", line: "none", vector: "none", + path: "none", polyline: "none", bitmap: "background", } as const; diff --git a/editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx b/editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx index e055820b6a..ac0976e2ce 100644 --- a/editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx +++ b/editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx @@ -95,7 +95,7 @@ export function ImportFromFigmaDialog({ onImportFig, ...props }: React.ComponentProps & { - onImport?: (node: FetchNodeResult) => void; + onImport?: (node: FetchNodeResult) => void | Promise; onImportFig?: (result: FigFileImportResult) => Promise; }) { return ( @@ -414,7 +414,7 @@ function FigmaApiImportTab({ onImport, onClose, }: { - onImport?: (node: FetchNodeResult) => void; + onImport?: (node: FetchNodeResult) => void | Promise; onClose: () => void; }) { const form = useRef(null); @@ -433,8 +433,8 @@ function FigmaApiImportTab({ id: nodeid, personalAccessToken: token, }) - .then((r) => { - onImport?.(r); + .then(async (r) => { + await onImport?.(r); }) .catch((e) => { console.error(e); diff --git a/editor/grida-canvas-react/use-data-transfer.ts b/editor/grida-canvas-react/use-data-transfer.ts index 9d2113329e..190930b55f 100644 --- a/editor/grida-canvas-react/use-data-transfer.ts +++ b/editor/grida-canvas-react/use-data-transfer.ts @@ -203,7 +203,7 @@ async function tryInsertFromFigmaClipboardPayload( // Convert each root node to Grida document (will recursively process children) const payloads: editor.api.InsertPayload[] = rootNodes.map((figmaNode) => { - const gridaDoc = iofigma.restful.factory.document( + const { document: gridaDoc } = iofigma.restful.factory.document( figmaNode, {}, // images map (empty for clipboard paste) context diff --git a/editor/grida-canvas/editor.ts b/editor/grida-canvas/editor.ts index b34927fd69..86f9148acd 100644 --- a/editor/grida-canvas/editor.ts +++ b/editor/grida-canvas/editor.ts @@ -2855,42 +2855,6 @@ export class Editor } } - private static __isRecord(v: unknown): v is Record { - return typeof v === "object" && v !== null && !Array.isArray(v); - } - - /** - * Recursively rewrites `{ type: "image", src: string }` paint values in-place. - * - * Returns the number of rewritten `src` fields. - */ - private static __rewriteImagePaintSrcDeep( - value: unknown, - mapSrc: (src: string) => string | undefined - ): number { - if (Array.isArray(value)) { - let sum = 0; - for (const item of value) - sum += Editor.__rewriteImagePaintSrcDeep(item, mapSrc); - return sum; - } - if (!Editor.__isRecord(value)) return 0; - - let rewrites = 0; - if (value["type"] === "image" && typeof value["src"] === "string") { - const mapped = mapSrc(value["src"]); - if (mapped && mapped !== value["src"]) { - value["src"] = mapped; - rewrites++; - } - } - - for (const key of Object.keys(value)) { - rewrites += Editor.__rewriteImagePaintSrcDeep(value[key], mapSrc); - } - return rewrites; - } - public subscribe(fn: editor.api.SubscriptionCallbackFn) { // TODO: we can have a single subscription to the document and use that. // Subscribe to the document store changes @@ -2908,14 +2872,11 @@ export class Editor * - OPFS persistence (playground `Cmd/Ctrl+S`) * * What it does: - * - Collects **used** image paint `src` references in the document. - * - For the WASM backend, resolves those runtime refs (`mem://...`) to bytes and a - * WASM-provided content hash (hex16), producing `images/.` entries. - * - Rewrites image paint `src` in the persisted document from runtime URL → hash id. + * - Collects **used** image paint `src` references (res://images/). + * - Parses ref from each src, resolves bytes from WASM, produces `images/.` entries. + * - Document keeps `src` unchanged (no rewrite). * - * Notes / policy: - * - We intentionally **do not** compute SeaHash in JS; persisted image IDs come from WASM. - * - For DOM backend (hosted HTML flow), we keep external `src` values as-is and do not embed. + * For DOM backend (hosted HTML flow), we keep external `src` values as-is and do not embed. * * TODO: support `thumbnail.png` generation/persistence alongside the document. */ @@ -2924,7 +2885,7 @@ export class Editor /** * Image files to persist under `images/`. * - * Keys are filenames (e.g. ".png"), values are bytes. + * Keys are filenames (e.g. ".png"), values are bytes. */ images: Record; } { @@ -2939,36 +2900,20 @@ export class Editor ); } - // Collect bytes keyed by hash, by reading them back from WASM. for (const src of usedSrcs) { - const hashHex = Editor.__try_parse_hex16_from_image_src(src); - if (!hashHex) { - throw new Error( - `Cannot persist image paint src (expected res://images/, mem://, or ): "${src}"` - ); - } - - // Rust/WASM normalizes non-RID ids into `res://images/`. - const bytes = this.__get_image_bytes_for_wasm(hashHex); + const ref = Editor.__parse_image_ref_from_src(src); + const bytes = this.__get_image_bytes_for_wasm(src); if (!bytes) { throw new Error( - `Cannot persist image bytes (WASM missing resource for "${hashHex}")` + `Cannot persist image bytes (WASM missing resource for "${ref}")` ); } - - const mime = snapshot.images?.[hashHex]?.type; + const mime = snapshot.images?.[ref]?.type; const ext = (mime && mime.split("/")[1]) || "bin"; - images[`${hashHex}.${ext}`] = bytes; + images[`${ref}.${ext}`] = bytes; } - // Rewrite persisted image paint refs (runtime URL -> hash id) before encoding. - const persisted = structuredClone(snapshot); - Editor.__rewriteImagePaintSrcDeep(persisted, (src) => { - const hex = Editor.__try_parse_hex16_from_image_src(src); - return hex ?? undefined; - }); - - return { document: persisted, images }; + return { document: structuredClone(snapshot), images }; } public archive(): Blob { @@ -3034,15 +2979,13 @@ export class Editor this._do_legacy_warmup(); - // If we had pending image assets (e.g. loaded from an archive before mount), - // register them into WASM now. - if (this._pending_image_assets) { - const pending = this._pending_image_assets; - this._pending_image_assets = null; + if (this._pending_images) { + const pending = this._pending_images; + this._pending_images = null; try { - this.loadImagesToWasmSurface(pending); + this.loadImages(pending); } catch (e) { - this.log("failed to load pending image assets into wasm runtime", e); + this.log("failed to load pending images into wasm runtime", e); } } } @@ -3345,74 +3288,46 @@ export class Editor private readonly images = new Map(); /** - * TODO(resource-lifecycle): replace `loadImagesToWasmSurface` + `_pending_image_assets` - * with a real lifecycle/event hook so hosts can do: - * `editor.once("surface:ready", () => loadAssetsToWasm())` - * instead of relying on a dedicated API and implicit deferral. - * - * (Skip details for now; keep current behavior stable.) - * - * Pending hash-addressed image assets loaded from an archive/OPFS before the WASM - * surface is mounted. Once mounted, we'll register and rewrite refs. + * Pending image assets (ref -> bytes) loaded before WASM mount. + * Keys are refs (the part after res://images/). */ - private _pending_image_assets: Record | null = null; + private _pending_images: Record | null = null; - private static __try_parse_hex16_from_image_src(src: string): string | null { - /** - * TODO(resources): cross-boundary image ids should be `res://...` only. - * - * - `res://...` is the engine/host boundary identifier (returned by WASM today). - * - `mem://...` should remain a rust-native/internal byte-store pointer only. - * - * Once the contract is finalized, drop `mem://` acceptance here and prefer - * parsing `res:///` generically (not hardcoded to `res://images/`). - */ - const raw = src.trim().toLowerCase(); - if (raw.startsWith("res://images/")) { - const hex = raw.slice("res://images/".length); - return /^[0-9a-f]{16}$/.test(hex) ? hex : null; - } - if (raw.startsWith("mem://")) { - const hex = raw.slice("mem://".length); - return /^[0-9a-f]{16}$/.test(hex) ? hex : null; + private static __parse_image_ref_from_src(src: string): string { + const raw = src.trim(); + if ( + raw.startsWith("res://images/") && + raw.length > "res://images/".length + ) { + return raw.slice("res://images/".length); } - return /^[0-9a-f]{16}$/.test(raw) ? raw : null; + throw new Error(`Image src must be res://images/. Got: "${src}"`); } /** - * Loads hash-addressed image bytes into the WASM renderer runtime. - * - * Why this exists: - * - DOM rendering can rely on fetchable URLs (`http(s):`, `blob:`). - * - WASM cannot fetch URLs directly; bytes must be provided by the host. - * The runtime then exposes a stable cross-boundary identifier (`res://...`). - * - * This method intentionally does **NOT** mutate the document (no `src` rewriting), - * so loading assets does not mark the document as "dirty". - * - * TODO(resource-lifecycle): evolve into a dynamic "asset server" flow where the renderer - * requests missing resources on-demand and the editor provides bytes as needed. + * Registers image bytes under RIDs (res://images/). + * Key = ref (the part after res://images/). Works for both internally-generated + * (hex16) and externally-defined refs (e.g. Figma 40-char). */ - public loadImagesToWasmSurface(images: Record) { + public loadImages(images: Record): void { if (!images || Object.keys(images).length === 0) return; - // If we're on canvas backend but not mounted yet, defer. if (this.backend === "canvas" && !this._m_wasm_canvas_scene) { - this._pending_image_assets = images; + if (!this._pending_images) { + this._pending_images = {}; + } + Object.assign(this._pending_images, images); return; } - if (this.backend !== "canvas") { - // DOM backend does not require pre-loading bytes into a renderer runtime. - return; - } + if (this.backend !== "canvas") return; assert(this._m_wasm_canvas_scene, "WASM canvas scene is not initialized"); - for (const [_hash, bytes] of Object.entries(images)) { + for (const [ref, bytes] of Object.entries(images)) { try { - this._experimental_createImage_for_wasm(bytes); + this._registerImageWithCustomRid(bytes, ref); } catch (e) { - this.log("failed to load image asset into wasm runtime", e); + this.log("failed to load image into wasm runtime", e); } } } @@ -3441,6 +3356,40 @@ export class Editor return size; } + protected _registerImageWithCustomRid( + data: Uint8Array, + ref: string + ): Readonly { + assert(this._m_wasm_canvas_scene, "WASM canvas scene is not initialized"); + + const rid = `res://images/${ref}`; + const result = this._m_wasm_canvas_scene.addImageWithId(data, rid); + if (!result) throw new Error("addImageWithId failed"); + const { width, height, type } = result; + + const imageRef: grida.program.document.ImageRef = { + url: rid, + width, + height, + bytes: data.byteLength, + type: type as grida.program.document.ImageType, + }; + + this.images.set(rid, imageRef); + this.doc.reduce((state) => { + state.document.images[ref] = { + url: rid, + width, + height, + bytes: data.byteLength, + type: type as grida.program.document.ImageType, + }; + return state; + }); + + return imageRef; + } + protected _experimental_createImage_for_wasm( data: Uint8Array ): Readonly { @@ -3460,10 +3409,8 @@ export class Editor this.images.set(url, ref); this.doc.reduce((state) => { - // Persistable image metadata is keyed by canonical content hash (hex16). - // Runtime refs use `res://images/` (returned by WASM) and are stored in `ImagePaint.src` after registration. state.document.images[hash] = { - url: hash, + url, width, height, bytes: data.byteLength, diff --git a/fixtures/test-fig/community/1510053249065427020-workos-radix-icons.fig b/fixtures/test-fig/community/1510053249065427020-workos-radix-icons.fig index 093ac79f0a..eeb1588006 100644 --- a/fixtures/test-fig/community/1510053249065427020-workos-radix-icons.fig +++ b/fixtures/test-fig/community/1510053249065427020-workos-radix-icons.fig @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf1e323b58742d203a2d022e259f0967bb23f7575631898be753798c495d2797 -size 787059 +oid sha256:aaf68cec29d1e6b780cab3a8f1e86a7a6536147ff12ce91c0e2dd0bf9bb17869 +size 786795 diff --git a/fixtures/test-fig/community/784448220678228461-figma-auto-layout-playground.fig b/fixtures/test-fig/community/784448220678228461-figma-auto-layout-playground.fig index 75260237f8..e9dcc0bbf5 100644 --- a/fixtures/test-fig/community/784448220678228461-figma-auto-layout-playground.fig +++ b/fixtures/test-fig/community/784448220678228461-figma-auto-layout-playground.fig @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2977a88413d49de50c432250e28787b6a7dc876f2d4206d32e7bbbab68663833 -size 22915832 +oid sha256:064ed60e8ab8aeae5a56b15a015df52f21653c0660e7d6df5a7e0b2e05725bc7 +size 22917482 diff --git a/fixtures/test-fig/community/README.md b/fixtures/test-fig/community/README.md index 0de5041f0c..061d4b4beb 100644 --- a/fixtures/test-fig/community/README.md +++ b/fixtures/test-fig/community/README.md @@ -1,8 +1,10 @@ -# `.fig` test fixtures (mostly from figma community) +# `.fig` test fixtures (mostly from Figma Community) -| Name | File ID | Link | License | -| ---------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------- | -| Simple Design System | [`1380235722331273046`](./1380235722331273046-figma-simple-design-system.fig) | [View](https://www.figma.com/community/file/1380235722331273046) | CC BY 4.0 | -| Auto Layout Playground | [`784448220678228461`](./784448220678228461-figma-auto-layout%20playground.fig) | [View](https://www.figma.com/community/file/784448220678228461) | CC BY 4.0 | -| Radix Icons | [`1510053249065427020`](./1510053249065427020-workos-radix-icons.fig) | [View](https://www.figma.com/community/file/1510053249065427020) | CC BY 4.0 | -| Apple iOS 26 | [`1527721578857867021`](./1527721578857867021-apple-ios-26.fig) | [View](https://www.figma.com/community/file/1527721578857867021) | License Agreement for Apple Design Resources | +Binary `.fig` exports from Figma. For REST API format (document.json + images), see [`test-figma/community`](../test-figma/community/). + +| Name | File ID | REST archive (same file) | Notes (Modifications made) | Link | License | +| ---------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------- | +| Simple Design System | [`1380235722331273046`](./1380235722331273046-figma-simple-design-system.fig) | — | — | [View](https://www.figma.com/community/file/1380235722331273046) | CC BY 4.0 | +| Auto Layout Playground | [`784448220678228461`](./784448220678228461-figma-auto-layout-playground.fig) | [test-figma/community](../test-figma/community/784448220678228461-figma-auto-layout-playground.zip) | Export settings added to each top-level frame (not in original) | [View](https://www.figma.com/community/file/784448220678228461) | CC BY 4.0 | +| Radix Icons | [`1510053249065427020`](./1510053249065427020-workos-radix-icons.fig) | [test-figma/community](../test-figma/community/1510053249065427020-workos-radix-icons.zip) | Overrides for each icon component (icons set) | [View](https://www.figma.com/community/file/1510053249065427020) | CC BY 4.0 | +| Apple iOS 26 | [`1527721578857867021`](./1527721578857867021-apple-ios-26.fig) | — | — | [View](https://www.figma.com/community/file/1527721578857867021) | License Agreement for Apple Design Resources | diff --git a/fixtures/test-figma/community/1510053249065427020-workos-radix-icons.zip b/fixtures/test-figma/community/1510053249065427020-workos-radix-icons.zip new file mode 100644 index 0000000000..3460526bc5 Binary files /dev/null and b/fixtures/test-figma/community/1510053249065427020-workos-radix-icons.zip differ diff --git a/fixtures/test-figma/community/784448220678228461-figma-auto-layout-playground.zip b/fixtures/test-figma/community/784448220678228461-figma-auto-layout-playground.zip new file mode 100644 index 0000000000..04b0feee73 Binary files /dev/null and b/fixtures/test-figma/community/784448220678228461-figma-auto-layout-playground.zip differ diff --git a/fixtures/test-figma/community/README.md b/fixtures/test-figma/community/README.md new file mode 100644 index 0000000000..32d8820681 --- /dev/null +++ b/fixtures/test-figma/community/README.md @@ -0,0 +1,27 @@ +# Figma REST API archive fixtures (Figma Community) + +ZIP archives of Figma REST API responses: `document.json` (with `geometry=paths`) and `images/` (downloaded image fills). Created with [`.tools/figma_archive.py`](../../.tools/figma_archive.py). + +These match the `.fig` fixtures in [`test-fig/community`](../test-fig/community/) — same source files, different formats. Use the REST archive for tests that need document JSON + images without Figma API calls. + +| Name | File ID | REST archive | .fig (binary) | Link | +| ---------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| Auto Layout Playground | `784448220678228461` | [`784448220678228461-figma-auto-layout-playground.zip`](./784448220678228461-figma-auto-layout-playground.zip) | [test-fig/community](../../test-fig/community/784448220678228461-figma-auto-layout-playground.fig) | [View](https://www.figma.com/community/file/784448220678228461) | +| Radix Icons | `1510053249065427020` | [`1510053249065427020-workos-radix-icons.zip`](./1510053249065427020-workos-radix-icons.zip) | [test-fig/community](../../test-fig/community/1510053249065427020-workos-radix-icons.fig) | [View](https://www.figma.com/community/file/1510053249065427020) | + +## Layout (per archive) + +``` +.zip +└── / + ├── document.json # GET /v1/files/:key?geometry=paths + └── images/ + └── . # Image fills from GET /v1/files/:key/images +``` + +## Regenerating + +```sh +python .tools/figma_archive.py --filekey 784448220678228461 --archive-dir /tmp/arch +# Then zip the output and replace the fixture. +``` diff --git a/package.json b/package.json index 5020b17800..def55690a1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "@types/node": "^22", "oxfmt": "^0.28.0", "tsup": "^8.5.0", - "turbo": "^2.8.3", + "tsx": "^4", + "turbo": "^2.8.9", "typescript": "^5", "vitest": "^4" }, @@ -34,7 +35,8 @@ "@types/react": "19.1.3", "@types/react-dom": "19.1.3", "eslint": "^9", - "typescript": "^5", + "tsx": "4.21.0", + "typescript": "5.9.3", "axios": "1.13.4", "prosemirror-model": "1.23.0", "prosemirror-view": "1.36.0", diff --git a/packages/grida-canvas-io-figma/__tests__/iofigma.kiwi.fig.test.ts b/packages/grida-canvas-io-figma/__tests__/iofigma.kiwi.fig.test.ts index 2c7143387b..790ed656a5 100644 --- a/packages/grida-canvas-io-figma/__tests__/iofigma.kiwi.fig.test.ts +++ b/packages/grida-canvas-io-figma/__tests__/iofigma.kiwi.fig.test.ts @@ -175,9 +175,12 @@ describe("FigImporter", () => { it("should merge all roots into single packed document", () => { const data = readFileSync(testFixture); const figFile = FigImporter.parseFile(data); - const packedDoc = FigImporter.convertPageToScene(figFile.pages[0], { - gradient_id_generator: () => "test-id", - }); + const { document: packedDoc } = FigImporter.convertPageToScene( + figFile.pages[0], + { + gradient_id_generator: () => "test-id", + } + ); expect(packedDoc.nodes).toBeDefined(); expect(packedDoc.links).toBeDefined(); @@ -185,7 +188,7 @@ describe("FigImporter", () => { expect(Array.isArray(packedDoc.scene.children_refs)).toBe(true); // All root IDs must exist in nodes - packedDoc.scene.children_refs.forEach((rootId) => { + packedDoc.scene.children_refs.forEach((rootId: string) => { expect(packedDoc.nodes[rootId]).toBeDefined(); }); }); @@ -263,14 +266,14 @@ describe("FigImporter", () => { ], }; - const packedDoc = FigImporter.convertPageToScene(mockPage, { + const { document: packedDoc } = FigImporter.convertPageToScene(mockPage, { gradient_id_generator: () => "test-id", // Intentionally omit node_id_generator to exercise the internal shared generator. }); const uniqueRoots = new Set(packedDoc.scene.children_refs); expect(uniqueRoots.size).toBe(2); - packedDoc.scene.children_refs.forEach((rootId) => { + packedDoc.scene.children_refs.forEach((rootId: string) => { expect(packedDoc.nodes[rootId]).toBeDefined(); }); }); diff --git a/packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts b/packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts index ba2e64e625..4d2f424e75 100644 --- a/packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts +++ b/packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts @@ -6,6 +6,37 @@ import type grida from "@grida/schema"; const FIXTURES_BASE = __dirname + "/../../../fixtures/test-figma/rest-api/L0"; const VECTOR_FRAME_FIXTURE = FIXTURES_BASE + "/vector-frame.response.json"; +/** Minimal valid REST volatile vectorNetwork (triangle: 3 vertices, 3 segments, 1 region). */ +const minimalVectorNetwork: iofigma.__ir.VectorNetwork_restapi_volatile20260217 = + { + vertices: [ + { position: { x: 0, y: 0 } }, + { position: { x: 10, y: 0 } }, + { position: { x: 5, y: 10 } }, + ], + segments: [ + { + start: 0, + startTangent: { x: 0, y: 0 }, + end: 1, + endTangent: { x: 0, y: 0 }, + }, + { + start: 1, + startTangent: { x: 0, y: 0 }, + end: 2, + endTangent: { x: 0, y: 0 }, + }, + { + start: 2, + startTangent: { x: 0, y: 0 }, + end: 0, + endTangent: { x: 0, y: 0 }, + }, + ], + regions: [{ loops: [[0, 1, 2]], windingRule: "NONZERO" }], + }; + describe("iofigma.restful.factory.document", () => { describe("HasGeometryTrait conversion (geometry=paths)", () => { it("should convert VECTOR node with fillGeometry and strokeGeometry to GroupNode with child VectorNodes", () => { @@ -32,7 +63,7 @@ describe("iofigma.restful.factory.document", () => { const context: iofigma.restful.factory.FactoryContext = { gradient_id_generator: () => `gradient_${Math.random()}`, }; - const gridaDocument = iofigma.restful.factory.document( + const { document: gridaDocument } = iofigma.restful.factory.document( frameNode, {}, context @@ -44,7 +75,7 @@ describe("iofigma.restful.factory.document", () => { // Find the frame node (frames are converted to container nodes) const frameGridaNode = Object.values(gridaDocument.nodes).find( - (n): n is grida.program.nodes.ContainerNode => + (n: grida.program.nodes.Node): n is grida.program.nodes.ContainerNode => n.type === "container" && n.name === "vector-frame" ); expect(frameGridaNode).toBeDefined(); @@ -66,31 +97,41 @@ describe("iofigma.restful.factory.document", () => { // Verify child nodes are VectorNodes const childNodes = groupChildren! - .map((id) => gridaDocument.nodes[id]) + .map((id: string) => gridaDocument.nodes[id]) .filter( - (n): n is grida.program.nodes.VectorNode => n.type === "vector" + ( + n: grida.program.nodes.Node | undefined + ): n is grida.program.nodes.VectorNode => n?.type === "vector" ); expect(childNodes.length).toBeGreaterThan(0); // Verify we have both fill and stroke children - const fillChildren = childNodes.filter((n) => n.name.includes("Fill")); - const strokeChildren = childNodes.filter((n) => - n.name.includes("Stroke") + const fillChildren = childNodes.filter( + (n: grida.program.nodes.VectorNode) => n.name.includes("Fill") + ); + const strokeChildren = childNodes.filter( + (n: grida.program.nodes.VectorNode) => n.name.includes("Stroke") ); expect(fillChildren.length).toBeGreaterThan(0); expect(strokeChildren.length).toBeGreaterThan(0); // Verify fill children have fills - fillChildren.forEach((child) => { + fillChildren.forEach((child: grida.program.nodes.VectorNode) => { expect(child.type).toBe("vector"); expect(child.fill || child.fill_paints?.length).toBeTruthy(); }); - // Verify stroke children have strokes - strokeChildren.forEach((child) => { + // Verify stroke children have paints (stroke geometry is rendered as fill, so fill_paints or stroke_paints) + strokeChildren.forEach((child: grida.program.nodes.VectorNode) => { expect(child.type).toBe("vector"); - expect(child.stroke || child.stroke_paints?.length).toBeTruthy(); - expect(child.stroke_width).toBeGreaterThan(0); + expect( + child.stroke || + (child.stroke_paints?.length ?? 0) > 0 || + (child.fill_paints?.length ?? 0) > 0 + ).toBeTruthy(); + if ((child.stroke_paints?.length ?? 0) > 0) { + expect(child.stroke_width).toBeGreaterThan(0); + } }); }); @@ -111,7 +152,7 @@ describe("iofigma.restful.factory.document", () => { const context: iofigma.restful.factory.FactoryContext = { gradient_id_generator: () => `gradient_${Math.random()}`, }; - const gridaDocument = iofigma.restful.factory.document( + const { document: gridaDocument } = iofigma.restful.factory.document( frameNode, {}, context @@ -119,7 +160,7 @@ describe("iofigma.restful.factory.document", () => { // Find the vector group node const vectorGroupNode = Object.values(gridaDocument.nodes).find( - (n): n is grida.program.nodes.GroupNode => + (n: grida.program.nodes.Node): n is grida.program.nodes.GroupNode => n.type === "group" && n.name === "Vector 1" ); @@ -142,9 +183,11 @@ describe("iofigma.restful.factory.document", () => { expect(childIds!.length).toBeGreaterThan(0); const childNodes = childIds! - .map((id) => gridaDocument.nodes[id]) + .map((id: string) => gridaDocument.nodes[id]) .filter( - (n): n is grida.program.nodes.VectorNode => n.type === "vector" + ( + n: grida.program.nodes.Node | undefined + ): n is grida.program.nodes.VectorNode => n?.type === "vector" ); expect(childNodes.length).toBeGreaterThan(0); @@ -154,7 +197,7 @@ describe("iofigma.restful.factory.document", () => { // at their bbox origin to maintain correct spatial relationships // Note: In test environment with mocked svg-pathdata, vector networks may be empty, // but we can still verify the positioning logic is correct - childNodes.forEach((child) => { + childNodes.forEach((child: grida.program.nodes.VectorNode) => { expect(child.type).toBe("vector"); // Child nodes should be positioned at their bbox origin relative to parent // (not at 0,0, which would cause misalignment) @@ -168,5 +211,305 @@ describe("iofigma.restful.factory.document", () => { // (In mocked environment, bbox may be 0,0, but the logic is correct) }); }); + + it("should convert VECTOR node with vectorNetwork (volatile API) to single VectorNode with vector_network", () => { + const vectorNodeWithNetwork: iofigma.__ir.VectorNodeRestInput = { + id: "vector-with-vn", + name: "Vector with vectorNetwork", + type: "VECTOR", + blendMode: "PASS_THROUGH", + absoluteBoundingBox: { x: 0, y: 0, width: 10, height: 10 }, + absoluteRenderBounds: { x: 0, y: 0, width: 10, height: 10 }, + constraints: { vertical: "TOP", horizontal: "LEFT" }, + scrollBehavior: "FIXED", + size: { x: 10, y: 10 }, + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + fills: [ + { + type: "SOLID", + color: { r: 1, g: 0, b: 0, a: 1 }, + visible: true, + blendMode: "NORMAL", + }, + ], + strokes: [], + strokeWeight: 0, + strokeAlign: "INSIDE", + effects: [], + cornerRadius: 0, + vectorNetwork: minimalVectorNetwork, + }; + + const context: iofigma.restful.factory.FactoryContext = { + gradient_id_generator: () => `gradient_${Math.random()}`, + }; + const { document: gridaDocument } = iofigma.restful.factory.document( + vectorNodeWithNetwork, + {}, + context + ); + + const vectorGridaNode = Object.values(gridaDocument.nodes).find( + (n): n is grida.program.nodes.VectorNode => + n.type === "vector" && n.name === "Vector with vectorNetwork" + ); + expect(vectorGridaNode).toBeDefined(); + expect(vectorGridaNode!.type).toBe("vector"); + expect(vectorGridaNode!.vector_network).toBeDefined(); + expect(vectorGridaNode!.vector_network!.vertices).toHaveLength(3); + expect(vectorGridaNode!.vector_network!.segments).toHaveLength(3); + + // Should not be a group with children + const groupNode = Object.values(gridaDocument.nodes).find( + (n): n is grida.program.nodes.GroupNode => + n.type === "group" && n.name === "Vector with vectorNetwork" + ); + expect(groupNode).toBeUndefined(); + + const childIds = gridaDocument.links[vectorGridaNode!.id]; + expect(childIds).toBeUndefined(); + }); + + it("should fall back to GroupNode when vectorNetwork is present but invalid", () => { + const invalidVectorNetwork = { + vertices: [{ position: { x: 0, y: 0 } }], + segments: [ + { start: 0, end: 99, startTangent: { x: 0, y: 0 }, endTangent: { x: 0, y: 0 } }, + ], + regions: [{ loops: [[0]], windingRule: "NONZERO" as const }], + }; + + const vectorNodeInvalidVn: iofigma.__ir.VectorNodeRestInput = { + id: "vector-invalid-vn", + name: "Vector invalid vectorNetwork", + type: "VECTOR", + blendMode: "PASS_THROUGH", + absoluteBoundingBox: { x: 0, y: 0, width: 10, height: 10 }, + absoluteRenderBounds: { x: 0, y: 0, width: 10, height: 10 }, + constraints: { vertical: "TOP", horizontal: "LEFT" }, + scrollBehavior: "FIXED", + size: { x: 10, y: 10 }, + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + fills: [ + { + type: "SOLID", + color: { r: 0, g: 1, b: 0, a: 1 }, + visible: true, + blendMode: "NORMAL", + }, + ], + strokes: [], + strokeWeight: 0, + strokeAlign: "INSIDE", + effects: [], + cornerRadius: 0, + fillGeometry: [{ path: "M0 0 L10 0 L5 10 Z", windingRule: "NONZERO" as const }], + strokeGeometry: [], + vectorNetwork: invalidVectorNetwork as iofigma.__ir.VectorNetwork_restapi_volatile20260217, + }; + + const context: iofigma.restful.factory.FactoryContext = { + gradient_id_generator: () => `gradient_${Math.random()}`, + }; + const { document: gridaDocument } = iofigma.restful.factory.document( + vectorNodeInvalidVn, + {}, + context + ); + + const groupNode = Object.values(gridaDocument.nodes).find( + (n): n is grida.program.nodes.GroupNode => + n.type === "group" && n.name === "Vector invalid vectorNetwork" + ); + expect(groupNode).toBeDefined(); + expect(groupNode!.type).toBe("group"); + }); + + it("should convert fillGeometry/strokeGeometry to Path nodes when prefer_path_for_geometry is true", () => { + const vectorNodeWithGeometry: figrest.VectorNode & figrest.HasGeometryTrait = + { + id: "vec-path-pref", + name: "Vector path prefer", + type: "VECTOR", + blendMode: "PASS_THROUGH", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 100 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 100 }, + constraints: { vertical: "TOP", horizontal: "LEFT" }, + scrollBehavior: "FIXED", + fills: [ + { + type: "SOLID", + color: { r: 1, g: 0, b: 0, a: 1 }, + visible: true, + blendMode: "NORMAL", + }, + ], + strokes: [ + { + type: "SOLID", + color: { r: 0, g: 0, b: 1, a: 1 }, + visible: true, + blendMode: "NORMAL", + }, + ], + strokeWeight: 2, + strokeAlign: "INSIDE", + effects: [], + cornerRadius: 0, + fillGeometry: [ + { path: "M0 0 L100 0 L100 100 L0 100 Z", windingRule: "NONZERO" }, + ], + strokeGeometry: [ + { path: "M10 10 L90 10 L90 90 L10 90 Z", windingRule: "NONZERO" }, + ], + }; + + const context: iofigma.restful.factory.FactoryContext = { + gradient_id_generator: () => `gradient_${Math.random()}`, + prefer_path_for_geometry: true, + }; + + const { document: gridaDocument } = iofigma.restful.factory.document( + vectorNodeWithGeometry, + {}, + context + ); + + const groupNode = Object.values(gridaDocument.nodes).find( + (n): n is grida.program.nodes.GroupNode => + n.type === "group" && n.name === "Vector path prefer" + ); + expect(groupNode).toBeDefined(); + expect(groupNode!.type).toBe("group"); + + const groupChildren = gridaDocument.links[groupNode!.id]; + expect(groupChildren).toBeDefined(); + expect(groupChildren!.length).toBeGreaterThan(0); + + const childNodes = groupChildren! + .map((id: string) => gridaDocument.nodes[id]) + .filter(Boolean) as grida.program.nodes.Node[]; + + const pathChildren = childNodes.filter( + (n): n is grida.program.nodes.PathNode => n.type === "path" + ); + expect(pathChildren.length).toBeGreaterThan(0); + expect(pathChildren.length).toBe(childNodes.length); + + pathChildren.forEach((child) => { + expect(child.type).toBe("path"); + expect("data" in child && typeof child.data === "string").toBe(true); + expect(child.data.length).toBeGreaterThan(0); + expect("vector_network" in child).toBe(false); + }); + }); + }); + + describe("resolve_image_src and imageRefsUsed", () => { + it("should use resolve_image_src when provided and collect imageRefsUsed", () => { + const rectWithImage: figrest.RectangleNode = { + id: "rect-img", + name: "Image Rect", + type: "RECTANGLE", + blendMode: "PASS_THROUGH", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 100 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 100 }, + constraints: { vertical: "TOP", horizontal: "LEFT" }, + scrollBehavior: "FIXED", + fills: [ + { + type: "IMAGE", + imageRef: "figma-image-ref-abc123", + scaleMode: "FILL", + } as figrest.ImagePaint, + ], + strokes: [], + strokeWeight: 0, + strokeAlign: "INSIDE", + effects: [], + cornerRadius: 0, + }; + + const images: Record = { + "figma-image-ref-abc123": "https://example.com/image.png", + }; + + const context: iofigma.restful.factory.FactoryContext = { + gradient_id_generator: () => `gradient_${Math.random()}`, + resolve_image_src: (ref) => + ref in images ? `res://images/${ref}` : null, + }; + + const { document: gridaDoc, imageRefsUsed } = + iofigma.restful.factory.document(rectWithImage, images, context); + + expect(imageRefsUsed).toContain("figma-image-ref-abc123"); + expect(imageRefsUsed).toHaveLength(1); + + const rectNode = Object.values(gridaDoc.nodes).find( + (n): n is grida.program.nodes.RectangleNode => n.type === "rectangle" + ); + expect(rectNode).toBeDefined(); + const imagePaint = rectNode!.fill_paints?.find( + (p) => p && typeof p === "object" && "type" in p && p.type === "image" + ); + expect(imagePaint).toBeDefined(); + expect((imagePaint as { src?: string })!.src).toBe( + "res://images/figma-image-ref-abc123" + ); + }); + + it("should use placeholder when resolve_image_src returns null and not add to imageRefsUsed", () => { + const rectWithImage: figrest.RectangleNode = { + id: "rect-img", + name: "Image Rect", + type: "RECTANGLE", + blendMode: "PASS_THROUGH", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 100 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 100 }, + constraints: { vertical: "TOP", horizontal: "LEFT" }, + scrollBehavior: "FIXED", + fills: [ + { + type: "IMAGE", + imageRef: "missing-ref", + scaleMode: "FILL", + } as figrest.ImagePaint, + ], + strokes: [], + strokeWeight: 0, + strokeAlign: "INSIDE", + effects: [], + cornerRadius: 0, + }; + + const context: iofigma.restful.factory.FactoryContext = { + gradient_id_generator: () => `gradient_${Math.random()}`, + resolve_image_src: () => null, + }; + + const { document: gridaDoc, imageRefsUsed } = + iofigma.restful.factory.document(rectWithImage, {}, context); + + expect(imageRefsUsed).toHaveLength(0); + + const rectNode = Object.values(gridaDoc.nodes).find( + (n): n is grida.program.nodes.RectangleNode => n.type === "rectangle" + ); + expect(rectNode).toBeDefined(); + const imagePaint = rectNode!.fill_paints?.find( + (p) => p && typeof p === "object" && "type" in p && p.type === "image" + ); + expect(imagePaint).toBeDefined(); + expect((imagePaint as { src?: string })!.src).toBe( + "system://images/checker-16-strip-L98L92.png" + ); + }); }); }); diff --git a/packages/grida-canvas-io-figma/lib.ts b/packages/grida-canvas-io-figma/lib.ts index 5bcf45db68..44b31bb709 100644 --- a/packages/grida-canvas-io-figma/lib.ts +++ b/packages/grida-canvas-io-figma/lib.ts @@ -6,6 +6,7 @@ import type * as figkiwi from "./fig-kiwi/schema"; import cmath from "@grida/cmath"; import kolor from "@grida/color"; import { + extractImages as extractImagesFromFigKiwi, getBlobBytes, parseVectorNetworkBlob, readFigFile, @@ -46,6 +47,48 @@ export namespace iofigma { targetAspectRatio?: figrest.Vector; }; + /** + * Vector network shape returned by the Figma REST API as an **undocumented, volatile** field + * on VECTOR nodes when the file is requested with `geometry=paths`. + * + * Alongside the well-known `fillGeometry` and `strokeGeometry` (SVG path strings), the API + * may also include a `vectorNetwork` object with vertices, segments, and regions. This type + * describes that object. It is not part of the official `@figma/rest-api-spec` and may change + * or be removed at any time without notice. + * + * Use this type only when volatile APIs are enabled (see `disable_volatile_apis`); normalize + * it to `__ir.VectorNetwork` before using in the import pipeline. + * + * @remarks + * - First observed present in REST responses on **2026-02-17**. The type name is dated so that + * future changes to the API can be tracked with a new type if the shape diverges. + * - As an undocumented volatile API, the response may have minor inconsistencies (e.g. + * `windingRule` may be `"EVENODD"` | `"NONZERO"` | `"nonzero"` | `"odd"`). Normalizers should handle all casings. + * + * @deprecated Volatile, versioned shape; the type name includes the snapshot date (2026-02-17) + * so that future API changes can introduce a new type (e.g. 20260301) without breaking callers. + * Search for "volatile" to locate related code (e.g. disable_volatile_apis) for removal. + */ + export type VectorNetwork_restapi_volatile20260217 = { + vertices: Array<{ + position: { x: number; y: number }; + meta?: 0 | unknown; + }>; + segments: Array<{ + start: number; + startTangent: { x: number; y: number }; + end: number; + endTangent: { x: number; y: number }; + meta?: 0 | unknown; + }>; + regions: Array<{ + loops: number[][]; + /** Volatile API may return inconsistent casing: EVENODD | NONZERO | nonzero | odd */ + windingRule: "EVENODD" | "NONZERO" | "nonzero" | "odd"; + meta?: 0 | unknown; + }>; + }; + /** * Vector network structure (vertices, segments, regions) * Matches the output of parseVectorNetworkBlob from blob-parser @@ -64,6 +107,14 @@ export namespace iofigma { }>; }; + /** + * REST API VECTOR when using geometry=paths. Volatile API may also return vectorNetwork. + * Use this type when passing REST response nodes that may include the undocumented vectorNetwork field. + */ + export type VectorNodeRestInput = figrest.VectorNode & { + vectorNetwork?: VectorNetwork_restapi_volatile20260217; + }; + /** * - rest-api-spec - Not supported * - kiwi-spec - Supported @@ -208,9 +259,147 @@ export namespace iofigma { ...blendModeMap, PASS_THROUGH: "pass-through", }; + + /** + * Normalizes REST API volatile vector network shape to __ir.VectorNetwork. + * Returns null if the payload is missing, not an array, or has invalid indices. + */ + export function normalizeRestVectorNetworkToIR( + rest: __ir.VectorNetwork_restapi_volatile20260217 + ): __ir.VectorNetwork | null { + if ( + !Array.isArray(rest.vertices) || + !Array.isArray(rest.segments) || + !Array.isArray(rest.regions) + ) { + return null; + } + const vertexCount = rest.vertices.length; + const segmentCount = rest.segments.length; + + for (const seg of rest.segments) { + if ( + typeof seg.start !== "number" || + typeof seg.end !== "number" || + seg.start < 0 || + seg.start >= vertexCount || + seg.end < 0 || + seg.end >= vertexCount || + !seg.startTangent || + !seg.endTangent + ) { + return null; + } + } + + for (const region of rest.regions) { + if (!Array.isArray(region.loops)) return null; + for (const loop of region.loops) { + if (!Array.isArray(loop)) return null; + for (const segIdx of loop) { + if ( + typeof segIdx !== "number" || + segIdx < 0 || + segIdx >= segmentCount + ) { + return null; + } + } + } + } + + const vertices: __ir.VectorNetwork["vertices"] = rest.vertices.map( + (v) => ({ + x: v.position?.x ?? 0, + y: v.position?.y ?? 0, + styleID: 0, + }) + ); + + const segments: __ir.VectorNetwork["segments"] = rest.segments.map( + (seg) => ({ + styleID: 0, + start: { + vertex: seg.start, + dx: seg.startTangent.x, + dy: seg.startTangent.y, + }, + end: { + vertex: seg.end, + dx: seg.endTangent.x, + dy: seg.endTangent.y, + }, + }) + ); + + const regions: __ir.VectorNetwork["regions"] = rest.regions.map( + (region) => { + const wr = region.windingRule?.toUpperCase?.(); + const windingRule: "NONZERO" | "ODD" = + wr === "EVENODD" || wr === "ODD" ? "ODD" : "NONZERO"; + const loops = region.loops.map((loop) => ({ segments: loop })); + return { styleID: 0, windingRule, loops }; + } + ); + + return { vertices, segments, regions }; + } } export namespace factory { + /** + * Result of converting Figma REST document to Grida format. + * + * - **document**: The packed scene document for insert. + * - **imageRefsUsed**: Figma image refs that appear in the document and need registration. + * Caller should only download/register refs in this set to avoid bloat. + */ + export interface FigmaImportResult { + document: grida.program.document.IPackedSceneDocument; + imageRefsUsed: string[]; + } + + /** + * Context for the REST document factory. + * + * @property node_id_generator - Optional ID generator for Grida node IDs. + * @property gradient_id_generator - ID generator for gradient definitions. + * @property resolve_image_src - Resolves a Figma image ref to a runtime src (e.g. res://images/<ref>). + * Caller owns resource availability; io-figma does not fetch or validate. + * @param imageRef - Figma image reference (hash string from API or Kiwi). + * @returns Runtime src string, or null to use placeholder. + */ + export type FactoryContext = { + node_id_generator?: () => string; + gradient_id_generator: () => string; + /** + * Resolves a Figma image ref to a runtime src (e.g. res://images/<ref>). + * Caller owns resource availability; io-figma does not fetch or validate. + * + * @param imageRef - Figma image reference (hash string from API or Kiwi). + * @returns Runtime src string, or null to use placeholder. + */ + resolve_image_src?: (imageRef: string) => string | null; + /** + * When true, use Figma node IDs as Grida node IDs instead of generating new ones. + * Required for export-by-nodeId from .fig files. + */ + preserve_figma_ids?: boolean; + /** + * When true, disable volatile/undocumented APIs (e.g. REST vectorNetwork on VECTOR nodes). + * Default false: volatile APIs are enabled; vectorNetwork is used when present. + * Set to true to always use fillGeometry/strokeGeometry (GroupNode + children) for VECTORs. + */ + disable_volatile_apis?: boolean; + /** + * When true, convert fillGeometry/strokeGeometry to Path nodes (raw SVG path data) + * instead of Vector nodes (vector network). Use for render-only pipelines (e.g. refig) + * to avoid vn.fromSVGPathData conversions. Path nodes are not editable in the editor. + * @default false + */ + prefer_path_for_geometry?: boolean; + }; + function toGradientPaint(paint: figrest.GradientPaint) { const type_map = { GRADIENT_LINEAR: "linear_gradient", @@ -263,54 +452,64 @@ export namespace iofigma { }; } - function paint(paint: figrest.Paint): cg.Paint | undefined { - switch (paint.type) { + function convertPaint( + p: figrest.Paint, + ctx: FactoryContext, + imageRefsUsed: Set + ): cg.Paint | undefined { + switch (p.type) { case "SOLID": { - return toSolidPaint(paint); + return toSolidPaint(p); } case "GRADIENT_LINEAR": case "GRADIENT_RADIAL": case "GRADIENT_ANGULAR": case "GRADIENT_DIAMOND": { - return toGradientPaint(paint); + return toGradientPaint(p); } - case "IMAGE": - // Return image paint with empty src - renderer will use placeholder + case "IMAGE": { + const imageRef = (p as figrest.ImagePaint).imageRef ?? ""; + const src = + ctx.resolve_image_src?.(imageRef) ?? + _GRIDA_SYSTEM_EMBEDDED_CHECKER; + if (src !== _GRIDA_SYSTEM_EMBEDDED_CHECKER && imageRef) { + imageRefsUsed.add(imageRef); + } return { type: "image", - src: _GRIDA_SYSTEM_EMBEDDED_CHECKER, - fit: paint.scaleMode - ? paint.scaleMode === "FILL" + src, + fit: p.scaleMode + ? p.scaleMode === "FILL" ? "cover" - : paint.scaleMode === "FIT" + : p.scaleMode === "FIT" ? "contain" - : paint.scaleMode === "TILE" + : p.scaleMode === "TILE" ? "tile" : "fill" : "cover", - transform: paint.imageTransform + transform: p.imageTransform ? [ [ - paint.imageTransform[0][0], - paint.imageTransform[0][1], - paint.imageTransform[0][2], + p.imageTransform[0][0], + p.imageTransform[0][1], + p.imageTransform[0][2], ], [ - paint.imageTransform[1][0], - paint.imageTransform[1][1], - paint.imageTransform[1][2], + p.imageTransform[1][0], + p.imageTransform[1][1], + p.imageTransform[1][2], ], ] : cmath.transform.identity, - filters: paint.filters + filters: p.filters ? { - exposure: paint.filters.exposure ?? 0, - contrast: paint.filters.contrast ?? 0, - saturation: paint.filters.saturation ?? 0, - temperature: paint.filters.temperature ?? 0, - tint: paint.filters.tint ?? 0, - highlights: paint.filters.highlights ?? 0, - shadows: paint.filters.shadows ?? 0, + exposure: p.filters.exposure ?? 0, + contrast: p.filters.contrast ?? 0, + saturation: p.filters.saturation ?? 0, + temperature: p.filters.temperature ?? 0, + tint: p.filters.tint ?? 0, + highlights: p.filters.highlights ?? 0, + shadows: p.filters.shadows ?? 0, } : { exposure: 0, @@ -321,10 +520,13 @@ export namespace iofigma { highlights: 0, shadows: 0, }, - blend_mode: map.blendModeMap[paint.blendMode], - opacity: paint.opacity ?? 1, - active: paint.visible ?? true, + blend_mode: map.blendModeMap[p.blendMode], + opacity: p.opacity ?? 1, + active: p.visible ?? true, } satisfies cg.ImagePaint; + } + default: + return undefined; } } @@ -419,9 +621,13 @@ export namespace iofigma { /** * Fill properties - IFill */ - function fills_trait(fills: figrest.Paint[]) { + function fills_trait( + fills: figrest.Paint[], + context: FactoryContext, + imageRefsUsed: Set + ) { const fills_paints = fills - .map(paint) + .map((p) => convertPaint(p, context, imageRefsUsed)) .filter((p): p is cg.Paint => p !== undefined); return { fill_paints: fills_paints.length > 0 ? fills_paints : undefined, @@ -431,17 +637,21 @@ export namespace iofigma { /** * Stroke properties - IStroke */ - function stroke_trait(node: { - strokes?: figrest.Paint[]; - strokeWeight?: number; - strokeCap?: figrest.LineNode["strokeCap"]; - strokeJoin?: figrest.LineNode["strokeJoin"]; - strokeDashes?: number[]; - strokeAlign?: "INSIDE" | "OUTSIDE" | "CENTER"; - strokeMiterAngle?: number; - }) { + function stroke_trait( + node: { + strokes?: figrest.Paint[]; + strokeWeight?: number; + strokeCap?: figrest.LineNode["strokeCap"]; + strokeJoin?: figrest.LineNode["strokeJoin"]; + strokeDashes?: number[]; + strokeAlign?: "INSIDE" | "OUTSIDE" | "CENTER"; + strokeMiterAngle?: number; + }, + context: FactoryContext, + imageRefsUsed: Set + ) { const strokes_paints = (node.strokes ?? []) - .map(paint) + .map((p) => convertPaint(p, context, imageRefsUsed)) .filter((p): p is cg.Paint => p !== undefined); return { stroke_paints: strokes_paints.length > 0 ? strokes_paints : undefined, @@ -463,13 +673,17 @@ export namespace iofigma { /** * Text stroke properties - ITextStroke (simpler than IStroke) */ - function text_stroke_trait(node: { - strokes?: figrest.Paint[]; - strokeWeight?: number; - strokeAlign?: "INSIDE" | "OUTSIDE" | "CENTER"; - }) { + function text_stroke_trait( + node: { + strokes?: figrest.Paint[]; + strokeWeight?: number; + strokeAlign?: "INSIDE" | "OUTSIDE" | "CENTER"; + }, + context: FactoryContext, + imageRefsUsed: Set + ) { const strokes_paints = (node.strokes ?? []) - .map(paint) + .map((p) => convertPaint(p, context, imageRefsUsed)) .filter((p): p is cg.Paint => p !== undefined); return { stroke_paints: strokes_paints.length > 0 ? strokes_paints : undefined, @@ -659,13 +873,9 @@ export namespace iofigma { | figrest.FrameNode | figrest.GroupNode; - export type FactoryContext = { - node_id_generator?: () => string; - gradient_id_generator: () => string; - }; - type InputNode = | (figrest.SubcanvasNode & Partial<__ir.HasLayoutTraitIR>) + | __ir.VectorNodeRestInput | __ir.VectorNodeWithVectorNetworkDataPresent | __ir.StarNodeWithPointsDataPresent | __ir.RegularPolygonNodeWithPointsDataPresent; @@ -674,9 +884,10 @@ export namespace iofigma { node: InputNode, images: { [key: string]: string }, context: FactoryContext - ): grida.program.document.IPackedSceneDocument { + ): FigmaImportResult { const nodes: Record = {}; const graph: Record = {}; + const imageRefsUsed = new Set(); // Map from Figma ID (ephemeral) to Grida ID (final) const figma_id_to_grida_id = new Map(); @@ -691,7 +902,7 @@ export namespace iofigma { const getOrCreateGridaId = (figmaId: string): string => { const existing = figma_id_to_grida_id.get(figmaId); if (existing) return existing; - const gridaId = generateId(); + const gridaId = context.preserve_figma_ids ? figmaId : generateId(); figma_id_to_grida_id.set(figmaId, gridaId); return gridaId; }; @@ -706,11 +917,34 @@ export namespace iofigma { return "fillGeometry" in node || "strokeGeometry" in node; } + /** + * Extracts width/height from a node with layout traits (Figma REST API). + * Uses absoluteBoundingBox or size; avoids parsing path data. + */ + function getParentBounds(node: InputNode & figrest.HasGeometryTrait): { + width: number; + height: number; + } { + const box = + "absoluteBoundingBox" in node + ? node.absoluteBoundingBox + : undefined; + const sz = "size" in node ? node.size : undefined; + return { + width: box?.width ?? sz?.x ?? 0, + height: box?.height ?? sz?.y ?? 0, + }; + } + /** * Creates a VectorNode from SVG path data. * Used for converting nodes with HasGeometryTrait (REST API with geometry=paths). * Applies to VECTOR, STAR, REGULAR_POLYGON, and other shape nodes. */ + /** + * When true, the path is stroke geometry from REST API (outlined stroke shape). + * Stroke color must be applied as fill; the child should have no stroke. + */ function createVectorNodeFromPath( pathData: string, geometry: { @@ -722,21 +956,18 @@ export namespace iofigma { options: { useFill: boolean; useStroke: boolean; + /** Stroke geometry is an outlined path: apply stroke color as fill, no stroke. */ + strokeAsFill?: boolean; } ): grida.program.nodes.VectorNode | null { if (!pathData) return null; try { const vectorNetwork = vn.fromSVGPathData(pathData); - const bbox = vn.getBBox(vectorNetwork); - - // Note: In test environment with mocked svg-pathdata, vector networks may be empty. - // This is expected and the positioning logic will still work correctly. + const { width, height } = getParentBounds(parentNode); - // The SVG path coordinates are already in the parent VECTOR node's coordinate space. - // We keep the vector network coordinates as-is and position the child at its bbox origin - // relative to the parent GroupNode. This preserves the correct spatial relationships - // between fill and stroke geometries. + // Use parent node bounds instead of computing bbox from path; Figma path data aligns with node coordinate space. + const strokeAsFill = options.strokeAsFill === true; return { id: childId, ...base_node_trait({ @@ -755,25 +986,43 @@ export namespace iofigma { }), ...positioning_trait({ relativeTransform: [ - [1, 0, bbox.x], - [0, 1, bbox.y], + [1, 0, 0], + [0, 1, 0], ], - size: { x: bbox.width, y: bbox.height }, + size: { x: width, y: height }, }), - ...(options.useFill ? fills_trait(parentNode.fills) : {}), - ...(options.useStroke - ? stroke_trait(parentNode) - : stroke_trait({ - strokes: [], - strokeWeight: 0, - })), + ...(strokeAsFill + ? { + ...fills_trait( + parentNode.strokes ?? [], + context, + imageRefsUsed + ), + ...stroke_trait( + { strokes: [], strokeWeight: 0 }, + context, + imageRefsUsed + ), + } + : { + ...(options.useFill + ? fills_trait(parentNode.fills, context, imageRefsUsed) + : {}), + ...(options.useStroke + ? stroke_trait(parentNode, context, imageRefsUsed) + : stroke_trait( + { strokes: [], strokeWeight: 0 }, + context, + imageRefsUsed + )), + }), ...("effects" in parentNode && parentNode.effects ? effects_trait(parentNode.effects) : effects_trait(undefined)), type: "vector", vector_network: vectorNetwork, - layout_target_width: bbox.width, - layout_target_height: bbox.height, + layout_target_width: width, + layout_target_height: height, fill_rule: map.windingRuleMap[geometry.windingRule] ?? "nonzero", }; } catch (e) { @@ -785,6 +1034,92 @@ export namespace iofigma { } } + /** + * Creates a PathNode from SVG path data. + * Used when prefer_path_for_geometry is true for render-only pipelines. + */ + function createPathNodeFromPath( + pathData: string, + geometry: { + windingRule: figrest.Path["windingRule"]; + }, + parentNode: InputNode & figrest.HasGeometryTrait, + childId: string, + name: string, + options: { + useFill: boolean; + useStroke: boolean; + strokeAsFill?: boolean; + } + ): grida.program.nodes.PathNode | null { + if (!pathData) return null; + + try { + const { width, height } = getParentBounds(parentNode); + const strokeAsFill = options.strokeAsFill === true; + return { + id: childId, + ...base_node_trait({ + name, + visible: "visible" in parentNode ? parentNode.visible : true, + locked: "locked" in parentNode ? parentNode.locked : false, + rotation: 0, + opacity: + "opacity" in parentNode && parentNode.opacity !== undefined + ? parentNode.opacity + : 1, + blendMode: + "blendMode" in parentNode && parentNode.blendMode + ? parentNode.blendMode + : "NORMAL", + }), + ...positioning_trait({ + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + size: { x: width, y: height }, + }), + ...(strokeAsFill + ? { + ...fills_trait( + parentNode.strokes ?? [], + context, + imageRefsUsed + ), + ...stroke_trait( + { strokes: [], strokeWeight: 0 }, + context, + imageRefsUsed + ), + } + : { + ...(options.useFill + ? fills_trait(parentNode.fills, context, imageRefsUsed) + : {}), + ...(options.useStroke + ? stroke_trait(parentNode, context, imageRefsUsed) + : stroke_trait( + { strokes: [], strokeWeight: 0 }, + context, + imageRefsUsed + )), + }), + ...("effects" in parentNode && parentNode.effects + ? effects_trait(parentNode.effects) + : effects_trait(undefined)), + type: "path", + data: pathData, + layout_target_width: width, + layout_target_height: height, + fill_rule: map.windingRuleMap[geometry.windingRule] ?? "nonzero", + }; + } catch (e) { + console.warn(`Failed to create path node (${name}):`, e); + return null; + } + } + /** * Processes fill geometries from a node with HasGeometryTrait. * Returns array of child node IDs that were successfully created. @@ -802,14 +1137,23 @@ export namespace iofigma { const childId = `${parentGridaId}_fill_${idx}`; const name = `${node.name || nodeTypeName} Fill ${idx + 1}`; - const childNode = createVectorNodeFromPath( - geometry.path ?? "", - geometry, - node, - childId, - name, - { useFill: true, useStroke: false } - ); + const childNode = context.prefer_path_for_geometry + ? createPathNodeFromPath( + geometry.path ?? "", + geometry, + node, + childId, + name, + { useFill: true, useStroke: false } + ) + : createVectorNodeFromPath( + geometry.path ?? "", + geometry, + node, + childId, + name, + { useFill: true, useStroke: false } + ); if (childNode) { nodes[childId] = childNode; @@ -837,14 +1181,23 @@ export namespace iofigma { const childId = `${parentGridaId}_stroke_${idx}`; const name = `${node.name || nodeTypeName} Stroke ${idx + 1}`; - const childNode = createVectorNodeFromPath( - geometry.path ?? "", - geometry, - node, - childId, - name, - { useFill: false, useStroke: true } - ); + const childNode = context.prefer_path_for_geometry + ? createPathNodeFromPath( + geometry.path ?? "", + geometry, + node, + childId, + name, + { useFill: false, useStroke: false, strokeAsFill: true } + ) + : createVectorNodeFromPath( + geometry.path ?? "", + geometry, + node, + childId, + name, + { useFill: false, useStroke: false, strokeAsFill: true } + ); if (childNode) { nodes[childId] = childNode; @@ -914,7 +1267,8 @@ export namespace iofigma { gridaId, images, parent, - context + context, + imageRefsUsed ); if (!processedNode) { @@ -952,7 +1306,7 @@ export namespace iofigma { // Generate a new scene ID const sceneId = generateId(); - return { + const packed: grida.program.document.IPackedSceneDocument = { nodes, links: graph, scene: { @@ -971,6 +1325,11 @@ export namespace iofigma { images: {}, properties: {}, }; + + return { + document: packed, + imageRefsUsed: Array.from(imageRefsUsed), + }; } /** @@ -983,6 +1342,7 @@ export namespace iofigma { * @param images * @param parent * @param context + * @param imageRefsUsed - Mutable set to collect image refs that need registration * @returns */ function node_without_children( @@ -990,7 +1350,8 @@ export namespace iofigma { gridaId: string, images: { [key: string]: string }, parent: FigmaParentNode | undefined, - context: FactoryContext + context: FactoryContext, + imageRefsUsed: Set ): grida.program.nodes.Node | undefined { switch (node.type) { case "SECTION": { @@ -1005,8 +1366,8 @@ export namespace iofigma { blendMode: "PASS_THROUGH", }), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...style_trait({}), ...corner_radius_trait({ cornerRadius: 0 }), ...container_layout_trait({}, false), @@ -1017,13 +1378,16 @@ export namespace iofigma { // case "COMPONENT": case "INSTANCE": - case "FRAME": { + case "FRAME": + // Fallback: treat COMPONENT_SET as FRAME for rendering. Grida does not yet + // support component semantics; proper variant/swap support to be added later. + case "COMPONENT_SET": { return { id: gridaId, ...base_node_trait(node), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...style_trait({ overflow: node.clipsContent ? "clip" : undefined, }), @@ -1082,8 +1446,8 @@ export namespace iofigma { return { id: gridaId, ...base_node_trait(node), - ...fills_trait(node.fills), - ...text_stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...text_stroke_trait(node, context, imageRefsUsed), ...style_trait({}), ...effects_trait(node.effects), type: "tspan", @@ -1127,8 +1491,8 @@ export namespace iofigma { id: gridaId, ...base_node_trait(node), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...corner_radius_trait(node), ...effects_trait(node.effects), type: "rectangle", @@ -1139,8 +1503,8 @@ export namespace iofigma { id: gridaId, ...base_node_trait(node), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...arc_data_trait(node), ...effects_trait(node.effects), type: "ellipse", @@ -1151,8 +1515,8 @@ export namespace iofigma { id: gridaId, ...base_node_trait(node), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...effects_trait(node.effects), type: "boolean", op: mapBooleanOperation(node.booleanOperation), @@ -1162,7 +1526,7 @@ export namespace iofigma { return { id: gridaId, ...base_node_trait(node), - ...stroke_trait(node), + ...stroke_trait(node, context, imageRefsUsed), ...effects_trait(node.effects), type: "line", layout_positioning: "absolute", @@ -1178,9 +1542,46 @@ export namespace iofigma { case "REGULAR_POLYGON": case "STAR": case "VECTOR": { - // Nodes with HasGeometryTrait (REST API with geometry=paths) don't have - // vector network data, only fillGeometry and strokeGeometry (SVG path strings). - // We'll create a GroupNode with child VectorNodes in processNode. + // When REST API returns prerelease vectorNetwork, use it for a single VectorNode; otherwise GroupNode + fill/stroke children. + // prefer_path_for_geometry takes priority: when true, always use fillGeometry/strokeGeometry (Path nodes) even if vectorNetwork is available. + const useRestVectorNetwork = + context.prefer_path_for_geometry !== true && + context.disable_volatile_apis !== true && + "vectorNetwork" in node && + node.vectorNetwork != null; + if (useRestVectorNetwork) { + try { + const ir = restful.map.normalizeRestVectorNetworkToIR( + node.vectorNetwork as __ir.VectorNetwork_restapi_volatile20260217 + ); + if (ir) { + const gridaVectorNetwork: vn.VectorNetwork = { + vertices: ir.vertices.map((v) => [v.x, v.y]), + segments: ir.segments.map((seg) => ({ + a: seg.start.vertex, + b: seg.end.vertex, + ta: [seg.start.dx, seg.start.dy], + tb: [seg.end.dx, seg.end.dy], + })), + }; + return { + id: gridaId, + ...base_node_trait(node), + ...positioning_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), + ...corner_radius_trait(node), + ...effects_trait(node.effects), + type: "vector", + vector_network: gridaVectorNetwork, + } satisfies grida.program.nodes.VectorNode; + } + } catch { + // Fall through to GroupNode + fillGeometry/strokeGeometry path + } + } + // Nodes with HasGeometryTrait (REST API with geometry=paths) without vectorNetwork + // or with invalid vectorNetwork: create GroupNode with child VectorNodes in processNode. return { id: gridaId, ...base_node_trait(node), @@ -1206,8 +1607,8 @@ export namespace iofigma { id: gridaId, ...base_node_trait(node), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...corner_radius_trait(node), ...effects_trait(node.effects), type: "vector", @@ -1219,8 +1620,8 @@ export namespace iofigma { id: gridaId, ...base_node_trait(node), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...effects_trait(node.effects), type: "star", point_count: node.pointCount, @@ -1232,19 +1633,14 @@ export namespace iofigma { id: gridaId, ...base_node_trait(node), ...positioning_trait(node), - ...fills_trait(node.fills), - ...stroke_trait(node), + ...fills_trait(node.fills, context, imageRefsUsed), + ...stroke_trait(node, context, imageRefsUsed), ...effects_trait(node.effects), type: "polygon", point_count: node.pointCount, } satisfies grida.program.nodes.RegularPolygonNode; } - // components - case "COMPONENT_SET": { - throw new Error(`Unsupported node type: ${node.type}`); - } - // figjam case "LINK_UNFURL": case "EMBED": @@ -1676,6 +2072,55 @@ export namespace iofigma { }; } + /** + * HasExportSettingsTrait + * Maps Kiwi exportSettings → REST. Skips MP4 (refig unsupported). + */ + function kiwi_has_export_settings_trait(nc: figkiwi.NodeChange) { + const settings = nc.exportSettings; + if (!settings?.length) return {}; + const FMT = ["PNG", "JPG", "SVG", "PDF"] as const; + const FMT_STR: Record = { + PNG: "PNG", + JPEG: "JPG", + JPG: "JPG", + SVG: "SVG", + PDF: "PDF", + }; + const CSTR = ["SCALE", "WIDTH", "HEIGHT"] as const; + const CSTR_STR: Record = { + CONTENT_SCALE: "SCALE", + CONTENT_WIDTH: "WIDTH", + CONTENT_HEIGHT: "HEIGHT", + }; + const fmt = (t: string | number | undefined) => + typeof t === "string" + ? (FMT_STR[t] ?? null) + : typeof t === "number" && t < 4 + ? FMT[t] + : null; + const cstr = (t: string | number | undefined) => + typeof t === "string" + ? (CSTR_STR[t] ?? "SCALE") + : typeof t === "number" && t < 3 + ? CSTR[t] + : "SCALE"; + + const exportSettings = settings + .map((s) => { + const f = fmt(s.imageType); + if (!f) return null; + const c = s.constraint; + return { + format: f, + suffix: s.suffix ?? "", + constraint: { type: cstr(c?.type), value: c?.value ?? 1 }, + }; + }) + .filter((x): x is figrest.ExportSetting => x !== null); + return exportSettings.length ? { exportSettings } : {}; + } + /** * HasChildrenTrait */ @@ -1916,6 +2361,7 @@ export namespace iofigma { ...kiwi_frame_clip_trait(nc), ...kiwi_children_trait(), ...kiwi_effects_trait(nc), + ...kiwi_has_export_settings_trait(nc), } satisfies figrest.FrameNode; } @@ -1933,6 +2379,7 @@ export namespace iofigma { ...kiwi_geometry_trait(nc), sectionContentsHidden: nc.sectionContentsHidden ?? false, ...kiwi_children_trait(), + ...kiwi_has_export_settings_trait(nc), } satisfies figrest.SectionNode; } @@ -1953,6 +2400,7 @@ export namespace iofigma { ...kiwi_frame_clip_trait(nc), ...kiwi_children_trait(), ...kiwi_effects_trait(nc), + ...kiwi_has_export_settings_trait(nc), } satisfies figrest.ComponentNode; } @@ -2659,6 +3107,8 @@ export namespace iofigma { metadata: { version: number; }; + /** ZIP contents from the .fig archive (for extractImages etc.). */ + zip_files?: { [key: string]: Uint8Array }; } export interface FigPage { @@ -2691,9 +3141,21 @@ export namespace iofigma { metadata: { version: figData.header.version, }, + zip_files: figData.zip_files, }; } + /** + * Extract images from .fig ZIP archive. + * @param zipFiles - Raw ZIP contents from FigFileDocument.zip_files or ParsedFigmaArchive.zip_files + * @returns Map of hash (hex string) to image bytes + */ + export function extractImages( + zipFiles: { [key: string]: Uint8Array } | undefined + ): Map { + return extractImagesFromFigKiwi(zipFiles); + } + /** * Extract pages (CANVAS nodes) with their complete hierarchies * Skips internal-only canvases (component libraries) @@ -2748,12 +3210,13 @@ export namespace iofigma { } /** - * Convert page to single packed document (bulk insert to avoid reducer nesting) + * Convert page to single packed document (bulk insert to avoid reducer nesting). + * Returns {@link restful.factory.FigmaImportResult} with merged document and imageRefsUsed. */ export function convertPageToScene( page: FigPage, - context: restful.factory.FactoryContext - ): grida.program.document.IPackedSceneDocument { + context: iofigma.restful.factory.FactoryContext + ): iofigma.restful.factory.FigmaImportResult { // IMPORTANT: // When converting multiple root nodes, each `restful.factory.document()` call maintains its // own local ID mapping. If the caller doesn't provide a `node_id_generator`, the fallback @@ -2765,18 +3228,19 @@ export namespace iofigma { const sharedNodeIdGenerator = context.node_id_generator ?? (() => `figma-import-${Date.now()}-${++counter}`); - const sharedContext: restful.factory.FactoryContext = { + const sharedContext: iofigma.restful.factory.FactoryContext = { ...context, node_id_generator: sharedNodeIdGenerator, }; - const individualDocs = page.rootNodes.map((rootNode) => - restful.factory.document(rootNode, {}, sharedContext) + const individualResults = page.rootNodes.map((rootNode) => + iofigma.restful.factory.document(rootNode, {}, sharedContext) ); - if (individualDocs.length === 1) return individualDocs[0]; + if (individualResults.length === 1) return individualResults[0]; // Merge multiple roots into single document + const imageRefsUsed = new Set(); const merged: grida.program.document.IPackedSceneDocument = { bitmaps: {}, images: {}, @@ -2796,16 +3260,21 @@ export namespace iofigma { }, }; - individualDocs.forEach((doc) => { + individualResults.forEach((result) => { + const doc = result.document; Object.assign(merged.nodes, doc.nodes); Object.assign(merged.links, doc.links); Object.assign(merged.images, doc.images); Object.assign(merged.bitmaps, doc.bitmaps); Object.assign(merged.properties, doc.properties); merged.scene.children_refs.push(...doc.scene.children_refs); + result.imageRefsUsed.forEach((ref: string) => imageRefsUsed.add(ref)); }); - return merged; + return { + document: merged, + imageRefsUsed: Array.from(imageRefsUsed), + }; } /** @@ -2822,8 +3291,8 @@ export namespace iofigma { static convertPageToScene( page: FigPage, - context: restful.factory.FactoryContext - ): grida.program.document.IPackedSceneDocument { + context: iofigma.restful.factory.FactoryContext + ): iofigma.restful.factory.FigmaImportResult { return convertPageToScene(page, context); } } diff --git a/packages/grida-canvas-io-figma/package.json b/packages/grida-canvas-io-figma/package.json index f8fd99c0fb..467721a01a 100644 --- a/packages/grida-canvas-io-figma/package.json +++ b/packages/grida-canvas-io-figma/package.json @@ -16,7 +16,7 @@ "kiwi-schema": "^0.5.0" }, "devDependencies": { - "@figma/rest-api-spec": "0.35.0", + "@figma/rest-api-spec": "0.36.0", "@grida/cg": "workspace:*", "@grida/schema": "workspace:*" } diff --git a/packages/grida-canvas-io-figma/vitest.config.ts b/packages/grida-canvas-io-figma/vitest.config.ts index ba9d4b9cfa..da9bbfb231 100644 --- a/packages/grida-canvas-io-figma/vitest.config.ts +++ b/packages/grida-canvas-io-figma/vitest.config.ts @@ -1,8 +1,9 @@ -import { defineConfig } from "vitest/config"; +import { configDefaults, defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, testTimeout: 60000, + exclude: [...configDefaults.exclude, "**/dist/**"], }, }); diff --git a/packages/grida-canvas-io-svg/lib.ts b/packages/grida-canvas-io-svg/lib.ts index 6ba6626574..8827ca390c 100644 --- a/packages/grida-canvas-io-svg/lib.ts +++ b/packages/grida-canvas-io-svg/lib.ts @@ -181,6 +181,7 @@ export namespace iosvg { type SVGIOCompatibleNodePrototype = | grida.program.nodes.ContainerNodePrototype | grida.program.nodes.GroupNodePrototype + | grida.program.nodes.VectorNodePrototype | grida.program.nodes.PathNodePrototype | grida.program.nodes.RectangleNodePrototype | grida.program.nodes.EllipseNodePrototype; @@ -253,7 +254,7 @@ export namespace iosvg { layout_inset_top: position.top, fill_rule: fill_rule, opacity: fillOpacity, - } satisfies grida.program.nodes.PathNodePrototype; + } satisfies grida.program.nodes.VectorNodePrototype; } case "text": { diff --git a/packages/grida-canvas-io/__tests__/archive.test.ts b/packages/grida-canvas-io/__tests__/archive.test.ts index 572ccf3ae6..6ca82de8e3 100644 --- a/packages/grida-canvas-io/__tests__/archive.test.ts +++ b/packages/grida-canvas-io/__tests__/archive.test.ts @@ -184,6 +184,85 @@ describe("archive (.grida zip)", () => { expect(unpacked.images).toEqual({}); }); + it("should pack/unpack archive with custom RID images (e.g. Figma 40-char ref)", () => { + const customRid = "b4bdc286e0d6b4ee51263b01f94aaa2c8a60595a"; + const document: grida.program.document.Document = { + nodes: { + scene: { + type: "scene", + id: "scene", + name: "Scene", + active: true, + locked: false, + guides: [], + edges: [], + constraints: { children: "multiple" }, + }, + rect: { + type: "rectangle", + id: "rect", + name: "Rect", + active: true, + locked: false, + opacity: 1, + z_index: 0, + layout_positioning: "absolute", + layout_inset_left: 0, + layout_inset_top: 0, + layout_target_width: 100, + layout_target_height: 100, + rotation: 0, + stroke_width: 0, + stroke_cap: "butt", + stroke_join: "miter", + fill_paints: [ + { + type: "image", + src: `res://images/${customRid}`, + fit: "cover", + blend_mode: "normal", + opacity: 1, + active: true, + filters: { + exposure: 0, + contrast: 0, + saturation: 0, + temperature: 0, + tint: 0, + highlights: 0, + shadows: 0, + }, + }, + ], + }, + }, + links: { scene: ["rect"] }, + scenes_ref: ["scene"], + entry_scene_id: "scene", + images: {}, + bitmaps: {}, + properties: {}, + }; + const images: Record = { + [`${customRid}.png`]: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), + }; + const packed = io.archive.pack(document, images, schemaVersion); + const unpacked = io.archive.unpack(packed); + expect(unpacked.images[`${customRid}.png`]).toBeDefined(); + expect( + bytesEqual( + unpacked.images[`${customRid}.png`], + images[`${customRid}.png`] + ) + ).toBe(true); + const decoded = io.GRID.decode(unpacked.document); + const rect = decoded.nodes["rect"] as grida.program.nodes.RectangleNode; + expect(rect.fill_paints?.[0]?.type).toBe("image"); + if (rect.fill_paints?.[0]?.type === "image") { + expect(rect.fill_paints[0].src).toBe(`res://images/${customRid}`); + } + }); + it("should handle special characters in image filenames", () => { const document = createTestDocument(); const specialImages: Record = { diff --git a/packages/grida-canvas-io/__tests__/format-roundtrip.test.ts b/packages/grida-canvas-io/__tests__/format-roundtrip.test.ts index c82a028ee8..5fa1ef22b3 100644 --- a/packages/grida-canvas-io/__tests__/format-roundtrip.test.ts +++ b/packages/grida-canvas-io/__tests__/format-roundtrip.test.ts @@ -871,7 +871,7 @@ describe("format roundtrip", () => { fill_paints: [ { type: "image", - src: "0123456789abcdef", + src: "res://images/0123456789abcdef", fit, opacity: 1, blend_mode: "normal", @@ -2054,7 +2054,8 @@ describe("format roundtrip", () => { }); describe("ImagePaint with fill_paints", () => { - it("roundtrips ImagePaint fill_paints on ContainerNode", () => { + it("roundtrips ImagePaint with ResourceRefRID (custom RID, e.g. Figma 40-char)", () => { + const customRid = "res://images/b4bdc286e0d6b4ee51263b01f94aaa2c8a60595a"; const sceneId = "0-1"; const nodeId = "0-2"; const doc = createDocument(sceneId, { @@ -2063,7 +2064,50 @@ describe("format roundtrip", () => { fill_paints: [ { type: "image", - src: "0123456789abcdef", + src: customRid, + fit: "cover", + blend_mode: "normal", + opacity: 1, + active: true, + filters: { + exposure: 0, + contrast: 0, + saturation: 0, + temperature: 0, + tint: 0, + highlights: 0, + shadows: 0, + }, + } satisfies cg.ImagePaint, + ], + }, + }); + roundtripTest( + doc, + nodeId, + "container", + (containerNode) => { + expect(containerNode.fill_paints?.length).toBe(1); + const paint = containerNode.fill_paints?.[0]; + expect(paint?.type).toBe("image"); + if (paint && paint.type === "image") { + expect(paint.src).toBe(customRid); + } + } + ); + }); + + it("roundtrips ImagePaint fill_paints on ContainerNode (hex16 via ResourceRefRID)", () => { + const sceneId = "0-1"; + const nodeId = "0-2"; + const hex16Src = "res://images/0123456789abcdef"; + const doc = createDocument(sceneId, { + [nodeId]: { + ...baseContainer(nodeId), + fill_paints: [ + { + type: "image", + src: hex16Src, fit: "cover", blend_mode: "normal", opacity: 1, @@ -2092,6 +2136,7 @@ describe("format roundtrip", () => { const paint = containerNode.fill_paints?.[0]; expect(paint?.type).toBe("image"); if (paint && paint.type === "image") { + expect(paint.src).toBe(hex16Src); expect(paint.fit).toBe("cover"); expect(paint.blend_mode).toBe("normal"); expect(paint.opacity).toBe(1); @@ -2386,7 +2431,7 @@ describe("format roundtrip", () => { stroke_paints: [ { type: "image", - src: "fedcba9876543210", + src: "res://images/fedcba9876543210", fit: "cover", blend_mode: "normal", opacity: 1, diff --git a/packages/grida-canvas-io/format.ts b/packages/grida-canvas-io/format.ts index c0fb1be1f0..da3fb2eaca 100644 --- a/packages/grida-canvas-io/format.ts +++ b/packages/grida-canvas-io/format.ts @@ -1551,8 +1551,9 @@ export namespace format { ); fbs.LineNode.addMarkerEndShape( builder, - enums.STROKE_MARKER_PRESET_ENCODE.get(lineNode.marker_end_shape) ?? - fbs.StrokeMarkerPreset.None + enums.STROKE_MARKER_PRESET_ENCODE.get( + lineNode.marker_end_shape + ) ?? fbs.StrokeMarkerPreset.None ); nodeOffset = fbs.LineNode.endLineNode(builder); nodeType = fbs.Node.LineNode; @@ -1945,41 +1946,29 @@ export namespace format { /** * Encodes ImagePaint. + * Uses ResourceRefRID only: src must be res:// or system://. */ export function image( builder: Builder, paint: cg.ImagePaint ): { type: fbs.Paint; offset: flatbuffers.Offset } { - // Hash-only persistence for image paints. - // Accepted src forms: - // - "" (canonical persisted identifier) - // - "res://images/" (engine runtime URL) - // - "mem://" (legacy/alternate runtime URL) - // - // NOTE: We intentionally do NOT encode RID/external URLs into FlatBuffers here. - // - // TODO(resources): once `res://` is the only cross-boundary identifier, restrict - // accepted runtime forms here to `res:///` (and drop `mem://` entirely). - // Also, `res://` should map to a general-purpose archive directory path, not a - // hardcoded `images/` folder. - const rawSrc = (paint.src || "").trim().toLowerCase(); - const src = rawSrc.startsWith("res://images/") - ? rawSrc.slice("res://images/".length) - : rawSrc.startsWith("mem://") - ? rawSrc.slice(6) - : rawSrc; - if (!/^[0-9a-f]{16}$/.test(src)) { + const rawSrc = (paint.src || "").trim(); + + let resourceRefOffset: flatbuffers.Offset; + let imageType: fbs.ResourceRef; + + if (rawSrc.startsWith("res://") || rawSrc.startsWith("system://")) { + const ridOffset = builder.createString(rawSrc); + fbs.ResourceRefRID.startResourceRefRID(builder); + fbs.ResourceRefRID.addRid(builder, ridOffset); + resourceRefOffset = fbs.ResourceRefRID.endResourceRefRID(builder); + imageType = fbs.ResourceRef.ResourceRefRID; + } else { throw new Error( - `ImagePaint.src must be hex16 (or res://images/, mem://) for persistence. Got: "${paint.src}"` + `ImagePaint.src must be res:// or system:// resource ref. Got: "${paint.src}"` ); } - const hashOffset = builder.createString(src); - fbs.ResourceRefHASH.startResourceRefHASH(builder); - fbs.ResourceRefHASH.addHash(builder, hashOffset); - const resourceRefOffset = - fbs.ResourceRefHASH.endResourceRefHASH(builder); - // Create ImagePaintFit based on fit type let fitType: fbs.ImagePaintFit; let fitOffset: flatbuffers.Offset; @@ -2019,7 +2008,7 @@ export namespace format { // Structs must be created inline within table context fbs.ImagePaint.startImagePaint(builder); fbs.ImagePaint.addActive(builder, paint.active ?? true); - fbs.ImagePaint.addImageType(builder, fbs.ResourceRef.ResourceRefHASH); + fbs.ImagePaint.addImageType(builder, imageType); fbs.ImagePaint.addImage(builder, resourceRefOffset); fbs.ImagePaint.addQuarterTurns( builder, @@ -2315,9 +2304,9 @@ export namespace format { /** * Decodes ImagePaint. + * Supports both ResourceRefHASH (hex16) and ResourceRefRID (logical RID). */ export function image(paintValue: unknown): cg.ImagePaint { - // NOTE: hash-only decode for image resource reference. const imagePaint = paintValue as fbs.ImagePaint; // Decode src from ResourceRef @@ -2328,13 +2317,16 @@ export namespace format { new fbs.ResourceRefHASH() ) as fbs.ResourceRefHASH | null; if (resourceRef) { - // NOTE: WASM renderer currently uses `res://images/` as the cross-boundary image id. - // Persisted form remains plain hex16; decode returns the runtime identifier. - // - // TODO(resources): once `res:///` is fully generalized, avoid hardcoding `images/`. const hash = resourceRef.hash() ?? ""; src = hash ? `res://images/${hash}` : ""; } + } else if (imageType === fbs.ResourceRef.ResourceRefRID) { + const resourceRef = imagePaint.image( + new fbs.ResourceRefRID() + ) as fbs.ResourceRefRID | null; + if (resourceRef) { + src = resourceRef.rid() ?? ""; + } } // Decode fit from ImagePaintFit union diff --git a/packages/grida-canvas-io/index.ts b/packages/grida-canvas-io/index.ts index 98508462fb..2531c9218f 100644 --- a/packages/grida-canvas-io/index.ts +++ b/packages/grida-canvas-io/index.ts @@ -602,7 +602,7 @@ export namespace io { /** * Optional raw assets extracted from an archive. * - * Keys are canonical image hash ids (hex16), values are encoded image bytes. + * Keys are image refs (filename sans extension), values are bytes. */ assets?: { images: Record; @@ -779,7 +779,7 @@ export namespace io { for (const [key, imageData] of Object.entries(_x_images)) { const base = key.split("/").pop() ?? key; - const hashHex = base.includes(".") ? base.split(".")[0]! : base; + const ref = base.includes(".") ? base.split(".")[0]! : base; const dimensions = imageSize(new Uint8Array(imageData)); if (!dimensions || !dimensions.width || !dimensions.height) { @@ -788,9 +788,9 @@ export namespace io { const { width, height, type } = dimensions; const mimeType = IMAGE_TYPE_TO_MIME_TYPE[type || "png"] || "image/png"; - assets[hashHex] = imageData; - imagesRepo[hashHex] = { - url: hashHex, + assets[ref] = imageData; + imagesRepo[ref] = { + url: `res://images/${ref}`, width, height, bytes: imageData.byteLength, diff --git a/packages/grida-canvas-schema/grida.ts b/packages/grida-canvas-schema/grida.ts index e5b5e42abb..8b66f7eea0 100644 --- a/packages/grida-canvas-schema/grida.ts +++ b/packages/grida-canvas-schema/grida.ts @@ -1194,6 +1194,7 @@ export namespace grida.program.nodes { | HTMLRichTextNode | BitmapNode | VectorNode + | PathNode | LineNode | RectangleNode | EllipseNode @@ -1212,6 +1213,7 @@ export namespace grida.program.nodes { | ComputedHTMLIFrameNode | ComputedHTMLRichTextNode | ComputedVectorNode + | ComputedPathNode | ComputedLineNode | ComputedRectangleNode | ComputedEllipseNode @@ -1231,6 +1233,7 @@ export namespace grida.program.nodes { Partial & Partial & Partial & + Partial & Partial & Partial & Partial & @@ -1257,6 +1260,7 @@ export namespace grida.program.nodes { Partial & Partial & Partial & + Partial & Partial & Partial & Partial & @@ -1302,6 +1306,9 @@ export namespace grida.program.nodes { __IPrototypeNodeChildren >; export type PathNodePrototype = __TPrototypeNode< + Omit, __base_scene_node_properties> + >; + export type VectorNodePrototype = __TPrototypeNode< Omit, __base_scene_node_properties> >; export type LineNodePrototype = __TPrototypeNode< @@ -1340,6 +1347,7 @@ export namespace grida.program.nodes { > | __TPrototypeNode, __base_scene_node_properties>> | PathNodePrototype + | VectorNodePrototype | LineNodePrototype | RectangleNodePrototype | EllipseNodePrototype @@ -2424,6 +2432,34 @@ export namespace grida.program.nodes { readonly type: "vector"; } + /** + * Path Node + * + * SVG path compatible node that stores raw SVG path data. Use for render-only + * pipelines where editing is not needed. Avoids vector network conversion. + * + * - [Env:WASM] Renders via Path::from_svg and Skia path + * - [Env:HTML/SVG] Not yet supported in DOM renderer + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path} + */ + export interface PathNode + extends i.IBaseNode, + i.ISceneNode, + i.ILayerTrait, + i.ILayoutChildTrait, + i.IBasicShapeTrait, + i.IHotspotTrait { + readonly type: "path"; + data: string; + fill_rule?: cg.FillRule; + } + + export interface ComputedPathNode + extends __ReplaceSubset, i.IFill> { + readonly type: "path"; + } + /** * Line Node * diff --git a/packages/grida-canvas-sdk-render-figma/.gitignore b/packages/grida-canvas-sdk-render-figma/.gitignore new file mode 100644 index 0000000000..4402257773 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/.gitignore @@ -0,0 +1 @@ +.package.json.prepack.bak diff --git a/packages/grida-canvas-sdk-render-figma/AGENTS.md b/packages/grida-canvas-sdk-render-figma/AGENTS.md new file mode 100644 index 0000000000..00fdeba6a0 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/AGENTS.md @@ -0,0 +1,51 @@ +# @grida/refig + +Headless Figma renderer — render `.fig` and REST API JSON to PNG/JPEG/WebP/PDF/SVG. + +## Dependencies + +**dependencies** — What npm consumers install. Must be resolvable from npm (no `workspace:*`). + +- `@grida/canvas-wasm` — published on npm; kept external (WASM/binary). +- `commander` — kept external (CJS dynamic require does not bundle cleanly). + +**devDependencies** — Build-time / bundled into `dist`. Uses `workspace:*` for monorepo packages. + +- `@grida/io-figma`, `@grida/schema` — bundled via tsup `noExternal` regex. +- Other `@grida/*` pulled in transitively are bundled too. + +**Note:** Project is premature; only a few packages are published on npm. The split between deps and devDeps reflects this. Will be cleaned up with proper semver + changeset; all runtime deps will live in `dependencies` once everything is published. + +## Build + +tsup bundles all `@grida/*` except `@grida/canvas-wasm`. `external: ["@grida/canvas-wasm", "commander"]`; `noExternal: [/^@grida\/(?!canvas-wasm$)/]`. + +## Smoke test + +We have a publish smoke test because of the extra config (deps vs devDeps split, bundling). `__tests__/refig.cli.smoke.test.ts` — packs the package, installs in an isolated temp dir, runs `npx refig --help` and a real render (minimal REST fixture → PNG). Ensures it works for npm consumers (no workspace deps at runtime, WASM loads). Must pass before publish. + +## Publishing + +**TODO:** This is a temporary hack. Once `@grida/io-figma` and `@grida/schema` are published to npm, remove `scripts/prepack-publish.cjs` and `scripts/postpack-publish.cjs`, drop the prepack/postpack lifecycle from `package.json`, move those deps to `dependencies` with semver ranges, and use changesets normally. The prepack/postpack scripts exist only because we publish refig before its internal deps are on npm — they strip `workspace:*` from the manifest at pack time so the tarball has no unpublished deps. + +From repo root: + +```sh +pnpm install +pnpm -C packages/grida-canvas-sdk-render-figma build +pnpm publish ./packages/grida-canvas-sdk-render-figma --no-git-checks +``` + +Use `--dry-run` to test without uploading. Avoid `pnpm -C ... publish` — it can trigger npm EUSAGE; use the explicit path form instead. + +Bump version before publish (manual or via changesets) if needed. + +## How to develop + +```sh +pnpm build +pnpm test +pnpm typecheck +``` + +From monorepo root: `pnpm turbo build --filter=@grida/refig`, `pnpm turbo test --filter=@grida/refig`. diff --git a/packages/grida-canvas-sdk-render-figma/FONTS.md b/packages/grida-canvas-sdk-render-figma/FONTS.md new file mode 100644 index 0000000000..f6e418b5b4 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/FONTS.md @@ -0,0 +1,65 @@ +# Font strategy for @grida/refig (Figma renderer) + +This document describes Figma’s default font behavior and how this renderer aligns with it so that exported output matches Figma’s rendering (where possible). + +--- + +## Figma’s default font behavior + +Figma uses **platform-agnostic default fonts** and **implicit font fallback**. The fallback chain is not exposed in the Figma API or stored in `.fig` files. + +- **Primary font**: Whatever the designer sets (e.g. Caveat, Inter, Roboto). Only this family (and sometimes style) is stored in the file or returned by the API. +- **Fallback**: When the primary font lacks glyphs for some characters, Figma automatically uses a fixed set of default fonts. The **per-character** choice of fallback font is **not** stored and **not** returned in any API or in the `.fig` format. +- **Example**: A text node with font **Caveat** and content `"ABC가나다"` will have only `fontFamily: "Caveat"` (or equivalent) in the document. The Latin "ABC" is drawn with Caveat; the Korean "가나다" is drawn with a Figma platform default (e.g. Noto Sans KR). That Korean run is **implicit**—there is no separate style or font reference for "가나다" in the API or `.fig`; it is simply Figma’s platform default behavior. + +So for mixed-script text, the **effective** fonts used at render time include both the document font and Figma’s default fallback set, even though only the primary font is explicit in the file. + +Figma has stated they **fall back only to Noto fonts**, regardless of platform ([Figma: When fonts fall](https://www.figma.com/blog/when-fonts-fall/)). The effective default fallback set (by script) is summarized below. Exact names/order may vary; this table reflects the mapping we align to. + +| Script / usage | Figma default font | Note | +| ---------------------- | ----------------------------------- | ---------------------------------- | +| Latin, Cyrillic, Greek | Inter | Common default for Western | +| Korean (Hangul, etc.) | Noto Sans KR | CJK fallback | +| Japanese (Kana, Kanji) | Noto Sans JP | CJK fallback | +| Chinese (Simplified) | Noto Sans SC | CJK fallback | +| Chinese (Traditional) | Noto Sans TC | Optional; TC/HK variants | +| Chinese (Hong Kong) | Noto Sans HK | Optional | +| Emoji | Platform font (see [Emoji](#emoji)) | Apple Color Emoji / Segoe UI Emoji | + +None of the fallback choices appear in the Figma API or `.fig`; they are implicit at render time. + +--- + +## Aligning this renderer with Figma + +To avoid **tofu** (missing glyphs / `.notdef`) and to approximate Figma’s behavior, the renderer should **boot with the same config**: + +| Step | Action | +| ---- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | **Load** the default font set (same families as the table above: Inter + Noto Sans KR/JP/SC, and optionally TC/HK). | +| 2 | **Register** each font with the canvas (e.g. `addFont(family, bytes)`). | +| 3 | **Set fallback order** (e.g. `setFallbackFonts([...])`) so the canvas uses the same script → font mapping when the primary font lacks glyphs. | + +Then any document that relies on implicit fallback (e.g. Caveat + `"ABC가나다"`) will render with the expected CJK font instead of tofu. Family names and order should match Figma where documented or reverse‑engineered. + +The built-in implementation uses CDN URLs defined in the package (see `figma-default-fonts.ts`) and does not ship font files in the bundle. + +--- + +## Emoji + +| | Figma | This renderer (@grida/refig) | +| ---------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | +| **Font** | **Platform** emoji font (e.g. Apple Color Emoji on macOS, Segoe UI Emoji on Windows) | **Noto Color Emoji** | +| **Reason** | OS-provided; rendering is OS-dependent | We cannot ship Apple/Segoe emoji fonts (license/redistribution). | +| **Output** | Varies by platform | Fixed (Noto Color Emoji); **different from Figma by design**. | + +Same logic applies: unsupported characters (including emoji) fall back to a default font. We intentionally use a different default for emoji, so **different render output is expected** for emoji (and for any other characters Figma would draw with a platform-specific font we don’t ship). + +This is the only documented, intentional deviation from Figma’s default behavior; the rest of the strategy (Noto for CJK, explicit default set + fallback order) is intended to align with Figma. + +--- + +## Last updated + +**2025-02-17** — Figma’s default font set and fallback behavior are unlikely to change often but can change without notice. Re‑check Figma’s behavior and docs when doing font-related work. diff --git a/packages/grida-canvas-sdk-render-figma/LICENSE b/packages/grida-canvas-sdk-render-figma/LICENSE new file mode 100644 index 0000000000..943658afac --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Grida + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/grida-canvas-sdk-render-figma/README.md b/packages/grida-canvas-sdk-render-figma/README.md index 6cf205800b..4a8e16c446 100644 --- a/packages/grida-canvas-sdk-render-figma/README.md +++ b/packages/grida-canvas-sdk-render-figma/README.md @@ -1,151 +1,388 @@ - +# `@grida/refig` -# `@grida/refig` (planned) +> **re**nder **fig**ma — headless Figma renderer (Node.js + browser) in the spirit of [`resvg`](https://github.com/linebender/resvg) -> **re**: render, **fig**: figma → **refig** (in the spirit of `resvg`) +Render Figma documents to **PNG, JPEG, WebP, PDF, and SVG** in **Node.js (no browser required)** or directly in the **browser**. -Headless Figma renderer for **pixel-perfect exports** without Figma Desktop and without a browser. +Use a `.fig` export (offline) or a Figma REST API file JSON response (`GET /v1/files/:key`), pick a node ID, and get pixels. -This package takes a Figma document file (`.fig`), converts it into **Grida IR**, then renders via the Grida Canvas runtime while preserving core properties (layout, constraints, images, etc.). The goal is to make “render this Figma node at this viewport size” deterministic and CI-friendly. +## Features (checklist) -## Status +- [x] Render from **`.fig` files** (offline / no API calls) +- [x] Render from **Figma REST API JSON** (bring your own auth + HTTP client) +- [x] Output formats: **PNG, JPEG, WebP, PDF, SVG** +- [x] **CLI** (`refig`) and **library API** (`FigmaDocument`, `FigmaRenderer`) +- [x] **Node.js** + **browser** entrypoints (`@grida/refig`, `@grida/refig/browser`) +- [x] IMAGE fills supported via **embedded `.fig` images** or a **local `images/` directory** for REST JSON +- [x] Batch export with **`--export-all`** (renders nodes with Figma export presets) +- [x] WASM + Skia-backed renderer via `@grida/canvas-wasm` -- **WIP / planned**: API shape, feature set, and package name may change. +## Use cases -## Features +- Export assets in CI (deterministic, no network calls required) +- Generate thumbnails / previews from `.fig` or REST JSON +- Offline / air-gapped rendering from `.fig` exports +- In-browser previews with `@grida/refig/browser` -- **Multiple output formats**: `png`, `jpeg`, `webp`, `pdf`, `svg` -- **`.fig` file input**: render from an exported `.fig` without waiting on API calls -- **CI-friendly**: runs headlessly (no browser / no Figma Desktop) -- **Layout-aware rendering**: render responsive designs at arbitrary viewport sizes (within supported Figma semantics) +## Install -## Use cases +```sh +pnpm add @grida/refig +``` -- **Research / studies**: reproducible rendering for datasets that rely heavily on Figma -- **Design-as-input pipelines**: build apps that consume Figma but need high-fidelity raster/vector output -- **Post-processing**: render then run your own pixel pipeline (filters, analysis, diffing, etc.) +## Entrypoints -## Who is this for? +| Import | Environment | Notes | +| ---------------------- | ----------- | ------------------------------------------------------------------------- | +| `@grida/refig` | **Node.js** | Default. Includes `fs` helpers for reading `.fig` / JSON files from disk. | +| `@grida/refig/browser` | **Browser** | No `node:fs` dependency. Accepts `Uint8Array` and JSON objects only. | -- People who want **rendered output** (images / PDFs / SVGs) from Figma sources in a headless environment -- Teams who need **repeatable rendering in CI** (snapshot tests, design regression tests, batch export) +Both entrypoints export the same core API (`FigmaDocument`, `FigmaRenderer`, types). The only difference is that the Node entrypoint adds a convenience `FigmaDocument.fromFile(path)` static method. -## Who is this NOT for? +## Quick start (Node) -- **Design-to-code conversion**: if you want HTML/CSS, Flutter widgets, etc., this is not the right tool -- **Authoring / editing Figma**: this module is intended for **reading + rendering**, not programmatically creating or modifying designs -- **“Just export images from the Figma API”**: if you already have Figma API access, the Images API is usually the simplest solution. `.fig`-based rendering is intended for headless, reproducible exports when you don’t want to depend on the API at render time. +### Render from a `.fig` file -## Getting started +```ts +import { writeFileSync } from "node:fs"; +import { FigmaDocument, FigmaRenderer } from "@grida/refig"; -> The API below reflects the current draft and may change. +const doc = FigmaDocument.fromFile("path/to/file.fig"); +const renderer = new FigmaRenderer(doc); -### Install +const { data } = await renderer.render("", { + format: "png", + width: 1024, + height: 1024, + scale: 2, +}); -```sh -pnpm add @grida/refig +writeFileSync("out.png", data); +renderer.dispose(); ``` -> If the package is not yet published, you can depend on the workspace version from this repo. - -### Render from a `.fig` file +### Render from Figma REST API JSON ```ts -import { writeFileSync } from "node:fs"; +import { readFileSync, writeFileSync } from "node:fs"; import { FigmaDocument, FigmaRenderer } from "@grida/refig"; -// Input from a local `.fig` file -const document = new FigmaDocument("path/to/file.fig"); +// GET /v1/files/:key — fetched by your own client +const json = JSON.parse(readFileSync("figma-response.json", "utf-8")); + +const renderer = new FigmaRenderer(new FigmaDocument(json)); -const renderer = new FigmaRenderer(document, { - // When enabled, the renderer may download fonts (network access required). - // Alternative strategies (e.g. local font dirs) may be supported later. - useGoogleFonts: true, +const { data } = await renderer.render("", { + format: "svg", }); +writeFileSync("out.svg", data); +renderer.dispose(); +``` + +> Fetching / authentication is intentionally out of scope. Provide the document data from your own API layer. + +### Render from REST JSON with custom images + +When your document has IMAGE fills, pass image bytes keyed by the Figma image ref (hash): + +```ts +import { readFileSync, readdirSync } from "node:fs"; +import path from "node:path"; +import { FigmaDocument, FigmaRenderer } from "@grida/refig"; + +const json = JSON.parse(readFileSync("figma-response.json", "utf-8")); +const imagesDir = "./downloaded-images"; +const images: Record = {}; +for (const file of readdirSync(imagesDir)) { + const ref = path.basename(file).replace(/\.[^.]+$/, ""); + images[ref] = new Uint8Array(readFileSync(path.join(imagesDir, file))); +} + +const renderer = new FigmaRenderer(new FigmaDocument(json), { images }); +const { data } = await renderer.render("", { format: "png" }); +// ... +renderer.dispose(); +``` + +## Quick start (Browser) + +```ts +import { FigmaDocument, FigmaRenderer } from "@grida/refig/browser"; + +// `file` is a File from , drag-and-drop, etc. +// Uint8Array from a File input, fetch(), or drag-and-drop +const figBytes: Uint8Array = await file + .arrayBuffer() + .then((b) => new Uint8Array(b)); + +const renderer = new FigmaRenderer(new FigmaDocument(figBytes)); + const { data } = await renderer.render("", { format: "png", - scale: 1, - width: 1024, - height: 1024, + width: 512, + height: 512, +}); + +// data is a Uint8Array — display it, upload it, etc. +const blob = new Blob([data], { type: "image/png" }); +renderer.dispose(); +``` - // Toggle which parts of the pipeline run (names may change). - layout: true, - images: true, +## API + +### `FigmaDocument` + +```ts +// From raw .fig bytes (Node + Browser) +new FigmaDocument(figBytes: Uint8Array) + +// From Figma REST API JSON (Node + Browser) +new FigmaDocument(json: Record) + +// From a file path (Node only — @grida/refig) +FigmaDocument.fromFile("path/to/file.fig") // .fig binary +FigmaDocument.fromFile("path/to/doc.json") // REST API JSON +``` + +### `FigmaRenderer` + +```ts +const renderer = new FigmaRenderer(document: FigmaDocument, options?: { + useEmbeddedFonts?: boolean; // default: true + images?: Record; // image ref → bytes; used for REST API IMAGE fills }); -writeFileSync("out.png", data); +const result = await renderer.render(nodeId: string, { + format: "png" | "jpeg" | "webp" | "pdf" | "svg"; + width?: number; // default: 1024 + height?: number; // default: 1024 + scale?: number; // default: 1 +}); + +// result.data — Uint8Array (encoded image / document bytes) +// result.format +// result.mimeType +// result.nodeId +// result.width +// result.height + +renderer.dispose(); // release WASM resources ``` -## CLI +### `RefigRenderResult` -> Planned. The exact command name / flags may change. +```ts +interface RefigRenderResult { + data: Uint8Array; + format: "png" | "jpeg" | "webp" | "pdf" | "svg"; + mimeType: string; + nodeId: string; + width: number; + height: number; +} +``` + +## CLI -### Install (optional, for `refig` command) +### Install ```sh pnpm add -g @grida/refig ``` -### Render a node from a `.fig` file +Or run without installing: ```sh -refig ./path/to/file.fig \ - --node "" \ - --out ./out.png \ - --format png \ - --width 1024 \ - --height 1024 \ - --scale 1 +# Instant usage (writes to OS temp dir; output path printed) +npx @grida/refig --node --format png +pnpm dlx @grida/refig --export-all ``` -### No-install (one-off / CI) +### Usage + +**``** can be: + +- A **file**: path to a `.fig` file or a JSON file (Figma REST API response). +- A **directory**: path to a folder that contains: + - **`document.json`** — the REST API response (required), + - **`images/`** — directory of image assets (optional; used for REST API IMAGE fills). + +Using a directory avoids passing the document and images separately. ```sh -pnpm dlx @grida/refig ./path/to/file.fig \ - --node "" \ - --out ./out.png \ - --format png \ - --width 1024 \ - --height 1024 \ - --scale 1 +# Single node (default) +# - Without --out: writes to OS temp dir (requires --format) +# - With --out: format inferred from file extension unless --format is provided +refig --node --format [options] +refig --node --out [--format ] [options] + +# With images directory (REST JSON only; IMAGE fills rendered from local files) +refig --images --node --format [options] +refig --images --node --out [--format ] [options] + +# Directory input: document.json + images/ under one folder +refig ./my-figma-export --node "1:23" --format png + +# Export all nodes that have exportSettings (REST JSON or .fig) +refig --export-all [--out ] ``` -### Common flags (draft) +### Examples -- **`--node`**: node id to render -- **`--out`**: output file path -- **`--format`**: `png | jpeg | webp | pdf | svg` -- **`--width` / `--height`**: viewport size (optional; affects layout) -- **`--scale`**: raster scale factor (optional; default `1`) -- **`--fonts`**: font strategy (e.g. `google`, `local:`) (planned) +```sh +# Instant usage: omit --out to write to OS temp directory (output path printed) +refig ./design.fig --node "1:23" --format png +refig ./figma-response.json --node "1:23" --format svg -## Rendering notes +# Directory with document.json (and optionally images/): one path instead of response + --images +refig ./my-figma-export --node "1:23" --format png +# (my-figma-export/document.json, my-figma-export/images/) -- **Node selection**: you render a specific node (frame/component/group/etc.) by node id. -- **Viewport**: you can control output dimensions (`width` / `height`) to test responsive constraints. -- **Fonts**: Figma fidelity depends on available fonts. If fonts aren’t resolvable, output may differ. +# Explicit images directory (when not using a project directory) +refig ./figma-response.json --images ./downloaded-images --node "1:23" --format png -## Roadmap (non-binding) +# Export all: render every node that has export settings (see below) +refig ./figma-response.json --export-all +refig ./design.fig --export-all -- Better font resolution strategies (local font dirs, explicit font mapping, caching) -- More complete Figma feature coverage (effects, text shaping edge cases, vectors) -- Stable, documented API surface + versioned compatibility guarantees +# Scale 2x, custom dimensions +refig ./design.fig --node "1:23" --format png --width 512 --height 512 --scale 2 + +# Deterministic output: provide --out (useful for CI or saving into a known path) +refig ./design.fig --node "1:23" --out ./out.png +refig ./figma-response.json --export-all --out ./exports + +# No-install (run without installing) +npx @grida/refig ./design.fig --node "1:23" --format png +pnpm dlx @grida/refig ./design.fig --export-all +``` + +### Quick test via `figma_archive.py` (REST API → `document.json` + `images/`) + +If you want an end-to-end test from a real Figma file using the REST API, you can generate a local “project directory” that refig can consume directly. + +1. Archive a Figma file (stdlib-only Python script): + +- Script: [`figma_archive.py` (gist)](https://gist.github.com/softmarshmallow/27ad65dfa5babc2c67b41740f1f05791) +- (For repo contributors, it’s also in this monorepo at `.tools/figma_archive.py`.) +- Save the script locally as `figma_archive.py`, then run: + +```sh +# File key is the "" part of `https://www.figma.com/file//...` +python3 figma_archive.py --x-figma-token "" --filekey "" --archive-dir ./my-figma-export +``` + +This writes: + +- `./my-figma-export/document.json` (with `geometry=paths`) +- `./my-figma-export/images/.` (image fills downloaded from `/v1/files/:key/images`) + +2. Render using the directory as ``: + +```sh +# Single node +refig ./my-figma-export --node "1:23" --format png + +# Or export everything with Figma export presets +refig ./my-figma-export --export-all +``` + +### Export all (`--export-all`) + +With **`--export-all`**, refig walks the document and renders every node that has [Figma export settings](https://www.figma.com/developers/api#exportsetting-type) — one file per (node, setting), using that setting’s format, suffix, and constraint. Both **REST API JSON** (e.g. `GET /v1/files/:key`) and **`.fig` files** are supported when the file includes export settings. + +**When it’s useful:** You choose in Figma exactly what to export: select a node, click **Export +** in the right panel, add one or more presets (e.g. PNG @2x, SVG). Add exports on as many nodes as you want. Then run the CLI with `--export-all` and the path to your REST JSON; refig renders all of those with the same config, without long or repeated `--node` / `--format` / `--scale` options. Same idea for testing the renderer: add export presets on the nodes you care about in Figma, run `refig … --export-all --out ./out`, and compare outputs. + +**REST API note:** The Figma REST API (`GET /v1/files/:key`) does not include `exportSettings` for SECTION nodes, even when those sections have export presets in Figma. FRAME, COMPONENT, INSTANCE, etc. correctly include them. As a result, `--export-all` on REST JSON will not discover SECTION exports; use a `.fig` file input if you need to export nodes that are sections. See [figma/rest-api-spec#87](https://github.com/figma/rest-api-spec/issues/87). + +### Flags + +| Flag | Required | Default | Description | +| ---------------- | -------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `` | yes | | Path to `.fig`, JSON file, or directory containing `document.json` (and optionally `images/`) | +| `--images ` | no | | Directory of image assets for REST document (ignored if `` is a dir with `images/`) | +| `--node ` | yes\* | | Figma node ID to render (\*omit when using `--export-all`) | +| `--out ` | no | OS temp dir when omitted | Output file path (single node) or output directory (`--export-all`). When omitted, writes to the OS temp directory (valid with `--export-all` or with both `--format` and `--node`). | +| `--export-all` | no | | Export every node with exportSettings (REST JSON or .fig); `--out` is a directory | +| `--format ` | no | inferred from `--out` extension | `png`, `jpeg`, `webp`, `pdf`, `svg` (single-node only; required when `--out` is omitted) | +| `--width ` | no | `1024` | Viewport width (single-node only) | +| `--height ` | no | `1024` | Viewport height (single-node only) | +| `--scale ` | no | `1` | Raster scale factor (single-node only) | + +## Architecture + +``` +Input Conversion Rendering +───── ────────── ───────── +.fig bytes ──┐ + ├──→ @grida/io-figma ──→ Grida IR ──→ @grida/canvas-wasm ──→ PNG/JPEG/WebP/PDF/SVG +REST JSON ───┘ +``` + +- **`@grida/io-figma`** converts Figma data (`.fig` Kiwi binary or REST API JSON) into Grida's intermediate representation +- **`@grida/canvas-wasm`** renders the IR via Skia (raster backend for headless, WebGL for browser) +- **`@grida/refig`** ties them together behind a simple `render(nodeId, options)` call + +## Images + +**`.fig` input** — Image fills used in the design are stored inside the `.fig` file. No extra step is required; refig uses them when rendering. + +**REST API input** — The file JSON does not contain image bytes; it references image fills by hash. To render with correct bitmaps you must supply the image assets: + +1. **Fetch image fills** — Call `GET /v1/files/:key/images` (Figma REST API). This returns the list of **image fills** used in the file (i.e. which bitmap images are used as fills), not “export node as image.” The response includes a mapping of image hash → URL (signed) for each fill. + +2. **Download and pass an images directory (recommended)** — Download each image from the returned URLs and save them under a directory using the `.` naming (e.g. `a1b2c3d4....png`). Pass that directory to refig as the **images directory**. We recommend this because the URLs from the API are **signed and expire**; downloading once and reusing the files avoids expiry and keeps rendering repeatable (e.g. in CI or offline). + +**API** — `FigmaRenderer` accepts an optional **`images`** option: `Record` (image ref → bytes). Supply image assets when using REST document input; IMAGE fills will render using these bytes. Refs must match the Figma image fill hashes in the document. + +**CLI** — You can pass images in two ways: + +- **`--images `** — Explicit images directory. Files are keyed by filename without extension (e.g. `a1b2c3d4.png` → ref `a1b2c3d4`). Use when the document is a separate file: + `refig ./figma-response.json --images ./downloaded-images --node "1:23" --format png` +- **Directory input** — Pass a single directory that contains **`document.json`** (REST response) and optionally **`images/`**. No need to pass `--images` separately: + `refig ./my-figma-export --node "1:23" --format png` + (expects `my-figma-export/document.json` and, if present, `my-figma-export/images/`.) + +For **`.fig`** input, images are embedded in the file; no extra images directory is needed. For **REST** input, use `--images` or a project directory with `images/` to render IMAGE fills correctly. ## Not planned -- **Figma API response ingestion**: if you have API access, you can typically use Figma’s Images API directly. This project focuses on `.fig` inputs and headless rendering. +- **Figma API fetching / auth** — bring your own tokens and HTTP client +- **Design-to-code** — this renders pixels, not HTML/CSS/Flutter +- **Authoring / editing** — read + render only ## FAQ -### Are other languages supported besides Node.js (TS/JS)? +### Why not just use the Figma Images API? + +If you have API access, the Images API is usually simplest. This package is for when you need: + +- Offline / air-gapped rendering +- Deterministic output in CI without network calls +- Custom viewport sizes or scale factors +- Rendering from `.fig` files without API access +- High-throughput or random access where the API is too slow or rate-limited (e.g. low Figma tier) +- Avoiding Figma access token lifecycle (refresh, storage, rotation) + +### Does this work in the browser? + +Yes. Import from `@grida/refig/browser`. The core renderer uses `@grida/canvas-wasm` which supports both Node (raster) and browser (WebGL) backends. + +### What about fonts? + +The WASM runtime ships with embedded fallback fonts (Geist / Geist Mono). **`loadFigmaDefaultFonts`** is enabled by default: the renderer loads the Figma default font set (Inter, Noto Sans KR/JP/SC, and optionally Noto Sans TC/HK and Noto Color Emoji) from CDN and registers them as fallbacks before the first render, so mixed-script and CJK text avoid tofu. Set **`loadFigmaDefaultFonts: false`** to disable (e.g. to avoid network or use only embedded fonts). Custom or other Google Fonts are **not** loaded by the renderer; the user is responsible for fetching font bytes and registering them with the canvas if needed. + +## Contributing + +From the package root: -No. The intended surface is **Node.js (TypeScript/JavaScript)**. +1. Install dependencies and build: `pnpm install && pnpm build` +2. Link the package so the `refig` CLI is available: `pnpm link --global` +3. Run the `refig` command from anywhere to test (e.g. `refig ./fixture.json --node "1:1" --format png`) -We intentionally keep the generic renderer in WASM, while the Figma → Grida conversion pipeline stays in JS to avoid shipping a much larger binary and to keep iteration fast. +To unlink: `pnpm unlink --global`. ## License -See the repository [`LICENSE`](../../LICENSE). +See [`LICENSE`](./LICENSE). diff --git a/packages/grida-canvas-sdk-render-figma/__tests__/.gitignore b/packages/grida-canvas-sdk-render-figma/__tests__/.gitignore new file mode 100644 index 0000000000..3018b3a681 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/__tests__/.gitignore @@ -0,0 +1 @@ +.tmp/ diff --git a/packages/grida-canvas-sdk-render-figma/__tests__/figma-default-fonts.test.ts b/packages/grida-canvas-sdk-render-figma/__tests__/figma-default-fonts.test.ts new file mode 100644 index 0000000000..d4196f8d70 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/__tests__/figma-default-fonts.test.ts @@ -0,0 +1,79 @@ +// @vitest-environment node + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + ensureFigmaDefaultFonts, + FIGMA_DEFAULT_FALLBACK_ORDER, + FIGMA_DEFAULT_FONT_ENTRIES, +} from "../figma-default-fonts"; + +describe("figma-default-fonts", () => { + const addFont = vi.fn(); + const setFallbackFonts = vi.fn(); + const mockCanvas = { addFont, setFallbackFonts } as any; + + beforeEach(() => { + addFont.mockClear(); + setFallbackFonts.mockClear(); + vi.stubGlobal( + "fetch", + vi.fn(() => + Promise.resolve({ + ok: true, + arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), + }) + ) + ); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("exports FIGMA_DEFAULT_FONT_ENTRIES with expected families", () => { + const families = FIGMA_DEFAULT_FONT_ENTRIES.map((e) => e.family); + expect(families).toContain("Inter"); + expect(families).toContain("Noto Sans KR"); + expect(families).toContain("Noto Sans JP"); + expect(families).toContain("Noto Sans SC"); + expect(families).toContain("Noto Sans TC"); + expect(families).toContain("Noto Sans HK"); + expect(families).toContain("Noto Color Emoji"); + expect(FIGMA_DEFAULT_FONT_ENTRIES.every((e) => e.url.length > 0)).toBe( + true + ); + }); + + it("exports FIGMA_DEFAULT_FALLBACK_ORDER matching font entries", () => { + expect(FIGMA_DEFAULT_FALLBACK_ORDER).toEqual( + FIGMA_DEFAULT_FONT_ENTRIES.map((e) => e.family) + ); + }); + + it("ensureFigmaDefaultFonts fetches each URL and registers fonts then sets fallback order", async () => { + await ensureFigmaDefaultFonts(mockCanvas); + + expect(addFont).toHaveBeenCalledTimes(FIGMA_DEFAULT_FONT_ENTRIES.length); + for (const entry of FIGMA_DEFAULT_FONT_ENTRIES) { + expect(addFont).toHaveBeenCalledWith( + entry.family, + expect.any(Uint8Array) + ); + } + expect(setFallbackFonts).toHaveBeenCalledTimes(1); + expect(setFallbackFonts).toHaveBeenCalledWith(FIGMA_DEFAULT_FALLBACK_ORDER); + }); + + it("ensureFigmaDefaultFonts throws when fetch fails", async () => { + vi.stubGlobal( + "fetch", + vi.fn(() => + Promise.resolve({ ok: false, status: 404, statusText: "Not Found" }) + ) + ); + + await expect(ensureFigmaDefaultFonts(mockCanvas)).rejects.toThrow( + /Figma default font fetch failed/ + ); + }); +}); diff --git a/packages/grida-canvas-sdk-render-figma/__tests__/refig.cli.smoke.test.ts b/packages/grida-canvas-sdk-render-figma/__tests__/refig.cli.smoke.test.ts new file mode 100644 index 0000000000..4f8da36455 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/__tests__/refig.cli.smoke.test.ts @@ -0,0 +1,102 @@ +/** + * Publish smoke test: packed tarball must run when installed in isolation. + * + * Ensures the package works for npm consumers (no workspace deps at runtime). + * Run via: pnpm test (or vitest run) + */ +import { execSync } from "node:child_process"; +import { + existsSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +/** Minimal Figma REST API document JSON (one frame with a solid fill). */ +const MINIMAL_REST_FIXTURE = { + document: { + id: "0:0", + type: "DOCUMENT", + name: "Smoke Test Doc", + children: [ + { + id: "0:1", + type: "CANVAS", + name: "Page 1", + children: [ + { + id: "1:1", + type: "FRAME", + name: "Frame 1", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 100 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 100 }, + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + size: { x: 100, y: 100 }, + clipsContent: false, + fills: [{ type: "SOLID", color: { r: 0.8, g: 0.2, b: 0.2, a: 1 } }], + strokes: [], + strokeWeight: 0, + effects: [], + children: [], + }, + ], + }, + ], + }, +}; + +describe("refig publish smoke", () => { + it("packed tarball runs when installed in isolation", () => { + const pkgDir = process.cwd(); + execSync("pnpm build", { cwd: pkgDir, stdio: "pipe" }); + + const tmp = mkdtempSync(join(tmpdir(), "refig-pack-")); + try { + execSync(`npm pack --pack-destination "${tmp}"`, { + cwd: pkgDir, + stdio: "pipe", + }); + const tgz = readdirSync(tmp).find((f) => f.endsWith(".tgz")); + if (!tgz) throw new Error("npm pack produced no tarball"); + + writeFileSync( + join(tmp, "package.json"), + JSON.stringify({ + name: "refig-smoke-verify", + private: true, + packageManager: "pnpm@10.24.0", + }) + ); + execSync(`pnpm add "./${tgz}"`, { cwd: tmp, stdio: "pipe" }); + + execSync("npx refig --help", { cwd: tmp, stdio: "pipe" }); + + const fixturePath = join(tmp, "fixture.json"); + writeFileSync(fixturePath, JSON.stringify(MINIMAL_REST_FIXTURE)); + const outPath = join(tmp, "smoke-out.png"); + const refigCli = join(tmp, "node_modules", "@grida", "refig", "dist", "cli.mjs"); + execSync( + `"${process.execPath}" "${refigCli}" fixture.json --node 1:1 --out smoke-out.png --format png`, + { cwd: tmp, stdio: "pipe", timeout: 60_000 } + ); + + expect(existsSync(outPath)).toBe(true); + const bytes = readFileSync(outPath); + expect(bytes[0]).toBe(0x89); + expect(bytes[1]).toBe(0x50); + expect(bytes[2]).toBe(0x4e); + expect(bytes[3]).toBe(0x47); + expect(bytes.byteLength).toBeGreaterThan(100); + } finally { + rmSync(tmp, { recursive: true, force: true }); + } + }, 120_000); +}); diff --git a/packages/grida-canvas-sdk-render-figma/__tests__/refig.cli.test.ts b/packages/grida-canvas-sdk-render-figma/__tests__/refig.cli.test.ts new file mode 100644 index 0000000000..6cf3e2464a --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/__tests__/refig.cli.test.ts @@ -0,0 +1,315 @@ +import { execFileSync } from "node:child_process"; +import { + existsSync, + mkdirSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { join } from "node:path"; +import { unzipSync } from "fflate"; +import { describe, expect, it } from "vitest"; +import { collectExportsFromDocument, figBytesToRestLikeDocument } from "../lib"; + +const TEST_OUTPUT_DIR = join(process.cwd(), "__tests__", ".tmp", "cli"); +const BIN = join(process.cwd(), "cli.ts"); + +/** Minimal Figma REST API document JSON (one frame with a solid fill). */ +const MINIMAL_REST_FIXTURE = { + document: { + id: "0:0", + type: "DOCUMENT", + name: "CLI Test Doc", + children: [ + { + id: "0:1", + type: "CANVAS", + name: "Page 1", + children: [ + { + id: "1:1", + type: "FRAME", + name: "Frame 1", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 100 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 100 }, + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + size: { x: 100, y: 100 }, + clipsContent: false, + fills: [{ type: "SOLID", color: { r: 0.8, g: 0.2, b: 0.2, a: 1 } }], + strokes: [], + strokeWeight: 0, + effects: [], + children: [], + }, + ], + }, + ], + }, +}; + +/** REST fixture with exportSettings for --export-all CLI test. */ +const EXPORT_SETTINGS_FIXTURE = { + document: { + id: "0:0", + type: "DOCUMENT", + name: "Export CLI Doc", + children: [ + { + id: "0:1", + type: "CANVAS", + name: "Page 1", + children: [ + { + id: "41:64", + type: "FRAME", + name: "Export Frame", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 50 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 50 }, + exportSettings: [ + { + suffix: "@2x", + format: "PNG", + constraint: { type: "SCALE", value: 2 }, + }, + { + suffix: "", + format: "SVG", + constraint: { type: "WIDTH", value: 200 }, + }, + ], + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + size: { x: 100, y: 50 }, + clipsContent: false, + fills: [{ type: "SOLID", color: { r: 0, g: 0.5, b: 0.5, a: 1 } }], + strokes: [], + strokeWeight: 0, + effects: [], + children: [], + }, + ], + }, + ], + }, +}; + +function resetOutputDir() { + rmSync(TEST_OUTPUT_DIR, { recursive: true, force: true }); + mkdirSync(TEST_OUTPUT_DIR, { recursive: true }); +} + +function writeFixture(content: object = MINIMAL_REST_FIXTURE) { + const fixturePath = join(TEST_OUTPUT_DIR, "fixture.json"); + writeFileSync(fixturePath, JSON.stringify(content)); + return fixturePath; +} + +/** Extract REST archive zip to a directory. Returns path to dir with document.json. */ +function extractArchiveFixture(zipPath: string): string { + const zipBytes = readFileSync(zipPath); + const unzipped = unzipSync(new Uint8Array(zipBytes)); + const extractDir = join(TEST_OUTPUT_DIR, "archive-fixture"); + rmSync(extractDir, { recursive: true, force: true }); + mkdirSync(extractDir, { recursive: true }); + + for (const [entryPath, data] of Object.entries(unzipped)) { + if (entryPath.startsWith("__MACOSX/") || entryPath.endsWith("/")) continue; + const fullPath = join(extractDir, entryPath); + mkdirSync(join(fullPath, ".."), { recursive: true }); + writeFileSync(fullPath, data); + } + + const innerDir = Object.keys(unzipped) + .find((k) => k.endsWith("/document.json")) + ?.replace("/document.json", ""); + if (!innerDir) throw new Error("No document.json in archive"); + return join(extractDir, innerDir); +} + +describe("refig CLI", () => { + it("writes a valid PNG output file", () => { + resetOutputDir(); + const fixturePath = writeFixture(); + const out = join(TEST_OUTPUT_DIR, "cli-out.png"); + + execFileSync( + process.execPath, + [ + "--import", + "tsx", + BIN, + fixturePath, + "--node", + "1:1", + "--out", + out, + "--format", + "png", + ], + { stdio: "pipe", timeout: 60_000 } + ); + + expect(existsSync(out)).toBe(true); + const bytes = readFileSync(out); + // PNG magic bytes + expect(bytes[0]).toBe(0x89); + expect(bytes[1]).toBe(0x50); + expect(bytes[2]).toBe(0x4e); + expect(bytes[3]).toBe(0x47); + expect(bytes.byteLength).toBeGreaterThan(100); + }, 60_000); + + it("infers SVG format from output extension", () => { + resetOutputDir(); + const fixturePath = writeFixture(); + const out = join(TEST_OUTPUT_DIR, "cli-inferred.svg"); + + execFileSync( + process.execPath, + ["--import", "tsx", BIN, fixturePath, "--node", "1:1", "--out", out], + { stdio: "pipe", timeout: 60_000 } + ); + + expect(existsSync(out)).toBe(true); + const content = readFileSync(out, "utf8"); + expect(content).toContain(" { + resetOutputDir(); + const fixturePath = writeFixture(EXPORT_SETTINGS_FIXTURE); + const outDir = join(TEST_OUTPUT_DIR, "export-all-out"); + + execFileSync( + process.execPath, + ["--import", "tsx", BIN, fixturePath, "--export-all", "--out", outDir], + { stdio: "pipe", timeout: 90_000 } + ); + + expect(existsSync(outDir)).toBe(true); + const files = readdirSync(outDir); + expect(files.length).toBe(2); + + const pngFile = files.find((f) => f.endsWith(".png")); + const svgFile = files.find((f) => f.endsWith(".svg")); + expect(pngFile).toBeTruthy(); + expect(svgFile).toBeTruthy(); + + const pngBytes = readFileSync(join(outDir, pngFile!)); + expect(pngBytes[0]).toBe(0x89); + expect(pngBytes[1]).toBe(0x50); + expect(pngBytes[2]).toBe(0x4e); + expect(pngBytes[3]).toBe(0x47); + + const svgContent = readFileSync(join(outDir, svgFile!), "utf8"); + expect(svgContent).toContain(" { + resetOutputDir(); + const zipPath = join( + process.cwd(), + "../../fixtures/test-figma/community/1510053249065427020-workos-radix-icons.zip" + ); + if (!existsSync(zipPath)) { + console.warn(`Skipping: fixture not found at ${zipPath}`); + return; + } + const archiveDir = extractArchiveFixture(zipPath); + const outDir = join(TEST_OUTPUT_DIR, "export-all-archive-out"); + + execFileSync( + process.execPath, + ["--import", "tsx", BIN, archiveDir, "--export-all", "--out", outDir], + { stdio: "pipe", timeout: 120_000 } + ); + + expect(existsSync(outDir)).toBe(true); + const files = readdirSync(outDir); + expect(files.length).toBeGreaterThan(1); + + const pngFiles = files.filter((f) => f.endsWith(".png")); + expect(pngFiles.length).toBeGreaterThan(0); + const samplePng = join(outDir, pngFiles[0]!); + const pngBytes = readFileSync(samplePng); + expect(pngBytes[0]).toBe(0x89); + expect(pngBytes[1]).toBe(0x50); + expect(pngBytes[2]).toBe(0x4e); + expect(pngBytes[3]).toBe(0x47); + }, 120_000); + + it("renders single node from .fig file", () => { + resetOutputDir(); + const figPath = join( + process.cwd(), + "../../fixtures/test-fig/community/1510053249065427020-workos-radix-icons.fig" + ); + if (!existsSync(figPath)) { + console.warn(`Skipping: fixture not found at ${figPath}`); + return; + } + const bytes = new Uint8Array(readFileSync(figPath)); + const restDoc = figBytesToRestLikeDocument(bytes); + const items = collectExportsFromDocument( + restDoc as Record + ); + if (items.length === 0) { + console.warn("Skipping: fixture has no nodes with exportSettings"); + return; + } + const nodeId = items[0]!.nodeId; + const outPath = join(TEST_OUTPUT_DIR, "fig-single.png"); + + execFileSync( + process.execPath, + ["--import", "tsx", BIN, figPath, "--node", nodeId, "--out", outPath], + { stdio: "pipe", timeout: 90_000 } + ); + + expect(existsSync(outPath)).toBe(true); + const pngBytes = readFileSync(outPath); + expect(pngBytes[0]).toBe(0x89); + expect(pngBytes[1]).toBe(0x50); + expect(pngBytes[2]).toBe(0x4e); + expect(pngBytes[3]).toBe(0x47); + expect(pngBytes.byteLength).toBeGreaterThan(100); + }, 90_000); + + it.skip("--export-all on .fig file exports all nodes with exportSettings", () => { + resetOutputDir(); + const figPath = join( + process.cwd(), + "../../fixtures/test-fig/community/1510053249065427020-workos-radix-icons.fig" + ); + if (!existsSync(figPath)) { + console.warn(`Skipping: fixture not found at ${figPath}`); + return; + } + const outDir = join(TEST_OUTPUT_DIR, "export-all-fig-out"); + + execFileSync( + process.execPath, + ["--import", "tsx", BIN, figPath, "--export-all", "--out", outDir], + { stdio: "pipe", timeout: 300_000 } + ); + + expect(existsSync(outDir)).toBe(true); + const files = readdirSync(outDir); + expect(files.length).toBeGreaterThan(1); + + const pngFiles = files.filter((f) => f.endsWith(".png")); + expect(pngFiles.length).toBeGreaterThan(0); + const samplePng = join(outDir, pngFiles[0]!); + const pngBytes = readFileSync(samplePng); + expect(pngBytes[0]).toBe(0x89); + expect(pngBytes[1]).toBe(0x50); + expect(pngBytes[2]).toBe(0x4e); + expect(pngBytes[3]).toBe(0x47); + }, 300_000); +}); diff --git a/packages/grida-canvas-sdk-render-figma/__tests__/refig.test.ts b/packages/grida-canvas-sdk-render-figma/__tests__/refig.test.ts new file mode 100644 index 0000000000..d2f8e58997 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/__tests__/refig.test.ts @@ -0,0 +1,427 @@ +// @vitest-environment node + +import { + existsSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { unzipSync } from "fflate"; +import { join } from "node:path"; +import { describe, expect, it, beforeAll } from "vitest"; +import { + FigmaDocument, + FigmaRenderer, + collectExportsFromDocument, + exportSettingToRenderOptions, +} from "../index"; +import { figBytesToRestLikeDocument } from "../lib"; + +// --------------------------------------------------------------------------- +// Binary signatures for output validation +// --------------------------------------------------------------------------- + +const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; + +function expectPng(data: Uint8Array) { + expect(Array.from(data.slice(0, 8))).toEqual(PNG_SIGNATURE); + expect(data.byteLength).toBeGreaterThan(100); +} + +function expectSvg(data: Uint8Array) { + const text = new TextDecoder().decode(data); + expect(text).toContain("Grida conversion accepts. +// It has one page with a single FRAME containing a solid fill rectangle. +// --------------------------------------------------------------------------- + +const MINIMAL_REST_FIXTURE = { + document: { + id: "0:0", + type: "DOCUMENT", + name: "Test Doc", + children: [ + { + id: "0:1", + type: "CANVAS", + name: "Page 1", + children: [ + { + id: "1:1", + type: "FRAME", + name: "Frame 1", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 100 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 100 }, + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + size: { x: 100, y: 100 }, + clipsContent: false, + fills: [{ type: "SOLID", color: { r: 0.8, g: 0.2, b: 0.2, a: 1 } }], + strokes: [], + strokeWeight: 0, + effects: [], + children: [], + }, + ], + }, + ], + }, +}; + +/** REST fixture with exportSettings on the frame (Figma HasExportSettingsTrait). */ +const REST_FIXTURE_WITH_EXPORT_SETTINGS = { + document: { + id: "0:0", + type: "DOCUMENT", + name: "Export Test Doc", + children: [ + { + id: "0:1", + type: "CANVAS", + name: "Page 1", + children: [ + { + id: "41:64", + type: "FRAME", + name: "Export Frame", + absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 50 }, + absoluteRenderBounds: { x: 0, y: 0, width: 100, height: 50 }, + exportSettings: [ + { + suffix: "@2x", + format: "PNG", + constraint: { type: "SCALE", value: 2 }, + }, + { + suffix: "", + format: "SVG", + constraint: { type: "WIDTH", value: 200 }, + }, + ], + relativeTransform: [ + [1, 0, 0], + [0, 1, 0], + ], + size: { x: 100, y: 50 }, + clipsContent: false, + fills: [{ type: "SOLID", color: { r: 0, g: 0.5, b: 0.5, a: 1 } }], + strokes: [], + strokeWeight: 0, + effects: [], + children: [], + }, + ], + }, + ], + }, +}; + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("collectExportsFromDocument", () => { + it("returns empty array when document has no pages", () => { + const items = collectExportsFromDocument({ + document: { children: [] }, + } as any); + expect(items).toEqual([]); + }); + + it("returns empty array when no node has exportSettings", () => { + const items = collectExportsFromDocument(MINIMAL_REST_FIXTURE as any); + expect(items).toEqual([]); + }); + + it("returns one item per (node, setting) for nodes with exportSettings", () => { + const items = collectExportsFromDocument( + REST_FIXTURE_WITH_EXPORT_SETTINGS as any + ); + expect(items).toHaveLength(2); + expect(items[0].nodeId).toBe("41:64"); + expect(items[0].setting.format).toBe("PNG"); + expect(items[0].setting.constraint.type).toBe("SCALE"); + expect(items[0].setting.suffix).toBe("@2x"); + expect(items[1].nodeId).toBe("41:64"); + expect(items[1].setting.format).toBe("SVG"); + expect(items[1].setting.constraint.type).toBe("WIDTH"); + expect(items[1].setting.constraint.value).toBe(200); + }); + + it("collects exportSettings from REST doc built from .fig via figBytesToRestLikeDocument", () => { + const figPath = join( + process.cwd(), + "../../fixtures/test-fig/community/1510053249065427020-workos-radix-icons.fig" + ); + if (!existsSync(figPath)) { + console.warn(`Skipping: fixture not found at ${figPath}`); + return; + } + const bytes = new Uint8Array(readFileSync(figPath)); + const restDoc = figBytesToRestLikeDocument(bytes); + const items = collectExportsFromDocument(restDoc as any); + expect(items.length).toBeGreaterThan(0); + expect(items[0]).toHaveProperty("nodeId"); + expect(items[0]).toHaveProperty("setting"); + expect(items[0].setting).toHaveProperty("format"); + expect(items[0].setting).toHaveProperty("constraint"); + }); +}); + +describe("exportSettingToRenderOptions", () => { + it("maps SCALE constraint to scale and format", () => { + const node = { absoluteBoundingBox: { width: 100, height: 50 } }; + const setting = { + suffix: "@2x", + format: "PNG" as const, + constraint: { type: "SCALE" as const, value: 2 }, + }; + const opts = exportSettingToRenderOptions(node as any, setting); + expect(opts.format).toBe("png"); + expect(opts.scale).toBe(2); + }); + + it("maps WIDTH constraint to width and height from node bounds", () => { + const node = { absoluteBoundingBox: { width: 100, height: 50 } }; + const setting = { + suffix: "", + format: "SVG" as const, + constraint: { type: "WIDTH" as const, value: 200 }, + }; + const opts = exportSettingToRenderOptions(node as any, setting); + expect(opts.format).toBe("svg"); + expect(opts.width).toBe(200); + expect(opts.height).toBe(100); // 200 * (50/100) + }); + + it("maps HEIGHT constraint to height and width from node bounds", () => { + const node = { absoluteBoundingBox: { width: 100, height: 50 } }; + const setting = { + suffix: "", + format: "PDF" as const, + constraint: { type: "HEIGHT" as const, value: 100 }, + }; + const opts = exportSettingToRenderOptions(node as any, setting); + expect(opts.format).toBe("pdf"); + expect(opts.height).toBe(100); + expect(opts.width).toBe(200); // 100 * (100/50) + }); +}); + +describe("@grida/refig (real render)", () => { + beforeAll(() => { + rmSync(TEST_OUTPUT_DIR, { recursive: true, force: true }); + mkdirSync(TEST_OUTPUT_DIR, { recursive: true }); + }); + + it("creates a FigmaDocument from REST API JSON", () => { + const doc = new FigmaDocument(MINIMAL_REST_FIXTURE); + expect(doc.sourceType).toBe("rest-api-json"); + }); + + it("FigmaDocument.fromFile reads a JSON file from disk", () => { + const fixturePath = join(TEST_OUTPUT_DIR, "fromfile-fixture.json"); + writeFileSync(fixturePath, JSON.stringify(MINIMAL_REST_FIXTURE)); + + const doc = FigmaDocument.fromFile(fixturePath); + expect(doc.sourceType).toBe("rest-api-json"); + }); + + it("renders a REST JSON document as PNG with valid signature", async () => { + const renderer = new FigmaRenderer(MINIMAL_REST_FIXTURE, { + loadFigmaDefaultFonts: false, + }); + + try { + const result = await renderer.render("1:1", { + format: "png", + width: 256, + height: 256, + }); + + expect(result.mimeType).toBe("image/png"); + expect(result.width).toBe(256); + expect(result.height).toBe(256); + expectPng(result.data); + + const outPath = join(TEST_OUTPUT_DIR, "rest-frame.png"); + writeFileSync(outPath, Buffer.from(result.data)); + } finally { + renderer.dispose(); + } + }, 30_000); + + it("renders a REST JSON document as SVG", async () => { + const renderer = new FigmaRenderer(MINIMAL_REST_FIXTURE, { + loadFigmaDefaultFonts: false, + }); + + try { + const result = await renderer.render("1:1", { + format: "svg", + width: 256, + height: 256, + }); + + expect(result.mimeType).toBe("image/svg+xml"); + expectSvg(result.data); + + const outPath = join(TEST_OUTPUT_DIR, "rest-frame.svg"); + writeFileSync(outPath, Buffer.from(result.data)); + } finally { + renderer.dispose(); + } + }, 30_000); + + it("renders a REST JSON document as PDF", async () => { + const renderer = new FigmaRenderer(MINIMAL_REST_FIXTURE, { + loadFigmaDefaultFonts: false, + }); + + try { + const result = await renderer.render("1:1", { + format: "pdf", + width: 256, + height: 256, + }); + + expect(result.mimeType).toBe("application/pdf"); + expectPdf(result.data); + + const outPath = join(TEST_OUTPUT_DIR, "rest-frame.pdf"); + writeFileSync(outPath, Buffer.from(result.data)); + } finally { + renderer.dispose(); + } + }, 30_000); + + it("renders a REST JSON document as JPEG", async () => { + const renderer = new FigmaRenderer(MINIMAL_REST_FIXTURE, { + loadFigmaDefaultFonts: false, + }); + + try { + const result = await renderer.render("1:1", { + format: "jpeg", + width: 256, + height: 256, + }); + + expect(result.mimeType).toBe("image/jpeg"); + expectJpegOrFallbackImage(result.data); + + const outPath = join(TEST_OUTPUT_DIR, "rest-frame.jpeg"); + writeFileSync(outPath, Buffer.from(result.data)); + } finally { + renderer.dispose(); + } + }, 30_000); + + it("renders with scale option", async () => { + const renderer = new FigmaRenderer(MINIMAL_REST_FIXTURE, { + loadFigmaDefaultFonts: false, + }); + + try { + const result = await renderer.render("1:1", { + format: "png", + width: 256, + height: 256, + scale: 2, + }); + + expectPng(result.data); + + const outPath = join(TEST_OUTPUT_DIR, "scaled-2x.png"); + writeFileSync(outPath, Buffer.from(result.data)); + } finally { + renderer.dispose(); + } + }, 30_000); + + it("renders REST document with custom IMAGE fill from fixture archive", async () => { + const zipPath = join( + __dirname, + "../../../fixtures/test-figma/community/1510053249065427020-workos-radix-icons.zip" + ); + const zipBytes = readFileSync(zipPath); + const unzipped = unzipSync(new Uint8Array(zipBytes)); + + const docEntry = Object.keys(unzipped).find((k) => + k.endsWith("/document.json") + ); + if (!docEntry) throw new Error("No document.json in archive"); + const document = JSON.parse( + new TextDecoder().decode(unzipped[docEntry]) + ) as Record; + + const images: Record = {}; + for (const path of Object.keys(unzipped)) { + const match = path.match(/\/images\/([^/]+)$/); + if (!match) continue; + const filename = match[1]; + const ref = filename.replace(/\.[^.]+$/, ""); + if (!ref) continue; + images[ref] = unzipped[path]; + } + + const items = collectExportsFromDocument(document); + const nodeId = items.length > 0 ? items[0].nodeId : null; + if (!nodeId) throw new Error("Fixture has no nodes with exportSettings"); + + const renderer = new FigmaRenderer(document, { + images, + loadFigmaDefaultFonts: false, + }); + + try { + const result = await renderer.render(nodeId, { + format: "png", + width: 512, + height: 512, + }); + + expectPng(result.data); + expect(result.width).toBe(512); + expect(result.height).toBe(512); + + const outPath = join(TEST_OUTPUT_DIR, "rest-image-fill-from-archive.png"); + writeFileSync(outPath, Buffer.from(result.data)); + } finally { + renderer.dispose(); + } + }, 60_000); +}); diff --git a/packages/grida-canvas-sdk-render-figma/browser.ts b/packages/grida-canvas-sdk-render-figma/browser.ts new file mode 100644 index 0000000000..8f41ed2c8e --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/browser.ts @@ -0,0 +1,21 @@ +/** + * @grida/refig/browser — Browser entrypoint + * + * Same core API as @grida/refig, without any Node.js dependencies (no node:fs). + * Use this when bundling for the browser. + */ + +export { + FigmaDocument, + FigmaRenderer, + collectExportsFromDocument, + exportSettingToRenderOptions, + resolveMimeType, + type ExportItem, + type RefigRenderFormat, + type RefigRendererOptions, + type RefigRenderOptions, + type RefigRenderResult, +} from "./lib"; + +export { FigmaRenderer as default } from "./lib"; diff --git a/packages/grida-canvas-sdk-render-figma/cli.ts b/packages/grida-canvas-sdk-render-figma/cli.ts new file mode 100644 index 0000000000..7e1dbb5845 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/cli.ts @@ -0,0 +1,385 @@ +#!/usr/bin/env node +/** + * refig CLI — headless Figma renderer + */ + +import { + readFileSync, + readdirSync, + mkdirSync, + writeFileSync, + existsSync, + statSync, + mkdtempSync, +} from "node:fs"; +import path from "node:path"; +import { tmpdir } from "node:os"; +import { program } from "commander"; +import { + FigmaDocument, + FigmaRenderer, + collectExportsFromDocument, + exportSettingToRenderOptions, + figFileToRestLikeDocument, + type ExportItem, + type RefigRenderFormat, +} from "./lib"; +import { iofigma } from "@grida/io-figma"; + +const FORMAT_SET = new Set(["png", "jpeg", "webp", "pdf", "svg"]); + +/** Conventional name for REST API response when using a project directory. */ +const DOCUMENT_JSON = "document.json"; +/** Subdirectory name for images when using a project directory. */ +const IMAGES_SUBDIR = "images"; + +function formatFromOutFile(outPath: string): string { + const ext = path.extname(outPath).replace(/^\./, "").toLowerCase(); + if (ext === "jpg") return "jpeg"; + return FORMAT_SET.has(ext) ? ext : "png"; +} + +const EXT_BY_FORMAT: Record = { + png: "png", + jpeg: "jpeg", + jpg: "jpeg", + webp: "webp", + svg: "svg", + pdf: "pdf", +}; + +/** Sanitize for use in filenames: replace : / \ with _. */ +function sanitizeForFilename(s: string): string { + return ( + String(s) + .replace(/[:/\\]/g, "_") + .replace(/\s+/g, "_") || "_" + ); +} + +/** + * Resolve CLI input to document path and optional images directory. + * - If input is a directory: document must be at /document.json; images at /images/ if present. + * - If input is a file: document is that file; images only if --images is provided. + */ +function resolveInput( + inputPath: string, + explicitImagesDir: string | undefined +): { + documentPath: string; + imagesDir: string | undefined; + /** True when document is REST API JSON (file path ending in .json or directory with document.json). */ + isRestJson: boolean; +} { + const resolved = path.resolve(inputPath); + const stat = statSync(resolved); + + if (stat.isDirectory()) { + const documentPath = path.join(resolved, DOCUMENT_JSON); + if (!existsSync(documentPath)) { + throw new Error( + `Input directory must contain ${DOCUMENT_JSON}; not found: ${documentPath}` + ); + } + const imagesDir = path.join(resolved, IMAGES_SUBDIR); + const useImagesDir = + existsSync(imagesDir) && statSync(imagesDir).isDirectory() + ? imagesDir + : undefined; + return { + documentPath, + imagesDir: explicitImagesDir + ? path.resolve(explicitImagesDir) + : useImagesDir, + isRestJson: true, + }; + } + + return { + documentPath: resolved, + imagesDir: explicitImagesDir ? path.resolve(explicitImagesDir) : undefined, + isRestJson: resolved.toLowerCase().endsWith(".json"), + }; +} + +/** + * Read image files from a directory. + * Uses filename (without extension) as ref for lookup. + * @returns Record of ref -> image bytes + */ +function readImagesFromDir(dirPath: string): Record { + const out: Record = {}; + for (const file of readdirSync(dirPath)) { + const fullPath = path.join(dirPath, file); + if (!statSync(fullPath).isFile()) continue; + const ref = path.basename(file).replace(/\.[^.]+$/, ""); + if (!ref) continue; + const buf = readFileSync(fullPath); + out[ref] = new Uint8Array(buf); + } + return out; +} + +function exportAllOutputBasename( + nodeId: string, + suffix: string, + format: string +): string { + const ext = EXT_BY_FORMAT[format] ?? "png"; + const safeId = sanitizeForFilename(nodeId); + const safeSuffix = sanitizeForFilename(suffix); + const name = safeSuffix ? `${safeId}_${safeSuffix}` : safeId; + return `${name}.${ext}`; +} + +async function runExportAll( + documentPath: string, + outDir: string, + imagesDir?: string, + skipDefaultFonts?: boolean +): Promise { + const isFig = documentPath.toLowerCase().endsWith(".fig"); + let document: FigmaDocument; + let items: ExportItem[]; + let rendererOptions: { + images?: Record; + loadFigmaDefaultFonts?: boolean; + } = {}; + + if (isFig) { + const bytes = new Uint8Array(readFileSync(documentPath)); + const figFile = iofigma.kiwi.parseFile(bytes); + const restDoc = figFileToRestLikeDocument(figFile); + items = collectExportsFromDocument(restDoc as Record); + document = new FigmaDocument(bytes); + const imagesMap = iofigma.kiwi.extractImages(figFile.zip_files); + const images: Record = {}; + imagesMap.forEach((imgBytes, ref) => { + images[ref] = imgBytes; + }); + if (Object.keys(images).length > 0) { + rendererOptions = { images }; + } + } else { + const json = JSON.parse(readFileSync(documentPath, "utf8")); + document = new FigmaDocument(json); + items = collectExportsFromDocument( + document.payload as Record + ); + if (imagesDir) { + rendererOptions = { images: readImagesFromDir(imagesDir) }; + } + } + if (skipDefaultFonts || process.env.REFIG_SKIP_DEFAULT_FONTS === "1") { + rendererOptions = { ...rendererOptions, loadFigmaDefaultFonts: false }; + } + + if (items.length === 0) { + process.stdout.write("No nodes with export settings found.\n"); + return; + } + + const renderer = new FigmaRenderer(document, rendererOptions); + try { + for (const { nodeId: nid, node, setting } of items) { + const options = exportSettingToRenderOptions(node, setting); + const result = await renderer.render(nid, options); + const basename = exportAllOutputBasename( + nid, + setting.suffix, + result.format + ); + const filePath = path.join(outDir, basename); + writeFileSync(filePath, Buffer.from(result.data)); + process.stdout.write( + `wrote ${filePath} (${result.mimeType}, ${result.data.byteLength} bytes)\n` + ); + } + process.stdout.write(`Exported ${items.length} file(s) to ${outDir}\n`); + } finally { + renderer.dispose(); + } +} + +async function runSingleNode( + documentPath: string, + nodeId: string, + outPath: string, + opts: { + format?: string; + width: number; + height: number; + scale: number; + imagesDir?: string; + skipDefaultFonts?: boolean; + } +): Promise { + const format = (opts.format ?? formatFromOutFile(outPath)).toLowerCase(); + if (!FORMAT_SET.has(format)) { + throw new Error(`Unsupported --format "${format}"`); + } + + const isJson = documentPath.toLowerCase().endsWith(".json"); + const document = isJson + ? new FigmaDocument(JSON.parse(readFileSync(documentPath, "utf8"))) + : new FigmaDocument(new Uint8Array(readFileSync(documentPath))); + + const rendererOptions: { + images?: Record; + loadFigmaDefaultFonts?: boolean; + } = + isJson && opts.imagesDir + ? { images: readImagesFromDir(opts.imagesDir) } + : {}; + if (opts.skipDefaultFonts || process.env.REFIG_SKIP_DEFAULT_FONTS === "1") { + rendererOptions.loadFigmaDefaultFonts = false; + } + const renderer = new FigmaRenderer(document, rendererOptions); + try { + const result = await renderer.render(nodeId, { + format: format as RefigRenderFormat, + width: opts.width, + height: opts.height, + scale: opts.scale, + }); + mkdirSync(path.dirname(outPath), { recursive: true }); + writeFileSync(outPath, Buffer.from(result.data)); + process.stdout.write( + `wrote ${outPath} (${result.mimeType}, ${result.data.byteLength} bytes)\n` + ); + } finally { + renderer.dispose(); + } +} + +async function main(): Promise { + program + .name("refig") + .description( + "Headless Figma renderer — render .fig and REST API JSON to PNG/JPEG/WebP/PDF/SVG" + ) + .argument( + "", + "Path to .fig, JSON file (REST API response), or directory containing document.json (and optionally images/)" + ) + .option( + "--out ", + "Output file path (single node) or output directory (--export-all); when omitted, uses OS temp directory (valid with --export-all or with both --format and --node)" + ) + .option( + "--images ", + "Directory of image files for REST API document (optional; not used if is a dir with images/)" + ) + .option( + "--node ", + "Figma node ID to render (required unless --export-all)" + ) + .option( + "--export-all", + "Export every node that has exportSettings (REST JSON only)" + ) + .option( + "--format ", + "png | jpeg | webp | pdf | svg (single-node only; default: from --out extension)" + ) + .option("--width ", "Viewport width (single-node only)", "1024") + .option("--height ", "Viewport height (single-node only)", "1024") + .option("--scale ", "Raster scale factor (single-node only)", "1") + .option( + "--skip-default-fonts", + "Do not load Figma default fonts (same as REFIG_SKIP_DEFAULT_FONTS=1)" + ) + .action( + async ( + input: string, + options: Record + ) => { + const outPath = String(options.out ?? "").trim(); + const exportAll = options.exportAll === true; + const nodeId = String(options.node ?? "").trim(); + const explicitImagesDir = + typeof options.images === "string" ? options.images : undefined; + + const { documentPath, imagesDir, isRestJson } = resolveInput( + input.trim(), + explicitImagesDir + ); + + if (exportAll) { + if (nodeId) { + program.error("--node must not be used with --export-all"); + } + const outDir = outPath + ? (() => { + const resolved = path.resolve(outPath); + if (existsSync(resolved)) { + const stat = statSync(resolved); + if (!stat.isDirectory()) { + program.error( + "--out must be a directory when using --export-all" + ); + } + } else { + mkdirSync(resolved, { recursive: true }); + } + return resolved; + })() + : mkdtempSync(path.join(tmpdir(), "refig-export-")); + await runExportAll( + documentPath, + outDir, + imagesDir, + options.skipDefaultFonts === true + ); + return; + } + + if (!nodeId) { + program.error("--node is required (or use --export-all)"); + } + + const width = Number(options.width ?? 1024); + const height = Number(options.height ?? 1024); + const scale = Number(options.scale ?? 1); + const formatOption = + typeof options.format === "string" ? options.format : undefined; + + let singleOutPath: string; + if (outPath) { + singleOutPath = path.resolve(outPath); + } else { + if (!formatOption) { + program.error( + "When --out is omitted, both --node and --format are required" + ); + } + const format = formatOption.toLowerCase(); + if (!FORMAT_SET.has(format)) { + program.error(`Unsupported --format "${format}"`); + } + const ext = EXT_BY_FORMAT[format] ?? "png"; + singleOutPath = path.join( + tmpdir(), + `refig-${sanitizeForFilename(nodeId)}.${ext}` + ); + } + + await runSingleNode(documentPath, nodeId, singleOutPath, { + format: formatOption, + width, + height, + scale, + imagesDir, + skipDefaultFonts: options.skipDefaultFonts === true, + }); + } + ); + + await program.parseAsync(process.argv); +} + +main().catch((err: unknown) => { + const message = err instanceof Error ? err.message : String(err); + process.stderr.write(`${message}\n`); + process.exit(1); +}); diff --git a/packages/grida-canvas-sdk-render-figma/figma-default-fonts.ts b/packages/grida-canvas-sdk-render-figma/figma-default-fonts.ts new file mode 100644 index 0000000000..4945224018 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/figma-default-fonts.ts @@ -0,0 +1,82 @@ +/** + * Figma default fonts — minimal built-in module (no new dependencies). + * Loads the Figma default font set from CDN and registers them with the canvas. + * See FONTS.md for strategy. Custom fonts are the user's responsibility. + */ + +/** Minimal canvas interface required for font registration and fallback. */ +export interface FigmaDefaultFontsCanvas { + addFont(family: string, data: Uint8Array): void; + setFallbackFonts(fonts: string[]): void; +} + +/** + * One URL per family. These may need updating if the CDN changes. + * Using single-file variable or regular TTF where available. + * + * @see https://github.com/gridaco/fonts/blob/main/www/public/webfonts-vf.json + */ +export const FIGMA_DEFAULT_FONT_ENTRIES: { family: string; url: string }[] = [ + { + family: "Inter", + url: "https://fonts.gstatic.com/s/inter/v19/UcCo3FwrK3iLTfvlaQc78lA2.ttf", + }, + { + family: "Noto Sans KR", + url: "https://fonts.gstatic.com/s/notosanskr/v37/PbykFmXiEBPT4ITbgNA5Cgm21nTs4JMMuA.ttf", + }, + { + family: "Noto Sans JP", + url: "https://fonts.gstatic.com/s/notosansjp/v54/-F62fjtqLzI2JPCgQBnw7HFoxgIO2lZ9hg.ttf", + }, + { + family: "Noto Sans SC", + url: "https://fonts.gstatic.com/s/notosanssc/v38/k3kXo84MPvpLmixcA63oeALhKYiJ-Q7m8w.ttf", + }, + { + family: "Noto Sans TC", + url: "https://fonts.gstatic.com/s/notosanstc/v37/-nF7OG829Oofr2wohFbTp9iFPysLA_ZJ1g.ttf", + }, + { + family: "Noto Sans HK", + url: "https://fonts.gstatic.com/s/notosanshk/v33/nKKQ-GM_FYFRJvXzVXaAPe9hNHB3Eu7mOQ.ttf", + }, + { + family: "Noto Color Emoji", + url: "https://fonts.gstatic.com/s/notocoloremoji/v35/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFab5s79iz64w.ttf", + }, +]; + +/** + * Fallback order for setFallbackFonts. Aligned with FONTS.md and editor DEFAULT_FONT_FALLBACK_SET. + */ +export const FIGMA_DEFAULT_FALLBACK_ORDER: string[] = [ + "Inter", + "Noto Sans KR", + "Noto Sans JP", + "Noto Sans SC", + "Noto Sans TC", + "Noto Sans HK", + "Noto Color Emoji", +]; + +/** + * Ensures all Figma default fonts are loaded from CDN and registered with the canvas, + * then sets the fallback font order. Call once after createCanvas when loadFigmaDefaultFonts is true. + * Uses native fetch only (Node 18+ and browsers). + */ +export async function ensureFigmaDefaultFonts( + canvas: FigmaDefaultFontsCanvas +): Promise { + for (const entry of FIGMA_DEFAULT_FONT_ENTRIES) { + const res = await fetch(entry.url); + if (!res.ok) { + throw new Error( + `Figma default font fetch failed: ${entry.family} ${res.status} ${res.statusText}` + ); + } + const buffer = await res.arrayBuffer(); + canvas.addFont(entry.family, new Uint8Array(buffer)); + } + canvas.setFallbackFonts(FIGMA_DEFAULT_FALLBACK_ORDER); +} diff --git a/packages/grida-canvas-sdk-render-figma/index.ts b/packages/grida-canvas-sdk-render-figma/index.ts new file mode 100644 index 0000000000..80e289e16a --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/index.ts @@ -0,0 +1,62 @@ +/** + * @grida/refig — Node.js entrypoint + * + * Re-exports the full core API and adds Node-specific helpers (file I/O). + */ + +import { readFileSync } from "node:fs"; +import { FigmaDocument, FigmaRenderer } from "./lib"; + +// --------------------------------------------------------------------------- +// Node-specific: FigmaDocument.fromFile +// --------------------------------------------------------------------------- + +/** + * Read a `.fig` binary or a Figma REST API JSON file from disk and return a + * `FigmaDocument`. + * + * @param filePath Path to a `.fig` file or a `.json` file. + */ +FigmaDocument.fromFile = function fromFile(filePath: string): FigmaDocument { + const normalized = filePath.trim(); + if (!normalized) { + throw new Error("FigmaDocument.fromFile: path must be non-empty"); + } + + if (normalized.toLowerCase().endsWith(".json")) { + const text = readFileSync(normalized, "utf-8"); + return new FigmaDocument(JSON.parse(text)); + } + + return new FigmaDocument(new Uint8Array(readFileSync(normalized))); +}; + +// --------------------------------------------------------------------------- +// Re-export everything from core +// --------------------------------------------------------------------------- + +export { + FigmaDocument, + FigmaRenderer, + collectExportsFromDocument, + exportSettingToRenderOptions, + resolveMimeType, + type ExportItem, + type RefigRenderFormat, + type RefigRendererOptions, + type RefigRenderOptions, + type RefigRenderResult, +} from "./lib"; + +export default FigmaRenderer; + +// --------------------------------------------------------------------------- +// Type augmentation: FigmaDocument.fromFile (Node only) +// --------------------------------------------------------------------------- + +declare module "./lib" { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace FigmaDocument { + function fromFile(filePath: string): FigmaDocument; + } +} diff --git a/packages/grida-canvas-sdk-render-figma/lib.ts b/packages/grida-canvas-sdk-render-figma/lib.ts new file mode 100644 index 0000000000..26ed18b529 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/lib.ts @@ -0,0 +1,722 @@ +/** + * @grida/refig — shared core (no node:fs, no DOM) + * + * This module is environment-agnostic. Both the Node and browser entrypoints + * re-export everything from here. + */ + +import type { ExportSetting } from "@figma/rest-api-spec"; +import { createCanvas, type Canvas, type types } from "@grida/canvas-wasm"; +import { iofigma } from "@grida/io-figma"; +import grida from "@grida/schema"; +import { + ensureFigmaDefaultFonts, + type FigmaDefaultFontsCanvas, +} from "./figma-default-fonts"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export type RefigRenderFormat = "png" | "jpeg" | "webp" | "pdf" | "svg"; + +export interface RefigRendererOptions { + /** + * When enabled, the renderer loads the embedded default fonts. + * @default true + */ + useEmbeddedFonts?: boolean; + + /** + * When true (default), the renderer ensures Figma default fonts (Inter, Noto Sans KR/JP/SC, etc.) + * are loaded from CDN and registered with the canvas before any scene is loaded. + * Reduces tofu for mixed-script and CJK text. Set to false to skip (e.g. to avoid network or use only embedded fonts). + * Custom fonts remain the user's responsibility. + * @default true + */ + loadFigmaDefaultFonts?: boolean; + + /** + * Map of Figma image ref (hash) to image bytes. + * Used for REST API and .fig input so IMAGE fills render correctly. + * Ref must match document references (e.g. 40-char hex for .fig, Figma image fill hash for REST). + */ + images?: Record; +} + +export interface RefigRenderOptions { + format: RefigRenderFormat; + width?: number; + height?: number; + scale?: number; +} + +export interface RefigRenderResult { + data: Uint8Array; + format: RefigRenderFormat; + mimeType: string; + nodeId: string; + width: number; + height: number; +} + +// --------------------------------------------------------------------------- +// FigmaDocument +// --------------------------------------------------------------------------- + +type FigmaJsonDocument = Record; + +export class FigmaDocument { + readonly sourceType: "fig-file" | "rest-api-json"; + + /** + * For "fig-file" this is the raw bytes of the .fig file. + * For "rest-api-json" this is the parsed REST API document JSON. + */ + readonly payload: Uint8Array | FigmaJsonDocument; + + /** + * @param input Raw `.fig` bytes (Uint8Array) or a parsed Figma REST API + * document JSON object. + * + * For file-path convenience in Node, use `FigmaDocument.fromFile()` from + * the `@grida/refig` entrypoint. + */ + constructor(input: Uint8Array | FigmaJsonDocument) { + if (input instanceof Uint8Array) { + this.sourceType = "fig-file"; + this.payload = input; + return; + } + + if (!input || typeof input !== "object" || Array.isArray(input)) { + throw new Error("FigmaDocument: input must be a Uint8Array or object"); + } + + this.sourceType = "rest-api-json"; + this.payload = input; + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +export function resolveMimeType(format: RefigRenderFormat): string { + const map: Record = { + png: "image/png", + jpeg: "image/jpeg", + webp: "image/webp", + pdf: "application/pdf", + svg: "image/svg+xml", + }; + return map[format]; +} + +function mapFormat(format: RefigRenderFormat): types.ExportAs { + switch (format) { + case "png": + return { format: "PNG", constraints: { type: "none", value: 1 } }; + case "jpeg": + return { + format: "JPEG", + constraints: { type: "none", value: 1 }, + quality: 100, + }; + case "webp": + return { + format: "WEBP", + constraints: { type: "none", value: 1 }, + quality: 75, + }; + case "pdf": + return { format: "PDF" }; + case "svg": + return { format: "SVG" }; + } +} + +function applyScale(exportAs: types.ExportAs, scale: number): types.ExportAs { + if ("constraints" in exportAs) { + return { + ...exportAs, + constraints: { type: "scale", value: scale }, + } as types.ExportAs; + } + return exportAs; +} + +type RestNode = Record & { + id?: string; + children?: RestNode[]; +}; + +/** One export to perform: a node and one of its Figma export settings. */ +export interface ExportItem { + nodeId: string; + node: RestNode; + setting: ExportSetting; +} + +/** + * Walk the REST document and collect every (node, exportSetting) for nodes that have exportSettings. + * Follows Figma HasExportSettingsTrait; one ExportItem per setting per node. + */ +export function collectExportsFromDocument( + json: FigmaJsonDocument +): ExportItem[] { + const doc = json as { document?: { children?: RestNode[] } }; + const pages = doc?.document?.children; + if (!pages?.length) return []; + + const items: ExportItem[] = []; + + function walk(nodes: RestNode[]): void { + for (const node of nodes) { + const id = node.id; + const settings = node.exportSettings as ExportSetting[] | undefined; + if ( + typeof id === "string" && + Array.isArray(settings) && + settings.length > 0 + ) { + for (const setting of settings) { + if ( + setting && + typeof setting === "object" && + "format" in setting && + "constraint" in setting && + "suffix" in setting + ) { + items.push({ nodeId: id, node, setting: setting as ExportSetting }); + } + } + } + const children = node.children as RestNode[] | undefined; + if (children?.length) walk(children); + } + } + + for (const page of pages) { + const pageChildren = page.children as RestNode[] | undefined; + if (pageChildren?.length) walk(pageChildren); + } + return items; +} + +const DEFAULT_EXPORT_SIZE = 1024; + +function getNodeBounds(node: RestNode): { width: number; height: number } { + const box = + (node.absoluteRenderBounds as { width?: number; height?: number }) ?? + (node.absoluteBoundingBox as { width?: number; height?: number }); + const w = + typeof box?.width === "number" && box.width > 0 + ? box.width + : DEFAULT_EXPORT_SIZE; + const h = + typeof box?.height === "number" && box.height > 0 + ? box.height + : DEFAULT_EXPORT_SIZE; + return { width: w, height: h }; +} + +/** + * Map a Figma ExportSetting and node bounds to RefigRenderOptions. + * Constraint SCALE → scale; WIDTH/HEIGHT → width/height from constraint value and node aspect ratio. + */ +export function exportSettingToRenderOptions( + node: RestNode, + setting: ExportSetting +): RefigRenderOptions { + const formatMap: Record = { + JPG: "jpeg", + PNG: "png", + SVG: "svg", + PDF: "pdf", + WEBP: "webp", + }; + const format = formatMap[setting.format] ?? "png"; + + const constraint = setting.constraint; + const { type, value } = constraint; + const bounds = getNodeBounds(node); + + if (type === "SCALE") { + return { + format, + scale: value, + width: DEFAULT_EXPORT_SIZE, + height: DEFAULT_EXPORT_SIZE, + }; + } + if (type === "WIDTH") { + const width = value; + const height = value * (bounds.height / bounds.width); + return { format, width, height }; + } + if (type === "HEIGHT") { + const height = value; + const width = value * (bounds.width / bounds.height); + return { format, width, height }; + } + return { format, width: DEFAULT_EXPORT_SIZE, height: DEFAULT_EXPORT_SIZE }; +} + +/** + * Find a node by id in the Figma REST document tree (walks all pages and descendants). + */ +function findNodeInRestDocument( + json: FigmaJsonDocument, + nodeId: string +): RestNode | undefined { + const doc = json as { document?: { children?: RestNode[] } }; + const pages = doc?.document?.children; + if (!pages?.length) return undefined; + + function walk(nodes: RestNode[]): RestNode | undefined { + for (const node of nodes) { + if (String(node.id) === nodeId) return node; + const children = node.children as RestNode[] | undefined; + if (children?.length) { + const found = walk(children); + if (found) return found; + } + } + return undefined; + } + + for (const page of pages) { + const pageChildren = page.children as RestNode[] | undefined; + if (pageChildren?.length) { + const found = walk(pageChildren); + if (found) return found; + } + } + return undefined; +} + +/** + * Find the FigPage that contains a node by id (for page-scoped scene loading). + */ +function findPageContainingNode( + figFile: { pages?: Array<{ rootNodes?: unknown[] }> }, + nodeId: string +): + | { name: string; canvas: unknown; rootNodes: unknown[]; sortkey: string } + | undefined { + const pages = figFile.pages; + if (!pages?.length) return undefined; + + function walk(nodes: RestNode[]): boolean { + for (const node of nodes) { + if (String(node.id) === nodeId) return true; + const children = node.children as RestNode[] | undefined; + if (children?.length && walk(children)) return true; + } + return false; + } + + for (const page of pages) { + const rootNodes = (page.rootNodes ?? []) as RestNode[]; + if (rootNodes.length && walk(rootNodes)) { + return page as { + name: string; + canvas: unknown; + rootNodes: unknown[]; + sortkey: string; + }; + } + } + return undefined; +} + +/** Result of REST JSON conversion with optional image refs used. */ +interface RestConversionResult { + sceneJson: string; + imageRefsUsed: string[]; +} + +/** + * Convert a Figma REST API document JSON -> Grida snapshot JSON string. + * When rootNodeId is provided, that node is used as the single root (and keeps that id). + */ +function restJsonToSceneJson( + json: FigmaJsonDocument, + rootNodeId?: string, + images?: Record +): RestConversionResult { + const doc = json as { + document?: { children?: Array> }; + }; + const pages = doc?.document?.children; + if (!pages || pages.length === 0) { + throw new Error("FigmaDocument: REST JSON has no document pages"); + } + + const page = pages[0] as { + name?: string; + children?: Array>; + }; + const rootNodes = page.children ?? []; + if (rootNodes.length === 0) { + throw new Error("FigmaDocument: first page has no root nodes"); + } + + let counter = 0; + const baseGradientGen = () => `grad-${++counter}`; + + const resolveImageSrc = + images && + (Object.keys(images).length > 0 + ? (ref: string) => (ref in images ? `res://images/${ref}` : null) + : undefined); + + const buildContext = ( + overrides: Partial + ): iofigma.restful.factory.FactoryContext => ({ + gradient_id_generator: baseGradientGen, + prefer_path_for_geometry: true, + ...(resolveImageSrc && { resolve_image_src: resolveImageSrc }), + ...overrides, + }); + + if (rootNodeId != null && rootNodeId !== "") { + const node = findNodeInRestDocument(json, rootNodeId); + if (!node) { + throw new Error( + `FigmaDocument: node with id "${rootNodeId}" not found in document` + ); + } + let rootIdUsed = false; + const context = buildContext({ + node_id_generator: () => { + if (!rootIdUsed) { + rootIdUsed = true; + return rootNodeId; + } + return `refig-${++counter}`; + }, + }); + const { document: packed, imageRefsUsed } = + iofigma.restful.factory.document(node as any, {}, context); + const fullDoc = + grida.program.nodes.factory.packed_scene_document_to_full_document( + packed + ); + return { + sceneJson: JSON.stringify({ + version: grida.program.document.SCHEMA_VERSION, + document: fullDoc, + }), + imageRefsUsed, + }; + } + + const context = buildContext({ + node_id_generator: () => `refig-${++counter}`, + }); + + const individualResults = rootNodes.map((rootNode) => + iofigma.restful.factory.document(rootNode as any, {}, context) + ); + + const imageRefsUsed = new Set(); + for (const r of individualResults) { + r.imageRefsUsed.forEach((ref: string) => imageRefsUsed.add(ref)); + } + + let packed: grida.program.document.IPackedSceneDocument; + if (individualResults.length === 1) { + packed = individualResults[0].document; + } else { + packed = { + bitmaps: {}, + images: {}, + nodes: {}, + links: {}, + properties: {}, + scene: { + type: "scene", + id: "main", + name: page.name ?? "Page 1", + children_refs: [], + guides: [], + edges: [], + constraints: { children: "multiple" }, + }, + }; + for (const result of individualResults) { + const d = result.document; + Object.assign(packed.nodes, d.nodes); + Object.assign(packed.links, d.links); + Object.assign(packed.images, d.images); + Object.assign(packed.bitmaps, d.bitmaps); + Object.assign(packed.properties, d.properties); + packed.scene.children_refs.push(...d.scene.children_refs); + } + } + + const fullDoc = + grida.program.nodes.factory.packed_scene_document_to_full_document(packed); + + return { + sceneJson: JSON.stringify({ + version: grida.program.document.SCHEMA_VERSION, + document: fullDoc, + }), + imageRefsUsed: Array.from(imageRefsUsed), + }; +} + +/** Result of .fig conversion with images to register. */ +interface FigConversionResult { + sceneJson: string; + images: Record; +} + +/** + * Build a REST-like document structure from .fig bytes for export collection. + * Wraps page.rootNodes in document/children so collectExportsFromDocument can walk it. + */ +export function figBytesToRestLikeDocument( + figBytes: Uint8Array +): FigmaJsonDocument { + const figFile = iofigma.kiwi.parseFile(figBytes); + return figFileToRestLikeDocument(figFile); +} + +/** + * Build REST-like document from a parsed FigFileDocument. + * Use when you already have the parse result (e.g. to avoid parsing twice for images). + */ +export function figFileToRestLikeDocument(figFile: { + pages?: Array<{ + name?: string; + sortkey: string; + canvas?: { guid?: unknown }; + rootNodes: unknown[]; + }>; +}): FigmaJsonDocument { + const pages = figFile.pages; + if (!pages || pages.length === 0) { + throw new Error("FigmaDocument: .fig file has no pages"); + } + + const sortedPages = [...pages].sort((a, b) => + a.sortkey.localeCompare(b.sortkey) + ); + + const canvasNodes = sortedPages.map((p) => ({ + type: "CANVAS" as const, + id: p.canvas?.guid + ? iofigma.kiwi.guid( + p.canvas.guid as { sessionID: number; localID: number } + ) + : `canvas-${String(p.name ?? "")}`, + name: p.name ?? "Page", + children: p.rootNodes, + })); + + return { + document: { + id: "0:0", + type: "DOCUMENT", + name: "Document", + children: canvasNodes, + }, + }; +} + +/** + * Convert .fig file bytes -> Grida snapshot JSON string. + * When rootNodeId is provided, loads only the page containing that node (enables export from any page). + */ +function figBytesToSceneJson( + figBytes: Uint8Array, + rootNodeId?: string +): FigConversionResult { + const figFile = iofigma.kiwi.parseFile(figBytes); + const pages = figFile.pages; + if (!pages || pages.length === 0) { + throw new Error("FigmaDocument: .fig file has no pages"); + } + + const sortedPages = [...pages].sort((a, b) => + a.sortkey.localeCompare(b.sortkey) + ); + + let page: (typeof sortedPages)[0]; + if (rootNodeId != null && rootNodeId !== "") { + const pageWithNode = findPageContainingNode(figFile, rootNodeId); + if (!pageWithNode) { + throw new Error( + `FigmaDocument: node with id "${rootNodeId}" not found in .fig` + ); + } + page = pageWithNode as (typeof sortedPages)[0]; + } else { + page = sortedPages[0]!; + } + + const imagesMap = iofigma.kiwi.extractImages(figFile.zip_files); + const images: Record = {}; + imagesMap.forEach((bytes, ref) => { + images[ref] = bytes; + }); + + const resolveImageSrc = + imagesMap.size > 0 + ? (ref: string) => (imagesMap.has(ref) ? `res://images/${ref}` : null) + : undefined; + + let counter = 0; + const context: iofigma.restful.factory.FactoryContext = { + node_id_generator: () => `refig-${++counter}`, + gradient_id_generator: () => `grad-${++counter}`, + preserve_figma_ids: true, + prefer_path_for_geometry: true, + ...(resolveImageSrc && { resolve_image_src: resolveImageSrc }), + }; + + const { document: packed } = iofigma.kiwi.convertPageToScene(page, context); + const fullDoc = + grida.program.nodes.factory.packed_scene_document_to_full_document(packed); + + return { + sceneJson: JSON.stringify({ + version: grida.program.document.SCHEMA_VERSION, + document: fullDoc, + }), + images, + }; +} + +// --------------------------------------------------------------------------- +// FigmaRenderer +// --------------------------------------------------------------------------- + +export class FigmaRenderer { + readonly document: FigmaDocument; + readonly options: RefigRendererOptions; + + private _canvas: Canvas | null = null; + private _sceneLoaded = false; + /** When set, scene is built with this node as root (REST only). Cleared when nodeId changes. */ + private _requestedNodeId: string | null = null; + + constructor( + document: FigmaDocument | FigmaJsonDocument, + options: RefigRendererOptions = {} + ) { + this.document = + document instanceof FigmaDocument + ? document + : new FigmaDocument(document); + this.options = options; + } + + private async ensureCanvas(width: number, height: number): Promise { + if (this._canvas) return this._canvas; + + this._canvas = await createCanvas({ + backend: "raster", + width, + height, + useEmbeddedFonts: this.options.useEmbeddedFonts ?? true, + }); + + if (this.options.loadFigmaDefaultFonts !== false) { + await ensureFigmaDefaultFonts( + this._canvas as unknown as FigmaDefaultFontsCanvas + ); + } + + return this._canvas; + } + + private loadScene(canvas: Canvas, nodeId: string): void { + if (this._sceneLoaded && this._requestedNodeId === nodeId) return; + + let sceneJson: string; + let imagesToRegister: Record = {}; + + if (this.document.sourceType === "fig-file") { + const figResult = figBytesToSceneJson( + this.document.payload as Uint8Array, + nodeId || undefined + ); + sceneJson = figResult.sceneJson; + imagesToRegister = figResult.images; + } else { + const restResult = restJsonToSceneJson( + this.document.payload as FigmaJsonDocument, + nodeId || undefined, + this.options.images + ); + sceneJson = restResult.sceneJson; + const used = new Set(restResult.imageRefsUsed); + const provided = this.options.images ?? {}; + for (const ref of used) { + if (ref in provided) { + imagesToRegister[ref] = provided[ref]; + } + } + } + + for (const [ref, bytes] of Object.entries(imagesToRegister)) { + canvas.addImageWithId(bytes, `res://images/${ref}`); + } + canvas.loadScene(sceneJson); + this._requestedNodeId = nodeId; + this._sceneLoaded = true; + } + + async render( + nodeId: string, + renderOptions: RefigRenderOptions + ): Promise { + const normalizedNodeId = nodeId.trim(); + if (!normalizedNodeId) { + throw new Error("FigmaRenderer.render: nodeId is required"); + } + + const width = renderOptions.width ?? 1024; + const height = renderOptions.height ?? 1024; + const scale = renderOptions.scale ?? 1; + const format = renderOptions.format; + const mimeType = resolveMimeType(format); + + const canvas = await this.ensureCanvas(width, height); + if (this._requestedNodeId !== normalizedNodeId) { + this._sceneLoaded = false; + } + this.loadScene(canvas, normalizedNodeId); + + let exportAs = mapFormat(format); + if (scale !== 1) { + exportAs = applyScale(exportAs, scale); + } + + const { data } = canvas.exportNodeAs(normalizedNodeId, exportAs); + + return { + data, + format, + mimeType, + nodeId: normalizedNodeId, + width, + height, + }; + } + + /** + * Release the underlying WASM canvas. + * After calling this the renderer must not be used again. + */ + dispose(): void { + this._canvas?.dispose(); + this._canvas = null; + this._sceneLoaded = false; + } +} diff --git a/packages/grida-canvas-sdk-render-figma/package.json b/packages/grida-canvas-sdk-render-figma/package.json new file mode 100644 index 0000000000..11ca2f071c --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/package.json @@ -0,0 +1,75 @@ +{ + "name": "@grida/refig", + "version": "0.0.3", + "private": false, + "description": "Headless Figma renderer — render .fig and REST API JSON to PNG/JPEG/WebP/PDF/SVG", + "keywords": [ + "canvas", + "cli", + "fig", + "fig-file", + "figma", + "figma-renderer", + "grida", + "headless", + "jpeg", + "pdf", + "png", + "refig", + "skia", + "svg", + "wasm", + "webp" + ], + "homepage": "https://grida.co/docs/packages/@grida/refig", + "bugs": "https://github.com/gridaco/grida/issues", + "license": "MIT", + "author": "softmarshmallow", + "repository": { + "type": "git", + "url": "https://github.com/gridaco/grida.git", + "directory": "packages/grida-canvas-sdk-render-figma" + }, + "bin": { + "refig": "./dist/cli.mjs" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + }, + "./browser": { + "types": "./dist/browser.d.mts", + "import": "./dist/browser.mjs", + "default": "./dist/browser.mjs" + } + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup", + "postpack": "node scripts/postpack-publish.cjs", + "prepack": "node scripts/prepack-publish.cjs", + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@grida/canvas-wasm": "0.90.0-canary.8", + "commander": "^12.1.0" + }, + "devDependencies": { + "@figma/rest-api-spec": "0.36.0", + "@grida/io-figma": "workspace:*", + "@grida/schema": "workspace:*", + "fflate": "^0.8.2", + "tsup": "^8.5.0" + } +} diff --git a/packages/grida-canvas-sdk-render-figma/scripts/postpack-publish.cjs b/packages/grida-canvas-sdk-render-figma/scripts/postpack-publish.cjs new file mode 100644 index 0000000000..3941892d29 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/scripts/postpack-publish.cjs @@ -0,0 +1,14 @@ +/** + * postpack: restore package.json from backup after pack/publish. + */ +const fs = require("fs"); +const path = require("path"); + +const pkgDir = path.resolve(__dirname, ".."); +const pkgPath = path.join(pkgDir, "package.json"); +const bakPath = path.join(pkgDir, ".package.json.prepack.bak"); + +if (fs.existsSync(bakPath)) { + fs.copyFileSync(bakPath, pkgPath); + fs.unlinkSync(bakPath); +} diff --git a/packages/grida-canvas-sdk-render-figma/scripts/prepack-publish.cjs b/packages/grida-canvas-sdk-render-figma/scripts/prepack-publish.cjs new file mode 100644 index 0000000000..73a1381dd3 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/scripts/prepack-publish.cjs @@ -0,0 +1,33 @@ +/** + * prepack: strip workspace:* deps from package.json before pack/publish. + * Internal packages (@grida/io-figma, @grida/schema) are bundled by tsup and + * not published; workspace: protocol breaks npm consumers. + */ +const fs = require("fs"); +const path = require("path"); + +const pkgDir = path.resolve(__dirname, ".."); +const pkgPath = path.join(pkgDir, "package.json"); +const bakPath = path.join(pkgDir, ".package.json.prepack.bak"); + +const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); + +function stripWorkspaceDeps(obj) { + if (!obj || typeof obj !== "object") return; + for (const key of Object.keys(obj)) { + const val = obj[key]; + if (typeof val === "string" && val.startsWith("workspace:")) { + delete obj[key]; + } + } +} + +// Backup original +fs.writeFileSync(bakPath, JSON.stringify(pkg, null, 2) + "\n", "utf8"); + +// Strip from dependencies and devDependencies +stripWorkspaceDeps(pkg.dependencies); +stripWorkspaceDeps(pkg.devDependencies); + +// Write modified manifest +fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8"); diff --git a/packages/grida-canvas-sdk-render-figma/tsconfig.json b/packages/grida-canvas-sdk-render-figma/tsconfig.json new file mode 100644 index 0000000000..da543e73be --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "esnext", + "moduleResolution": "bundler", + "types": ["vitest/globals", "node"], + "lib": ["esnext"], + "noImplicitAny": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + } +} diff --git a/packages/grida-canvas-sdk-render-figma/tsup.config.ts b/packages/grida-canvas-sdk-render-figma/tsup.config.ts new file mode 100644 index 0000000000..82b7518681 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["index.ts", "browser.ts", "cli.ts"], + format: ["esm"], + outDir: "dist", + dts: true, + external: ["@grida/canvas-wasm", "commander"], + noExternal: [/^@grida\/(?!canvas-wasm$)/], + clean: true, +}); diff --git a/packages/grida-canvas-sdk-render-figma/vitest.config.ts b/packages/grida-canvas-sdk-render-figma/vitest.config.ts new file mode 100644 index 0000000000..144ce12238 --- /dev/null +++ b/packages/grida-canvas-sdk-render-figma/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + // Run test files sequentially — they share the WASM runtime and a common + // output directory (__tests__/.tmp/). + fileParallelism: false, + testTimeout: 60000, + // Disable Figma default font loading in tests (avoids slow CDN fetches). + env: { REFIG_SKIP_DEFAULT_FONTS: "1" }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dab57a0298..604d2233e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,8 @@ overrides: '@types/react': 19.1.3 '@types/react-dom': 19.1.3 eslint: ^9 - typescript: ^5 + tsx: 4.21.0 + typescript: 5.9.3 axios: 1.13.4 prosemirror-model: 1.23.0 prosemirror-view: 1.36.0 @@ -32,16 +33,19 @@ importers: version: 0.28.0 tsup: specifier: ^8.5.0 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.7.0) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.7.0) + tsx: + specifier: 4.21.0 + version: 4.21.0 turbo: - specifier: ^2.8.3 - version: 2.8.3 + specifier: ^2.8.9 + version: 2.8.9 typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 vitest: specifier: ^4 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(jiti@2.6.1)(jsdom@20.0.3(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.39.0)(yaml@2.7.0) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(jiti@2.6.1)(jsdom@20.0.3(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.39.0)(tsx@4.21.0)(yaml@2.7.0) apps/backgrounds: dependencies: @@ -107,7 +111,7 @@ importers: specifier: ^4 version: 4.1.18 typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 apps/blog: @@ -150,7 +154,7 @@ importers: specifier: 3.9.2 version: 3.9.2(acorn@8.15.0)(esbuild@0.27.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 apps/docs: @@ -211,7 +215,7 @@ importers: specifier: ^4.0.10 version: 4.3.0 typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 apps/viewer: @@ -269,7 +273,7 @@ importers: specifier: ^4 version: 4.1.18 typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 crates/grida-canvas: {} @@ -286,7 +290,7 @@ importers: specifier: ^14.2.4 version: 14.2.5 typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 crates/math2: {} @@ -1003,7 +1007,7 @@ importers: specifier: ^1.2.9 version: 1.4.0 typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 packages/grida-canvas-bitmap: @@ -1085,8 +1089,8 @@ importers: version: 0.5.0 devDependencies: '@figma/rest-api-spec': - specifier: 0.35.0 - version: 0.35.0 + specifier: 0.36.0 + version: 0.36.0 '@grida/cg': specifier: workspace:* version: link:../grida-canvas-cg @@ -1173,6 +1177,31 @@ importers: specifier: 3.1.0 version: 3.1.0 + packages/grida-canvas-sdk-render-figma: + dependencies: + '@grida/canvas-wasm': + specifier: 0.90.0-canary.8 + version: 0.90.0-canary.8 + commander: + specifier: ^12.1.0 + version: 12.1.0 + devDependencies: + '@figma/rest-api-spec': + specifier: 0.36.0 + version: 0.36.0 + '@grida/io-figma': + specifier: workspace:* + version: link:../grida-canvas-io-figma + '@grida/schema': + specifier: workspace:* + version: link:../grida-canvas-schema + fflate: + specifier: ^0.8.2 + version: 0.8.2 + tsup: + specifier: ^8.5.0 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.7.0) + packages/grida-canvas-sequence: {} packages/grida-canvas-tailwind: {} @@ -1280,7 +1309,7 @@ importers: version: 13.6.29 devDependencies: typescript: - specifier: ^5 + specifier: 5.9.3 version: 5.9.3 wrangler: specifier: ^4.40.1 @@ -3231,8 +3260,8 @@ packages: resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} - '@figma/rest-api-spec@0.35.0': - resolution: {integrity: sha512-0v/KJmCWzKBp/wmsqjOas7tLauD1s0nrJJ70vzOCqEEgNrZvugi6/Myssfy+BsfpU5bcC66KrgApqoRRnsbJ4Q==} + '@figma/rest-api-spec@0.36.0': + resolution: {integrity: sha512-OyAjMF3ON2k6aVm61H4MrjSNwJ9Q4w2tYGa6qoUG6tVaNk9eMe/JcCKKYgLPfR9PKfKyoxV/xwn9/HBW3C3bKQ==} '@fingerprintjs/fingerprintjs@4.6.2': resolution: {integrity: sha512-g8mXuqcFKbgH2CZKwPfVtsUJDHyvcgIABQI7Y0tzWEFXpGxJaXuAuzlifT2oTakjDBLTK4Gaa9/5PERDhqUjtw==} @@ -3276,6 +3305,9 @@ packages: '@formatjs/intl-localematcher@0.6.2': resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} + '@grida/canvas-wasm@0.90.0-canary.8': + resolution: {integrity: sha512-Sd6v3Vmbogjx0iQgkMduGwTHpZaWR1XLHP1D+KSn1WnHB8S68MSV2Ta4hkhueEtsuFdLdvE71oovypo2iHwDMA==} + '@grida/tailwindcss-colors@4.0.1': resolution: {integrity: sha512-lRJ1YO3MVSoabbH+xU00IlE49zevUD2NiEjrclsPBNMRLbDSmG+6jdvEOVQHqBo5kXN9x5jomFQrjy/5hzebGQ==} @@ -6017,7 +6049,7 @@ packages: '@tosspayments/brandpay-types@0.2.6': resolution: {integrity: sha512-FY8aOm/2rqtXjYux9ZdNTL6GC5+VNIw8eOQ7TP01dS2uGWSzAtR6uuw9HVsgkmJtSyJgmo97tjmbio5oyeyFlg==} peerDependencies: - typescript: ^5 + typescript: 5.9.3 peerDependenciesMeta: typescript: optional: true @@ -6028,7 +6060,7 @@ packages: '@tosspayments/payment-widget-types@0.0.5': resolution: {integrity: sha512-Daaui9u7aRPzJhw4omJIUAbYBSmNAFNjWWkuUnX9uHxzE3gtjcJHIpGrjlzmCFm+j3ZH4rgyrgv4pkazVYH3OQ==} peerDependencies: - typescript: ^5 + typescript: 5.9.3 peerDependenciesMeta: typescript: optional: true @@ -6491,20 +6523,20 @@ packages: peerDependencies: '@typescript-eslint/parser': ^8.53.0 eslint: ^9 - typescript: ^5 + typescript: 5.9.3 '@typescript-eslint/parser@8.53.0': resolution: {integrity: sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9 - typescript: ^5 + typescript: 5.9.3 '@typescript-eslint/project-service@8.53.0': resolution: {integrity: sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ^5 + typescript: 5.9.3 '@typescript-eslint/scope-manager@8.53.0': resolution: {integrity: sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==} @@ -6514,14 +6546,14 @@ packages: resolution: {integrity: sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ^5 + typescript: 5.9.3 '@typescript-eslint/type-utils@8.53.0': resolution: {integrity: sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9 - typescript: ^5 + typescript: 5.9.3 '@typescript-eslint/types@8.53.0': resolution: {integrity: sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==} @@ -6531,14 +6563,14 @@ packages: resolution: {integrity: sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ^5 + typescript: 5.9.3 '@typescript-eslint/utils@8.53.0': resolution: {integrity: sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9 - typescript: ^5 + typescript: 5.9.3 '@typescript-eslint/visitor-keys@8.53.0': resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==} @@ -7659,6 +7691,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -7804,7 +7840,7 @@ packages: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: - typescript: ^5 + typescript: 5.9.3 peerDependenciesMeta: typescript: optional: true @@ -8642,7 +8678,7 @@ packages: resolution: {integrity: sha512-q2Z87VSsoJcv+vgR+Dm8NPRf+rErXcRktuBR5y3umo/j5zLjIWH7rqBCh3X804gUGKbOrqbgsLUkqDE35C93Gw==} peerDependencies: eslint: ^9 - typescript: ^5 + typescript: 5.9.3 peerDependenciesMeta: typescript: optional: true @@ -11563,7 +11599,7 @@ packages: peerDependencies: jiti: '>=1.21.0' postcss: '>=8.0.9' - tsx: ^4.8.1 + tsx: 4.21.0 yaml: ^2.4.2 peerDependenciesMeta: jiti: @@ -13515,7 +13551,7 @@ packages: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} peerDependencies: - typescript: ^5 + typescript: 5.9.3 ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} @@ -13547,7 +13583,7 @@ packages: '@microsoft/api-extractor': ^7.36.0 '@swc/core': ^1 postcss: ^8.4.12 - typescript: ^5 + typescript: 5.9.3 peerDependenciesMeta: '@microsoft/api-extractor': optional: true @@ -13558,6 +13594,11 @@ packages: typescript: optional: true + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + tsyringe@4.10.0: resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} engines: {node: '>= 6.0.0'} @@ -13568,38 +13609,38 @@ packages: tunnel-rat@0.1.2: resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} - turbo-darwin-64@2.8.3: - resolution: {integrity: sha512-4kXRLfcygLOeNcP6JquqRLmGB/ATjjfehiojL2dJkL7GFm3SPSXbq7oNj8UbD8XriYQ5hPaSuz59iF1ijPHkTw==} + turbo-darwin-64@2.8.9: + resolution: {integrity: sha512-KnCw1ZI9KTnEAhdI9avZrnZ/z4wsM++flMA1w8s8PKOqi5daGpFV36qoPafg4S8TmYMe52JPWEoFr0L+lQ5JIw==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.8.3: - resolution: {integrity: sha512-xF7uCeC0UY0Hrv/tqax0BMbFlVP1J/aRyeGQPZT4NjvIPj8gSPDgFhfkfz06DhUwDg5NgMo04uiSkAWE8WB/QQ==} + turbo-darwin-arm64@2.8.9: + resolution: {integrity: sha512-CbD5Y2NKJKBXTOZ7z7Cc7vGlFPZkYjApA7ri9lH4iFwKV1X7MoZswh9gyRLetXYWImVX1BqIvP8KftulJg/wIA==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.8.3: - resolution: {integrity: sha512-vxMDXwaOjweW/4etY7BxrXCSkvtwh0PbwVafyfT1Ww659SedUxd5rM3V2ZCmbwG8NiCfY7d6VtxyHx3Wh1GoZA==} + turbo-linux-64@2.8.9: + resolution: {integrity: sha512-OXC9HdCtsHvyH+5KUoH8ds+p5WU13vdif0OPbsFzZca4cUXMwKA3HWwUuCgQetk0iAE4cscXpi/t8A263n3VTg==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.8.3: - resolution: {integrity: sha512-mQX7uYBZFkuPLLlKaNe9IjR1JIef4YvY8f21xFocvttXvdPebnq3PK1Zjzl9A1zun2BEuWNUwQIL8lgvN9Pm3Q==} + turbo-linux-arm64@2.8.9: + resolution: {integrity: sha512-yI5n8jNXiFA6+CxnXG0gO7h5ZF1+19K8uO3/kXPQmyl37AdiA7ehKJQOvf9OPAnmkGDHcF2HSCPltabERNRmug==} cpu: [arm64] os: [linux] - turbo-windows-64@2.8.3: - resolution: {integrity: sha512-YLGEfppGxZj3VWcNOVa08h6ISsVKiG85aCAWosOKNUjb6yErWEuydv6/qImRJUI+tDLvDvW7BxopAkujRnWCrw==} + turbo-windows-64@2.8.9: + resolution: {integrity: sha512-/OztzeGftJAg258M/9vK2ZCkUKUzqrWXJIikiD2pm8TlqHcIYUmepDbyZSDfOiUjMy6NzrLFahpNLnY7b5vNgg==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.8.3: - resolution: {integrity: sha512-afTUGKBRmOJU1smQSBnFGcbq0iabAPwh1uXu2BVk7BREg30/1gMnJh9DFEQTah+UD3n3ru8V55J83RQNFfqoyw==} + turbo-windows-arm64@2.8.9: + resolution: {integrity: sha512-xZ2VTwVTjIqpFZKN4UBxDHCPM3oJ2J5cpRzCBSmRpJ/Pn33wpiYjs+9FB2E03svKaD04/lSSLlEUej0UYsugfg==} cpu: [arm64] os: [win32] - turbo@2.8.3: - resolution: {integrity: sha512-8Osxz5Tu/Dw2kb31EAY+nhq/YZ3wzmQSmYa1nIArqxgCAldxv9TPlrAiaBUDVnKA4aiPn0OFBD1ACcpc5VFOAQ==} + turbo@2.8.9: + resolution: {integrity: sha512-G+Mq8VVQAlpz/0HTsxiNNk/xywaHGl+dk1oiBREgOEVCCDjXInDlONWUn5srRnC9s5tdHTFD1bx1N19eR4hI+g==} hasBin: true tw-animate-css@1.4.0: @@ -13661,7 +13702,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9 - typescript: ^5 + typescript: 5.9.3 typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -13974,7 +14015,7 @@ packages: stylus: '>=0.54.8' sugarss: ^5.0.0 terser: ^5.16.0 - tsx: ^4.8.1 + tsx: 4.21.0 yaml: ^2.4.2 peerDependenciesMeta: '@types/node': @@ -14061,7 +14102,7 @@ packages: vue@3.5.13: resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} peerDependencies: - typescript: ^5 + typescript: 5.9.3 peerDependenciesMeta: typescript: optional: true @@ -17023,7 +17064,7 @@ snapshots: '@emotion/core@10.3.1(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 '@emotion/cache': 10.0.29 '@emotion/css': 10.0.27 '@emotion/serialize': 0.11.16 @@ -17323,7 +17364,7 @@ snapshots: '@faker-js/faker@8.4.1': {} - '@figma/rest-api-spec@0.35.0': {} + '@figma/rest-api-spec@0.36.0': {} '@fingerprintjs/fingerprintjs@4.6.2': dependencies: @@ -17375,6 +17416,8 @@ snapshots: dependencies: tslib: 2.8.1 + '@grida/canvas-wasm@0.90.0-canary.8': {} + '@grida/tailwindcss-colors@4.0.1': {} '@hapi/hoek@9.3.0': {} @@ -21421,13 +21464,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(yaml@2.7.0))': + '@vitest/mocker@4.0.18(vite@7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(tsx@4.21.0)(yaml@2.7.0))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(yaml@2.7.0) + vite: 7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(tsx@4.21.0)(yaml@2.7.0) '@vitest/pretty-format@4.0.18': dependencies: @@ -21811,7 +21854,7 @@ snapshots: aria-query@4.2.2: dependencies: - '@babel/runtime': 7.27.1 + '@babel/runtime': 7.28.6 '@babel/runtime-corejs3': 7.26.9 aria-query@5.3.2: {} @@ -22442,6 +22485,8 @@ snapshots: commander@10.0.1: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -23312,7 +23357,7 @@ snapshots: emotion-theming@10.3.0(@emotion/core@10.3.1(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 '@emotion/core': 10.3.1(react@19.2.3) '@emotion/weak-memoize': 0.2.5 hoist-non-react-statics: 3.3.2 @@ -27263,12 +27308,13 @@ snapshots: '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(yaml@2.7.0): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.7.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 2.6.1 postcss: 8.5.6 + tsx: 4.21.0 yaml: 2.7.0 postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.98.0(esbuild@0.27.3)): @@ -28040,7 +28086,7 @@ snapshots: react-inspector@5.1.1(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 is-dom: 1.1.0 prop-types: 15.8.1 react: 19.2.3 @@ -29761,7 +29807,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.7.0): + tsup@8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.7.0): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) cac: 6.7.14 @@ -29772,7 +29818,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(yaml@2.7.0) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.7.0) resolve-from: 5.0.0 rollup: 4.53.5 source-map: 0.7.6 @@ -29789,6 +29835,13 @@ snapshots: - tsx - yaml + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + tsyringe@4.10.0: dependencies: tslib: 1.14.1 @@ -29806,32 +29859,32 @@ snapshots: - immer - react - turbo-darwin-64@2.8.3: + turbo-darwin-64@2.8.9: optional: true - turbo-darwin-arm64@2.8.3: + turbo-darwin-arm64@2.8.9: optional: true - turbo-linux-64@2.8.3: + turbo-linux-64@2.8.9: optional: true - turbo-linux-arm64@2.8.3: + turbo-linux-arm64@2.8.9: optional: true - turbo-windows-64@2.8.3: + turbo-windows-64@2.8.9: optional: true - turbo-windows-arm64@2.8.3: + turbo-windows-arm64@2.8.9: optional: true - turbo@2.8.3: + turbo@2.8.9: optionalDependencies: - turbo-darwin-64: 2.8.3 - turbo-darwin-arm64: 2.8.3 - turbo-linux-64: 2.8.3 - turbo-linux-arm64: 2.8.3 - turbo-windows-64: 2.8.3 - turbo-windows-arm64: 2.8.3 + turbo-darwin-64: 2.8.9 + turbo-darwin-arm64: 2.8.9 + turbo-linux-64: 2.8.9 + turbo-linux-arm64: 2.8.9 + turbo-windows-64: 2.8.9 + turbo-windows-arm64: 2.8.9 tw-animate-css@1.4.0: {} @@ -30221,7 +30274,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(yaml@2.7.0): + vite@7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(tsx@4.21.0)(yaml@2.7.0): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -30235,12 +30288,13 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 terser: 5.39.0 + tsx: 4.21.0 yaml: 2.7.0 - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(jiti@2.6.1)(jsdom@20.0.3(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.39.0)(yaml@2.7.0): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(jiti@2.6.1)(jsdom@20.0.3(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.39.0)(tsx@4.21.0)(yaml@2.7.0): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(yaml@2.7.0)) + '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(tsx@4.21.0)(yaml@2.7.0)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -30257,7 +30311,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(yaml@2.7.0) + vite: 7.3.0(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.0)(tsx@4.21.0)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 diff --git a/services/grida-canvas-document-worker-cf/package.json b/services/grida-canvas-document-worker-cf/package.json index b73801f4ad..848b7bd630 100644 --- a/services/grida-canvas-document-worker-cf/package.json +++ b/services/grida-canvas-document-worker-cf/package.json @@ -16,7 +16,7 @@ "yjs": "^13.6.27" }, "devDependencies": { - "typescript": "^5.5.2", + "typescript": "^5", "wrangler": "^4.40.1" } } diff --git a/turbo.json b/turbo.json index 626993009b..cc92a1af4d 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,5 @@ { "$schema": "https://turborepo.com/schema.json", - "ui": "tui", "concurrency": "32", "tasks": { "generate": {