Add KerfScript rendering to a JavaScript application with the
@mylasertools/kerfscript package. The library runs in browsers and Node.js
and includes TypeScript definitions.
npm install @mylasertools/kerfscriptPass a program as a string. The result contains its geometry, files, parts and warnings:
import { render } from '@mylasertools/kerfscript';
const source = 'width = 80; rect([width, 50]);';
const result = render(source, {
params: { width: 120 },
sheet: [600, 400],
margin: 0,
});
console.log(result.svg);This exports a 120 × 50 mm rectangle. params overrides width without
changing the source string.
| Option | Value |
|---|---|
params |
Parameter values keyed by name |
fonts |
Font files keyed by name, as ArrayBuffer or Uint8Array |
defaultFont |
Name to use when text() does not specify a font |
resolve |
(path) => source callback for libraries outside the standard library |
kerf |
Kerf override for every part, in millimetres |
layout |
"pack" or "none" |
sheet |
[width, height] in millimetres, over the program's $sheet |
defaultSheet |
[width, height] for programs that do not set $sheet: the laser bed |
nest |
Pack small parts into the holes of bigger ones, as $nest |
gap |
Space between parts, in millimetres |
margin |
Space at sheet edges, in millimetres; defaults to gap |
guides |
Whether to include guide geometry |
analysis |
Capture nominal cut geometry before compensation, for verification |
maxSteps |
Evaluation step limit |
limits |
Memory limits, see below; any you leave out keep their default |
Supply a font for programs that use text:
const response = await fetch('/fonts/Inter.ttf');
const font = await response.arrayBuffer();
const result = render(source, {
fonts: { Inter: font },
defaultFont: 'Inter',
});The URL should point to a font file provided by your application.
| Field | Contents |
|---|---|
svg |
Combined SVG containing all sheets |
width, height |
Combined SVG dimensions in millimetres |
sheets |
Each sheet's dimensions, material, thickness, SVG, DXF, paths and placed items |
parts |
Part names, quantities, dimensions, piece counts, cut lengths and material properties |
assembly |
One entry per copy of a part with name, copy, thickness, material, outline, a 4 × 4 matrix and placed, for showing the design put together. Parts without at or rotate have placed: false. A bent part has bends; a part in a step has step; marks holds what is engraved and scored on it, front and back |
steps |
The text of each assembly step, in order; a part's step counts from 1 |
hardware |
Each hardware(): kind, from, to, size, up, sides, step and label |
paths |
Layer paths with name, mode, color, d, regions and lines |
layers |
Layer definitions in output order |
warnings |
Problems that did not stop the render |
echoes |
Output from the program's echo() calls |
analysis |
When requested: nominal cut regions and lines per definition, with compensation kerf; see assembly verification |
Each sheet with back-face marks has back, with its own svg, dxf and
paths: the sheet turned over left to right.
bendPoint(bends, thickness, [x, y, z]) gives where a point of a bent
part goes, in the part's own coordinates, before its matrix; and
meshPieces(outline, bends) cuts a part's outline into pieces narrow
enough across its hinges to bend smoothly. A 3D view extrudes each piece
by the thickness, moves its points with bendPoint() and then by the
matrix.
Use result.svg for a combined preview. Use result.sheets[i].svg or
.dxf to export individual sheets, particularly when they use different
materials.
For a custom preview, d contains all paths in a layer. regions contains
closed outlines and lines contains open paths. On a fill layer, draw
regions with the even-odd fill rule and draw open paths as strokes.
Read a program's editable settings without rendering its geometry:
import { parameters } from '@mylasertools/kerfscript';
const source = 'width = 80; // [40:5:200]\nrect([width, 50]);';
const settings = parameters(source).parameters;Each setting includes name, caption, group, type, initial and
span. Ranges add min, max and step; menus add options.
type is "number", "string" or "boolean". A numeric vector has
type "number" and an array as its initial value.
span gives the source offsets [start, end] of the value. An editor can
use them to update one setting:
const width = settings.find((p) => p.name === 'width');
if (width) {
const next = source.slice(0, width.span[0]) + '120' + source.slice(width.span[1]);
}Read the settings again after editing the source; the offsets refer to the
string passed to parameters().
format(source) returns the program with its layout set, as kerf fmt
does: two-space indentation, spaces between tokens, at most one blank line in
a row and a newline at the end. Line breaks and comments stay where they
are. It throws a KerfError for a program that does not parse, and checks
its own result: the output always has the same tokens and comments as the
input.
import { format } from '@mylasertools/kerfscript';
format('x=1;module m(a,b=2){rect([a,b]);}');
// 'x = 1; module m(a, b = 2) { rect([a, b]); }\n'format(source, { indent: 4 }) uses four spaces per level.
instructionsHtml(result, title) turns a render() result into the page
kerf instructions writes: a standalone HTML document with the parts,
the sheets and a drawing for each step.
diffRenders(a, b) compares two results of render(source, { layout: "none" })
and returns what differs, one entry per part copy and layer: part, copy,
layer, change ("added", "removed" or "changed"), area (mm² of
shapes that differ), linesAdded, linesRemoved and at, the box of the
difference in the part's own coordinates. With layout "none", each sheet
item's offset is the move from the part's coordinates (y down) to the
sheet's.
importSvg(svg, options) turns an SVG drawing into a program, as
kerf import does. svg is the text of the drawing, or the bytes of the
file as a Uint8Array or ArrayBuffer; bytes are read as UTF-8 or UTF-16,
which some programs write. It returns:
source: the programparts: the number of partsfound: what was recognised, as counts by kindsummary: the same in words, such as2 parts; found 3 circles.notes: what to know about the drawing, also written at the top of the program
The options are those of kerf import: dpi, tolerance, kerf and
pack, and name, the drawing's name for the comment at the top. See
Importing SVG drawings.
import { importSvg, render } from '@mylasertools/kerfscript';
const file = input.files[0];
const { source, summary } = importSvg(await file.arrayBuffer(), { name: file.name });
const result = render(source, { fonts, defaultFont: 'Atkinson' });A drawing with text needs a font to render, as any program with text()
does. A large drawing can take some seconds to import: in a browser, call
importSvg() in a web worker so the page stays responsive. It throws an
Error for a file that is not SVG.
@mylasertools/kerfscript/highlight colours KerfScript code without loading
the interpreter. It never throws, so it works on unfinished code in an editor.
Its categories match the VS Code extension's grammar.
import { highlight, highlightLine } from '@mylasertools/kerfscript/highlight';
// HTML with a t-keyword, t-builtin, t-comment ... span per token
pre.innerHTML = highlight(source);
// Line by line, for editors such as CodeMirror
const state = { inBlock: false };
for (const line of source.split('\n')) {
for (const { kind, text } of highlightLine(line, state)) {
// kind: comment, section, control, string, number, keyword, constant,
// definition, builtin, layer, call, special, modifier or plain
}
}highlight() never puts a span across a line break, so its result can be
split into lines. section and control are customizer comments
(/* [Size] */ and // [40:5:400]).
SIGNATURES lists every built-in module and function with its parameters,
defaults and a line of documentation, for editors, completion and help
text. @mylasertools/kerfscript/signatures has the same table without the
interpreter.
import { formatSignature, signature } from '@mylasertools/kerfscript/signatures';
const rect = signature('rect');
formatSignature(rect); // 'rect(size = 1, center = false, r = 0)'
rect.doc; // 'A rectangle with its corner at the origin'
rect.params[2]; // { name: 'r', default: '0', doc: 'Corner radius', named: true }Each signature has a kind (module, function, or both for echo and
assert), params in positional order, and section, the heading in the
language reference that documents it. A parameter with
named: true can only be given by name. rest marks built-ins that take
any number of values, such as str(...), and forms lists other ways to
call one, such as log(base, x).
@mylasertools/kerfscript/language answers what an editor asks about a
program. It is what kerf lsp uses (Editors), and it runs in
browsers too, for an editor on a web page. Positions are offsets into the
source in UTF-16 code units, as JavaScript strings count them.
import {
analyze,
complete,
hover,
problems,
signatureHelp,
} from '@mylasertools/kerfscript/language';
const a = analyze(source, { resolve });
hover(a, offset); // { start, end, text } with Markdown text, or undefined
complete(a, offset); // { from, to, items: [{ label, kind, detail, doc, insert }] }
signatureHelp(a, offset); // { label, params: [{ start, end, doc }], active }
problems(source, { resolve }); // [{ severity, message, start, end, related }]analyze() reads the program's names, scopes and libraries once for the
other functions, and works on code that does not parse: the lines with
errors are left out. Its resolve option finds libraries, as render()'s
does.
| Function | Returns |
|---|---|
hover(a, offset) |
What the name or library path at the offset is, as Markdown |
complete(a, offset) |
What can be typed there, to replace the source from from to to |
signatureHelp(a, offset) |
The parameters of the call being typed and the one being typed |
definition(a, offset) |
Where the name is defined; file names the library when it is not in the program |
references(a, offset) |
Every place in the program the name is defined and used |
rename(a, offset, name) |
The places to change, or { error } for a built-in or a library's name |
outline(a) |
Modules, functions and variables, nested as written |
problems(source, options) |
The errors and warnings from running the program; it takes render()'s options |
problems() runs the program, so call it in a worker on a page, as the
playground does with render().
Language errors use KerfError:
| Property | Value |
|---|---|
message |
the message with its position, as Unknown variable "w" (line 3, column 9) |
reason |
the message without its position |
line, col |
where the error is, when known; columns count UTF-16 code units |
file |
the library path when the error is inside a library, as kerf/box |
trace |
the module and function calls it happened inside, innermost first: { kind, name, line, col, file, count }, where count is how often a recursive call is on the stack |
call |
the built-in that was given an unusable argument |
echoes |
echo() output before the error |
warnings |
warnings before the error |
missingFonts |
fonts the application needs to supply |
formatError() writes all of that the way the command line shows it: the
source line with the problem underlined, the calls, and what a built-in
takes. Pass the source, and resolve to show lines inside your own
libraries; the standard library is found without it.
import { formatError, render } from '@mylasertools/kerfscript';
try {
render(source);
} catch (error) {
console.error(formatError(error, source, { file: 'panel.kerf' }));
}error: Unknown variable "widht". Did you mean "width"?
--> panel.kerf:3:9
|
3 | rect([widht, 20]);
| ^^^^^
= in module panel(), called at panel.kerf:5:1
Show it in a monospaced font so the marks line up. formatError() takes
any thrown value; for one that is not a KerfError it gives
error: <message>.
Run browser renders in a web worker so they do not block the page. Set a timeout and terminate the worker if it takes too long. Evaluation limits bound program steps and recursion, but geometry work also takes time.
Size limits stop a program before it builds more than a browser tab can
usually hold: part copies, circle segments, list sizes, text length, shape
points, exported points and sheets. They bound what a program makes, not
every byte the renderer uses, so keep the worker's timeout as well, and run
programs you did not write where the page can end them. The defaults are in
Limits and in DEFAULT_LIMITS. Lower them with the
limits option if your worker has little memory:
import { render, DEFAULT_LIMITS } from '@mylasertools/kerfscript';
render(source, { limits: { maxPoints: DEFAULT_LIMITS.maxPoints / 2 } });KerfScript programs have no direct file or network access. use and
include can load the standard library and sources returned by your
resolve callback. Restrict that callback to libraries you intend to expose.
In Node.js, resolver(file) from @mylasertools/kerfscript/libraries
finds a design's files and installed libraries as the command line does; see
Shared libraries.