> Canonical HTML: https://kerfscript.mylasertools.com/docs/webassembly/

# WebAssembly

`kerfscript.wasm` runs KerfScript from any language that can host a
WebAssembly module: Python, Rust, Go, C#, Java, Ruby and others. It is a
WASI (preview 1) module built from the same source as the JavaScript package,
so it makes the same SVG and DXF files, byte for byte.

For JavaScript applications, use the [JavaScript API](https://kerfscript.mylasertools.com/docs/javascript/index.md)
instead: it is smaller and faster.

## Getting the module

Download `kerfscript-<version>.wasm` from the
[releases](https://github.com/mylasertools/kerfscript/releases) page and save
it as `kerfscript.wasm`, or get `kerfscript-wasm-<version>.zip`, which has the
module with this guide and its licences. You can also build it from the
repository with [Javy](https://github.com/bytecodealliance/javy):

```sh
npm run build
npm run wasm        # writes dist/kerfscript.wasm
```

Set `JAVY` to the javy binary if it is not on the `PATH`.

## How it works

The module reads one JSON request from standard input, writes one JSON
response to standard output and exits. Start a fresh instance for each
request. From a shell, with [Wasmtime](https://wasmtime.dev):

```sh
echo '{"source": "rect([80, 50]);"}' | wasmtime run kerfscript.wasm
```

### Request

| Field         | Value                                                                       |
| ------------- | --------------------------------------------------------------------------- |
| `command`     | `"render"` (default), `"params"`, `"version"` or `"licenses"`               |
| `source`      | The program, as a string                                                    |
| `params`      | Parameter values keyed by name                                              |
| `files`       | Libraries for `include` and `use`, keyed by path: `{"lib/tab.kerf": "..."}` |
| `fonts`       | Font files keyed by name, base64 encoded. `Atkinson` is built in            |
| `defaultFont` | Name to use when `text()` does not specify a font                           |
| `kerf`        | Kerf override for every part, in millimetres                                |
| `layout`      | `"pack"` (default) or `"none"`                                              |
| `sheet`       | Sheet size `[width, height]` in millimetres                                 |
| `gap`         | Space between packed parts                                                  |
| `margin`      | Space between the parts and the sheet edge                                  |
| `guides`      | `true` to include guide layer geometry                                      |

These match the [render() options](https://kerfscript.mylasertools.com/docs/javascript/index.md#options). A path in `files`
can leave out `.kerf`, as in the program.

### Response

Every response has `ok`. When it is `false`, `error` holds the message and
`echoes` holds what the program echoed before it stopped. The module never
exits with an error code for a bad program.

- `render`: `svg` (all sheets, for preview), `width`, `height`, `parts`,
  `assembly`, `warnings`, `echoes` and `sheets`. `assembly` is the same as
  from [`render()`](https://kerfscript.mylasertools.com/docs/javascript/index.md): one entry per copy of a part, with its
  outline, thickness and placement. Each sheet has `width`, `height`,
  `material`, `thickness`, `svg` and `dxf`; write one file per sheet to cut.
- `params`: `parameters`, as from [`parameters()`](https://kerfscript.mylasertools.com/docs/javascript/index.md).
- `version`: `version` (package) and `language`.
- `licenses`: `files`, the licence texts of the module and of everything
  built into it, keyed by file name: `LICENSE`, `COMMERCIAL-LICENSE.md`,
  `THIRD-PARTY-NOTICES.md`, `THIRD-PARTY-LICENSES.md` (the JavaScript engine
  and its Rust crates) and `OFL.txt`. The release ZIP has the same files.

## Python

With the [`wasmtime`](https://pypi.org/project/wasmtime/) package:

```python
import json, os, tempfile
from wasmtime import Engine, Linker, Module, Store, WasiConfig

engine = Engine()
module = Module.from_file(engine, "kerfscript.wasm")
linker = Linker(engine)
linker.define_wasi()

def kerf(request):
    with tempfile.TemporaryDirectory() as d:
        inp, out = os.path.join(d, "in.json"), os.path.join(d, "out.json")
        with open(inp, "w", encoding="utf-8") as f:
            json.dump(request, f)
        wasi = WasiConfig()
        wasi.stdin_file = inp
        wasi.stdout_file = out
        store = Store(engine)
        store.set_wasi(wasi)
        linker.instantiate(store, module).exports(store)["_start"](store)
        with open(out, encoding="utf-8") as f:
            return json.load(f)

result = kerf({"source": "w = 80; rect([w, 50]);", "params": {"w": 120}})
for i, sheet in enumerate(result["sheets"]):
    with open(f"sheet-{i + 1}.svg", "w", encoding="utf-8", newline="") as f:
        f.write(sheet["svg"])
```

Compile the module once and reuse it; each request only needs a new `Store`.

## Node.js

The JavaScript package is the better fit in Node.js, but the module also runs
under `node:wasi`, which is how the test suite checks it:

```js
import { closeSync, openSync, readFileSync, writeFileSync } from 'node:fs';
import { WASI } from 'node:wasi';

const module = await WebAssembly.compile(readFileSync('kerfscript.wasm'));
writeFileSync('in.json', JSON.stringify({ source: 'rect([80, 50]);' }));
const stdin = openSync('in.json', 'r');
const stdout = openSync('out.json', 'w');
const wasi = new WASI({ version: 'preview1', stdin, stdout, returnOnExit: true });
wasi.start(await WebAssembly.instantiate(module, wasi.getImportObject()));
closeSync(stdin);
closeSync(stdout);
console.log(JSON.parse(readFileSync('out.json', 'utf8')).svg);
```

## Performance

The module carries its own JavaScript engine (QuickJS), so it is about 2 MB
and renders roughly three to five times slower than Node.js. The example
designs take well under a second.
