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

@hadimardanian/core

v1.1.1

Published

Use Gerber/drill files to create an SVG render of a finished PCB in Node.js or the browser.

Downloads

583

Readme

@hadimardanian/core

npm

Turn a folder of Gerber / Excellon drill files into SVG previews of a finished PCB.

Works in Node.js (directory helpers) and the browser (pass File[] into read).

npm install @hadimardanian/core

Quick start — render a board folder

Point renderDirectory at a directory of fab files and an output folder. It writes:

  • top.svg / bottom.svg — finished board composites
  • one SVG per layer (e.g. F_Cu.gbr.svg)
import {renderDirectory} from '@hadimardanian/core'

const {layers, outputFiles} = await renderDirectory('./my-board', './out')

console.log(
  `Rendered ${layers.length} layers → ${outputFiles.length} SVG files`
)
// → out/top.svg, out/bottom.svg, out/<layer-filename>.svg …

Board composites only (skip per-layer SVGs):

await renderDirectory('./my-board', './out', {boardOnly: true})

Per-layer SVGs only (skip top.svg / bottom.svg):

await renderDirectory('./my-board', './out', {layersOnly: true})

Batch-render many boards

When each board lives in its own subdirectory:

import fs from 'node:fs'
import path from 'node:path'
import {renderDirectory} from '@hadimardanian/core'

const boardsRoot = './boards' // each child folder is one PCB fab set
const outRoot = './out'

async function main() {
  for (const boardName of fs.readdirSync(boardsRoot)) {
    const boardPath = path.resolve(boardsRoot, boardName)
    if (!fs.statSync(boardPath).isDirectory()) continue

    const outDir = path.join(outRoot, boardName)
    const {layers, outputFiles} = await renderDirectory(boardPath, outDir)

    console.log(
      `Rendering ${boardName}: ${layers.length} layers, ${outputFiles.length} files`
    )
  }
}

main().catch(error => {
  console.error(error)
  process.exit(1)
})

Example layout:

boards/
  clockblock/          ← Gerbers + drills
  arduino-uno/
out/
  clockblock/
    top.svg
    bottom.svg
    …
  arduino-uno/
    …

What goes in the input folder?

Pass the full fab set for one design: copper, soldermask, silkscreen, outline/profile, and drill.

  • File selection is denylist-based (images, HTML, JSON, CAD reports, etc. are skipped). Odd Gerber extensions (e.g. .gm, .gm13) are kept.
  • Layer type/side come from filenames (@hadimardanian/identify-layers).
  • Unparseable files are soft-skipped by read().

Helpers (also exported):

| Function | Role | | -------- | ---- | | collectLayerFiles(dir) | Recursively list fab candidates | | shouldSkipLayerFile(path) | True for non-fab sidecars |

Step-by-step pipeline

If you need more control than renderDirectory:

import fs from 'node:fs/promises'
import {
  read,
  plot,
  renderLayers,
  renderBoard,
  stringifySvg,
} from '@hadimardanian/core'

const files = [
  'top-copper.gbr',
  'top-solder-mask.gbr',
  'top-silk-screen.gbr',
  'bottom-copper.gbr',
  'bottom-solder-mask.gbr',
  'outline.gbr',
  'drill.xnc',
]

const readResult = await read(files)
const plotResult = plot(readResult)
const layersResult = renderLayers(plotResult)
const board = renderBoard(layersResult)

await Promise.all([
  fs.writeFile('top.svg', stringifySvg(board.top)),
  fs.writeFile('bottom.svg', stringifySvg(board.bottom)),
])
read → plot → renderLayers → renderBoard → stringifySvg

In the browser, pass File[] (e.g. from <input type="file" multiple>) to read instead of filesystem paths.

API snapshot

| Export | Notes | | ------ | ----- | | renderDirectory(src, dest, options?) | Node: folder → SVG files | | collectLayerFiles / shouldSkipLayerFile | Node: fab file discovery | | read, plot, renderLayers, renderBoard, stringifySvg | Main pipeline | | BOARD_PALETTE, LAYER_COLORS | Fab / layer fill colors |

More detail: Usage Guide. CLI alternative: @hadimardanian/cli (tracer).

Built from