napkin-sketchdocs 1.0.0 GitHub

Writing a drawing out#

On this page

API hub · Reference · Quickstart · Cheatsheet

renderSketch writes a page and renderBook writes a book, as SVG, PNG, PDF, a .skbk the app opens, or an Illustrator script that rebuilds it. Both run with no DOM, in a browser or in plain Node, and add no dependency. This page is the reference for the formats, the box every format is cut to, and what a format says when it leaves something out. How a drawing is made is in Drawing with napkin script.

Contents

What this is#

One call, every format. A drawing from a script and a drawing from the app are the same kind of thing - a sketch book of pages, each a layer tree of marks - so both are written out the same way:

Every format is cut to one box, from one option: the ink, a box given outright, or a registration box every page shares.

The mental model#

 page ---renderSketch---> svg | png | pdf | skbk | jsx
 book ---renderBook-----> svg per page | png per page | one pdf | one skbk | one jsx

 PNG:  page --sketchToComposition--> composition --renderPng--> bytes

A script's crop and registration come back from evaluate in output, and spreading output into the options is what applies them:

const result = evaluate(text);
const pngs = renderBook(result.book, { format: 'png', ...result.output });

Reference#

Writing a page and a book#

import { writeFileSync } from 'node:fs';
import { evaluate, renderBook, renderSketch } from '@isocialpractice/napkin-sketch';

const result = evaluate(text, { name: 'card', timestamp: '2026-09-25T00:00:00.000Z' });
const page = result.book.sketches[0];

writeFileSync('card.svg', renderSketch(page));
writeFileSync('card.png', renderSketch(page, { format: 'png', scale: 2 }));
writeFileSync('card.pdf', renderBook(result.book, { format: 'pdf' }), 'latin1');
writeFileSync('card.skbk', renderBook(result.book, { format: 'skbk' }));
writeFileSync('card.jsx', renderBook(result.book, { format: 'jsx' }));
FormatrenderSketch givesrenderBook givesWrite it as
svga stringa string a page, in page orderUTF-8 text
pnga Uint8Arraya Uint8Array a page, in page orderbytes
pdfa stringone string, a PDF page a sketchlatin1, which keeps an embedded image's bytes
skbka string: a book of that one pageone string for the bookUTF-8 text
jsxa string: a script for that one pageone string, a document a pageUTF-8 text; it is all ASCII

An unknown format throws, naming the five: that is a mistake in the calling code, not in a drawing.

In Node, writeBook and drawToFiles from @isocialpractice/napkin-sketch/node write these as files, named and numbered - card.svg, or card-1.svg and on for a book of several pages - so a caller does not have to; see Using napkin-sketch from code. From a shell, napkin-sketch draw and render do the same; see The napkin-sketch command line.

Options#

OptionDefaultMeaning
format'svg'svg, png, pdf, skbk or jsx.
cropthe whole page'auto' for the ink, 'none', a box { x, y, width, height }, or a script's crop as output.crop gives it. See the box.
registrationnoneOne box every page is cut to. It wins over crop.
transparentfalseLeaves the paper out of an SVG, a PNG or an Illustrator script. A PDF keeps its paper, as the app's exports do.
scale1PNG pixels a page pixel: 2 for a retina asset.
resolveLinknoneReads a linked file so the PNG can draw it. resolveLinkFromDir(folder) is one.
decodeImagenoneDecodes a placed image the PNG cannot open itself: anything but a PNG.
linkFolderthe script's own folderWhere an Illustrator script looks for a linked file: a folder relative to the script, or absolute. writeBook and drawToFiles set it to the folder the links are read in.
onWarningnoneTold what a format left out or drew as a stand-in. See Warnings.

A .skbk is the whole document, so crop, registration and transparent do not apply to it, scale, resolveLink and decodeImage are the PNG's alone, and linkFolder is the Illustrator script's.

SVG#

The app's export writer, sketchToSvg, which the app's own SVG export calls:

PNG#

Drawn by the composition rasterizer: scanline coverage at four samples a pixel row, anti-aliased, with no canvas, no font engine and no dependency, and the same bytes for the same page every time.

PDF#

The app's PDF writer, sketchesToPdf: a PDF page a sketch, marks in vector with round caps and joins, and text in Helvetica, set as the type layout sets it with the built-in face: each stretch in one style placed on its line, in the bold and oblique faces where a run is, its underlines, strikethroughs and highlights drawn as shapes.

The .skbk file#

The book as the app saves it, which opens in the app with every layer, group and Bezier anchor where the script put them. It keeps the book's own timestamps rather than stamping the moment it was written, so with timestamp fixed, one script writes the same bytes on every run. A page written with renderSketch is a book of that one page, named after it.

The Illustrator script#

sketchesToJsx: one ExtendScript file that, run in Adobe Illustrator with File > Scripts > Other Script, builds the book out of Illustrator's own objects. Rebuilding a drawing in Illustrator has what becomes what, where a link is found, and what a run reports; in short:

