npm.io
0.2.0 • Published 21h agoCLI

@endonny/inksight

Licence
MIT
Version
0.2.0
Deps
0
Size
100 kB
Vulns
0
Weekly
0

InkSight

InkSight turns a pen-plotter SVG into a structured, machine-readable report: page geometry, ink totals, per-layer stats, a density grid, and plottability warnings. It is deterministic — no rendering and no model calls, so two runs on the same file agree to the digit.

Use it

npx @endonny/inksight plot.svg                  # StatsReport JSON on stdout
npx @endonny/inksight plot.svg --grid 16        # finer density grid
cat plot.svg | npx @endonny/inksight            # or pipe it
npx @endonny/inksight diff a.svg b.svg c.svg    # variability across variants
npx @endonny/inksight check plot.svg --max-cv 1.5   # gate: exit 0 pass, 1 fail

npm i -g @endonny/inksight installs it permanently. Flags: --grid N, --pen-width MM, --saturation-threshold C, --compact, -i/--input FILE, -h/--help. Errors go to stderr with a nonzero exit, so the JSON on stdout is always a report or nothing.

Gate on it

check turns the report into a pass/fail decision a queue can branch on without parsing anything. Every threshold is optional; an unset one is not checked.

inksight check plot.svg --max-cv 1.5 --max-density 0.35 --no-margin-violations --max-saturated 0

Exit 0 prints {"pass":true,"failures":[]}. Exit 1 names each breached threshold with both sides of the comparison:

{
  "pass": false,
  "failures": [
    { "check": "cv", "measured": 0.76, "threshold": 0.5 },
    { "check": "inkDensity", "measured": 0.2807, "threshold": 0.1 }
  ]
}

Checks are cv (--max-cv), inkDensity (--max-density, a ratio), marginViolations (--no-margin-violations) and saturatedCells (--max-saturated). --report embeds the full StatsReport under report when you want the verdict and the numbers in one call; the default verdict stays compact so an exit-code consumer pays nothing for it. --grid, --pen-width and --saturation-threshold apply here too.

A file that cannot be measured is not a failed gate: it exits 1 with the error on stderr and no JSON on stdout, so a broken file can never be mistaken for a measured rejection.

There is also a browser version — drop an SVG and read the same report, entirely client-side: https://chronick.github.io/inksight/

What it measures

field what it says
page, drawable Physical page in mm, and the margin-clipped drawable rect inside it.
units The unit declared on width/height (mm, cm, in, px, or assumed-px when the file declares none), and the resulting mm per user unit.
penWidthMm, penWidthSource Effective pen width for coverage math, and whether it came from a flag, the SVG, or the default.
scale Path-space → mm factor recovered from the group transform.
totals Layers, paths, vertices, segments, arcLengthMm (ink drawn), penUpTravelMm (travel between paths in file order), bounding box, bboxCoverageRatio, inkDensity.
layers[] Per-layer paths, vertices, arc length, ink density, and stroke color — the pen-change plan, in numbers.
densityGrid Coverage per grid cell plus max, mean, and cv, the coefficient of variation that scores spatial balance (≤0.5 even, ≥1.5 clumped).
warnings Paths crossing the margin clip (they will be cut off), and cells at solid-ink saturation for this pen width.

Every length in the report is millimetres.

The shape itself is published as a JSON Schema (draft 2020-12), so an agent can validate or generate types against it instead of inferring the shape from one example:

import schema from "@endonny/inksight/schema.json" with { type: "json" };

additionalProperties is false at every level, and a test validates a real analyzeSvg run against the schema on every build — so a field that appears, disappears, or changes type fails CI rather than reaching you as a surprise.

Library usage

import { analyzeSvg } from "inksight";
import { readFileSync } from "node:fs";

const report = analyzeSvg(readFileSync("plot.svg", "utf-8"), { grid: 16 });
console.log(report.totals.arcLengthMm, report.densityGrid.cv);
if (report.warnings.marginViolationPaths > 0) throw new Error("geometry escapes the margin");

inksight/report exports the presentation layer the web page uses — buildReportModel(report) turns a StatsReport into labelled, formatted rows, a heatmap model, and warning items, with no DOM dependency.

Scope

v1 measures polyline SVGs: absolute M/L paths under a translate(cx,cy) scale(S) group, sized by a viewBox plus width/height. Files declaring mm, cm, in or px are converted to millimetres; a file with no unit is read as CSS px at 96dpi and the units field reports assumed-px, so a guess is never disguised as a measurement.

Curves (C/Q/A/…), relative commands, closepath, and nested transforms are rejected with a clear error rather than silently mis-measured. Plot-time estimation and pen-travel optimization modelling are deliberately out of scope — that is vpype-grade work.

Works with

Any generator that exports polyline SVGs in the shape above. hatch3d is one, and its exports are what the bundled example is rendered from.

License

MIT

Keywords