npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mylasertools/laser-stencil

v0.1.0

Published

Add bridges to stencils so the middles of letters and shapes don't fall out. SVG, TypeScript, CLI and browser.

Readme

Laser Stencil

Cut the letters. Keep the middles. Find every piece of a stencil that would fall out, and add the shortest bridges to hold it in place. Works with any font or SVG, in the browser, the CLI or TypeScript.

npm MIT

Try the live demo → · API · CLI · Report an issue

Without bridges, the middles of O, D and B fall out of the stencil. With laser-stencil, bridges hold them in place.

TypeScript · ESM + CommonJS · Browser + Node.js 20+ · MIT

An open-source tool by MyLaserTools. The demo runs this repository's own build and processes everything locally. Nothing is uploaded. The GIFs are captured from the running demo with npm run capture.

Why

Cut an O out of card, vinyl, mylar or plywood and its middle drops out. The same happens to A, B, D, P, Q, R, 0, 4, 6, 8, 9, %, & and @, and to any shape with an enclosed area. Stencil fonts solve this for a handful of typefaces. This package solves it for any font or artwork:

  1. It finds every enclosed piece ("island") that would fall out, including islands nested inside other islands.
  2. For each island, it looks for the shortest straight crossings to the surrounding sheet, in the direction you prefer.
  3. It places bridges on opposite sides of the island, checks the full bridge width, and keeps the bridge material in the cut.

Type any text, change the font, bridge width, direction and count. Bridges update live.

Install

npm install @mylasertools/laser-stencil

Or run it without installing:

npx @mylasertools/laser-stencil design.svg -o design-stencil.svg

Use it

import { stencilSvg } from '@mylasertools/laser-stencil';

const result = stencilSvg(svgString, {
  bridgeWidth: 1.5, // mm
  bridges: 2, // per island
  direction: 'vertical', // or 'horizontal', or 'auto' for the shortest at any angle
  frame: 8, // optional sheet outline, 8 mm around the design
});

result.svg; // laser-ready SVG in millimetres, hairline cut outlines
result.islands; // every piece that would have fallen out
result.bridges; // where each bridge went, how long and at what angle
result.issues; // anything it could not fix, explained

For GOOD VIBES in Archivo Black at 40 mm: 5 islands found, 10 bridges added, about 20 ms.

Already have outlines? Skip the SVG

Generators and editors that work with polygons can call the geometry directly. Coordinates are in millimetres.

import { stencilPolygons } from '@mylasertools/laser-stencil';

const { polygons, islands, bridges } = stencilPolygons(contours, { fillRule: 'evenodd' });

Just check a file

import { findIslands } from '@mylasertools/laser-stencil';

const islands = findIslands(svgString); // [] means nothing falls out

CLI

laser-stencil sign.svg -o sign-stencil.svg            # 2 vertical bridges per island
laser-stencil sign.svg -w 2.5 -b 1 -d auto            # one 2.5 mm bridge at the best angle
laser-stencil sign.svg --frame 10 -o sign-sheet.svg   # add a sheet outline 10 mm around it
laser-stencil sign.svg --islands --json               # report only, as JSON
cat sign.svg | laser-stencil - > sign-stencil.svg     # stdin and stdout

| Option | Default | Meaning | | ----------------- | ---------- | -------------------------------------------- | | -o, --output | stdout | Where to write the stencil SVG | | -w, --width | 1.5 | Bridge width in mm | | -b, --bridges | 2 | Bridges per island, 1 to 4 | | -d, --direction | vertical | vertical, horizontal or auto | | --min-area | 0 | Let islands smaller than this (mm²) fall out | | --frame | none | Sheet outline this many mm around the design | | --fill | off | Write filled shapes instead of cut outlines | | --islands | off | Report islands only | | --json | off | Print the report as JSON |

The exit code is 1 when an island could not be bridged, so it fits into scripts and CI checks.

API

stencilSvg(svg, options?)

| Option | Type | Default | | | --------------- | -------------------------------------- | ------------ | --------------------------------------------------------------- | | bridgeWidth | number | 1.5 | Bridge width in mm | | bridges | 1 \| 2 \| 3 \| 4 | 2 | Bridges per island | | direction | 'vertical' \| 'horizontal' \| 'auto' | 'vertical' | Other angles are tried only when this one cannot hold an island | | minIslandArea | number | 0 | Islands smaller than this (mm²) are left to fall out | | frame | number | none | Add a sheet outline this many mm outside the design | | output | 'cut' \| 'fill' | 'cut' | Hairline outlines for the laser, or filled shapes for previews | | tolerance | number | 0.02 | Curve flattening tolerance in mm | | precision | number | 3 | Decimal places in the output |

Returns { svg, width, height, unit: 'mm', polygons, islands, bridges, issues }.

  • islands[]: { id, area, bounds, center, polygon, bridges, skipped }, with sizes in mm and mm².
  • bridges[]: { island, center, angle, length, width, polygons }. An angle of 90 is vertical, 0 is horizontal.
  • issues[]: UNBRIDGED_ISLAND (no straight crossing fits), FEWER_BRIDGES (fewer than requested fit), OPEN_PATH_IGNORED.

stencilPolygons(contours, options?)

Same options without frame, output and precision, plus fillRule: 'nonzero' | 'evenodd'. Returns { polygons, islands, bridges, issues }.

findIslands(svg, options?) and toPathData(polygons, precision?)

Report the islands without changing anything, and turn polygons into SVG path data.

Errors are thrown as StencilError with a stable code.

What it reads

  • path, rect, circle, ellipse, polygon, polyline and line, with nested transforms, viewBox and physical units (mm, cm, in, px, pt).
  • Filled shapes use their own fill-rule. Outline-only artwork (fill="none" with a stroke), the usual shape of a laser file, is read as nested outlines: a shape inside a shape is a hole.
  • Text must be converted to paths first. Open paths are ignored and reported. Scripts, external references, clipping and masks are rejected.
  • Output curves are flattened to straight segments within tolerance (0.02 mm by default), well below a laser's kerf.

Good to know

  • Bridges are measured across their full width, not just along a centre line, so a bridge never pokes into a counter edge.
  • When there are two or more bridges, they go on different sides of the island, so pieces can't swing.
  • Islands inside islands (a ring inside an O) are bridged to their own surroundings, never across the counter.
  • Bridge width is your call. Around 1 to 2 mm suits card and vinyl at 40 mm letter size; thicker sheets and larger letters want more.

Development

npm ci
npm run check      # build, tests, bundle size
npm run demo       # http://127.0.0.1:4175/demo/
npm run capture    # regenerate the README GIFs (needs ffmpeg)
npm run examples   # regenerate examples/

Demo fonts are Archivo Black, Alfa Slab One and Rubik Mono One, all under the SIL Open Font License 1.1 and used only by the demo. See demo/fonts. Clipper2 is bundled under the Boost Software License; see THIRD-PARTY-NOTICES.md.

License

MIT © MyLaserTools