It is one self-contained file, the images it embeds riding in it as base64, and the same page writes the same bytes on every run.

Effects#

A mark's, a layer's or a group's effects - see Drawing with napkin script - are drawn by every format that can draw them:

FormatEffects
SVGA <filter> on the mark's outermost element or the layer's group, in sRGB and in user space, and the list itself as data-effects, which napkin's importer reads back. A shadow that blends, laid under its mark or layer on its own, is a <use> of it before it; a mark whose fill and stroke take effects of their own is a group of the two, each with its filter. A Splatter's hatch is the lines it draws, a Warp's mark the shape it bends, and a 3D mark a group of its faces, each carrying the mark it stands for; a layer's or a group's Warp is on its group, its marks written bent
PNGRun over the mark's or the layer's own picture before it is laid down, as the SVG draws them, with the same graph of filter primitives
PDFNot drawn: what carries them prints without them, and onWarning names each one. A Splatter, a Warp and 3D are drawn, as the lines, the bent shapes and the faces they are
.skbkKept - aims and switched-off effects too - and drawn by the app
.jsxNot written: what carries them is drawn without them in Illustrator, and onWarning names each one. A Splatter is drawn as open paths, a Warp as the bent paths, and 3D as a group of its faces

On a layer with an eraser, the SVG and the PNG apply the effect before the eraser cuts, the order SVG gives a filter and a mask. The app's PDF export lists what it left out in a report in the corner of the window.

The box: crop and registration#

One box cuts every format: the SVG's view box, the PNG's page, the PDF's media box and the Illustrator script's artboard.

crop auto pad 12

inkBox(page) gives the ink's box, and renderBox(page, options) the box a page will be written in, or null for the whole page, for a caller that lays things out around the drawing.

Warnings#

onWarning is told what a format left out or drew as a stand-in, each message once a page, starting with the page's name:

FormatSays so when
PNGan image needs decodeImage; a link drew as its placeholder, and why; a color is not one it can paint; a shadow color is not one
PDFan image is not a JPEG; a gradient printed flat; a color is not a color; an effect printed plain (a Splatter, a Warp and 3D it draws)
.jsxan effect left off (a Splatter, a Warp and 3D it draws); an eraser drawn in the paper's color, or left out on a page with no paper; a color is not a color
SVGnothing: the SVG writes every mark

A warning never stops a render. The rest of the page is drawn, which is what a caller writing a hundred files wants.

From a sketch to a composition#

sketchToComposition(page, { crop, transparent, onWarning }) is the lowering behind the PNG. It gives back a composition the graphic design API renders and edits, for a caller that wants to add to a drawing with the composition calls - a title band, a watermark - before rendering it, or to write it with compositionToSvg.

MarkComposition element
A line (brush or marker)path: stroke, strokeWidth, round lineCap and lineJoin, dash, and opacity, the tool's own when the mark sets none: a marker's is 0.38, a Copic's 0.5
A one-point markcircle, its radius half the mark's width
A filled shapefill as a color or a gradient; stroke: null when its outline is off
A profiled marka filled path of its outline; with a fill as well, a group of the fill under the outline, the opacity on the group
A Copic marka filled path of its nib's footprint
A text itemtext set from its top, a line and a quarter apart, maxWidth its box
An imageimage stretched to its box (fit: 'fill')
A linkimage with link: true and its path as src
A layergroup with the layer's name and opacity; a hidden layer is left out
An eraserone of its layer group's erase shapes

Worked examples#

Frames that line up. A square turned a little further on each page, all written in one box, so the frames can be played in sequence without shifting:

napkin 1
page 400 300
name "spin"
registration 100 50 200 200
color #1f2328 width 3 fill #ffe08a
rect 170 120 60 60
repeat 3 as i {
  newpage
  rotate ((i + 1) * 22.5) at 200 150
  rect 170 120 60 60
}
const result = evaluate(spin, { timestamp: '2026-09-25T00:00:00.000Z' });
renderBook(result.book, { format: 'png', ...result.output }).forEach((png, i) => {
  writeFileSync(`spin-${i + 1}.png`, png);
});

Each frame is 200 by 200 pixels, cut from the same place on its page.

A sprite on nothing. A badge cut to its ink with room to spare, on no paper, at twice the size for a sharp screen:

napkin 1
page 400 400
crop auto pad 8
color #326478 width 4 fill #ffe08a
circle 200 200 60
text "A" at 200 172 size 48 align center
const result = evaluate(badge);
const png = renderSketch(result.book.sketches[0], { format: 'png', scale: 2, transparent: true, ...result.output });

Everything a run left out, in one list. A caller writing many drawings collects the warnings beside the diagnostics:

const warnings: string[] = [];
const pdf = renderBook(result.book, { format: 'pdf', onWarning: (message) => warnings.push(message) });
if (warnings.length > 0) console.warn(warnings.join('\n'));

Limits#

For agents#

See also#