createAnnotator
import { createAnnotator } from "@ingcreators/annot-annotator";
const annotator = createAnnotator(options?);Factory that returns a headless annotator instance. The returned object is stateless — you can create one per call or hoist it to a module-level singleton; both are equivalent.
Signature
Section titled “Signature”function createAnnotator(options?: AnnotatorOptions): Annotator;
interface AnnotatorOptions { /** * Override the default font stack used for text annotations. * Defaults to the logical `Annot Sans` token with the * system-font cascade. */ fontFamily?: string;}
interface Annotator { toPng(input: AnnotateInput): Promise<Buffer>; toSvg(input: AnnotateInput): Promise<string>; toEditablePng(input: EditableInput): Promise<Buffer>;}
interface AnnotateInput { /** * Base image bytes. Accepts a Buffer, a Uint8Array, or a * `data:image/png;base64,...` URL. */ baseImage: Buffer | Uint8Array | string;
/** * SVG fragment to overlay on the base image. Coordinate space * is the natural pixel dimensions of `baseImage` (read from * the PNG IHDR chunk). */ annotationsSvg: string;}
interface EditableInput extends AnnotateInput { /** * Optional opaque kv tags written into the embedded XMP. * Soft-convention key names (`source`, `screen`, `capturedAt`, * `commit`) are documented but not validated. */ tags?: Record<string, string>;}Examples
Section titled “Examples”Simple rectangle
Section titled “Simple rectangle”const annotator = createAnnotator();const annotated = await annotator.toPng({ baseImage: await readFile("./screenshot.png"), annotationsSvg: ` <rect x="100" y="100" width="200" height="40" fill="none" stroke="#ff5252" stroke-width="3" /> `,});SVG output (no rasterisation)
Section titled “SVG output (no rasterisation)”When you want the SVG itself — for embedding in a webpage, for
diffing in tests, for piping into another converter — use
toSvg:
const svg = await annotator.toSvg({ baseImage, annotationsSvg });await writeFile("./annotated.svg", svg);Re-editable PNG
Section titled “Re-editable PNG”toEditablePng returns the same visible image as toPng, plus
the original un-annotated capture + the annotations SVG embedded
in the PNG’s XMP metadata + custom svGo chunk. Re-opening the
file in the Annot editor — annot.work/app/, the desktop /
VSCode hosts, or the Chrome extension — restores the annotations
as selectable / movable / restylable objects rather than a flat
bitmap.
const annotator = createAnnotator();const editable = await annotator.toEditablePng({ baseImage: await readFile("./screenshot.png"), annotationsSvg, tags: { source: "docs-tour", screen: "app-overview", capturedAt: new Date().toISOString(), },});await writeFile("./editable.png", editable);Image viewers that don’t know about the custom chunks display
the rasterised pixels verbatim — no compatibility loss versus
toPng. The bytes are still a valid PNG.
The optional tags are written verbatim into the XMP — no
validation, no transformation. Soft-convention key names that
built-in producers use (so downstream readers can key off the
same names when present):
| Key | Meaning |
|---|---|
source | What produced the PNG — e.g. "docs-tour", "playwright-fixture", "annot-mcp". |
screen | Living-product-docs <Screen id> value. |
capturedAt | ISO timestamp. |
commit | Git SHA when applicable. |
From a Playwright test
Section titled “From a Playwright test”For Playwright specs producing screenshots of a documented
screen, the @ingcreators/annot-product-docs package ships an
extended test fixture that bundles the whole flow (refresh
MDX snapshot → take raw screenshot → bake editable PNG → write
to disk) into one page.screenshot() call:
import { test } from "@ingcreators/annot-product-docs";
test("app overview", async ({ page }) => { await page.goto("https://annot.work/app/"); await page.screenshot({ path: "public/app/shots/app-overview.png", annot: { mdx: { id: "app-overview", path: "src/content/docs/app/index.mdx" }, tags: { source: "docs-tour", capturedAt: new Date().toISOString() }, }, });});If you don’t need MDX-linked screenshots, swap the import to
@ingcreators/annot-playwright — same annot: { overlays, tags, editable } surface, no Astro / MDX dependency.
The annot option is compositional — mdx / overlays / tags
/ editable are independent fields, each contributing to a
separate slice of the embedded XMP record. See
product-docs/playwright-fixture
for the full vocabulary, locator screenshot semantics, and the
codegen→hand-edit workflow.
Sanitisation
Section titled “Sanitisation”The annotator strips editor-internal artefacts from the input SVG fragment before composition:
<style data-annot-fonts>blocks- legacy base-image-in-wrapper structures
#ui-overlayand<g id="annotations">wrappers
This means it’s safe to pass output from the web app’s Save as SVG
straight in — the artefacts that exist only for the live editor
session are removed before rendering.