napkin-sketchdocs 1.0.0 GitHub

The napkin script language#

On this page

API hub · Reference · Quickstart · Cheatsheet

Napkin script says what to draw without a pointer. A person or an AI helper writes it as text, one instruction to a line; a program in any language builds the same thing as JSON. Both become one list of instructions, the object form, which is what the rest of the API runs.

This page is the reference for the language itself: how a script is written, what its values look like, how expressions work, what the diagnostics mean, and the functions that read, check and write scripts. What each instruction draws, and the function that runs a script, are in Drawing with napkin script.

Contents

What this is#

A drawing language small enough to learn from one page and strict enough to check before anything runs. A script is a list of instructions such as page, layer, color, rect, text and repeat. Each one is a verb followed by its arguments, and every verb is described in one table, verbs.json, which the parser follows and every listing of the language is generated from.

The text form is for people and for AI helpers; it reads like a list of drawing steps and is forgiving about spacing and case. The object form is for programs: an array of plain objects, one per instruction, each naming its verb. A Python script, a C program or a shell pipeline builds the object form as JSON and never has to produce the text syntax. The two are interchangeable: a script read from text and the same script built as JSON come out as exactly the same instructions.

The mental model#

 text (.napkin) --parseScript-->  instructions  <--validateScript-- JSON (.napkin.json)
                                       |
                                  formatScript
                                       |
                                       v
                                 canonical text
StepFunctionTakesGives
Read textparseScript(text)a script as textthe instructions, and what was wrong
Check JSONvalidateScript(value)a parsed JSON valuethe same
Write textformatScript(script)instructionsthe script as canonical text

Neither reader throws. Each returns { ok, script, diagnostics }: script holds every instruction that was read without an error, diagnostics holds the errors and warnings in reading order, and ok is true when there were no errors. An instruction with an error is left out rather than half-read, so what comes back is always a well-formed script.

Reference#

A script, line by line#

napkin 1
page 400 300          # the page, in pixels
color #1f2328 width 3 # two paint instructions on one line
group "Figure" {
  layer "Head"
  circle 200 80 30
}
repeat 3 as i { rect (20 + i * 40) 200 30 30 }

Values#

KindWrittenIn JSONNotes
Number12, -3.5, 1e312Degrees for angles, clockwise.
Length120, 10mm, 0.5in, 12pt, 4px, 50%120, "10mm", "50%"A bare number is in the current units.
Expression(i * 40 + 20){ "expr": "i * 40 + 20" }Anywhere a number or a length goes.
String"Acme Corp""Acme Corp"\", \\, \n and \t are escapes.
Color#1f2328, steelblue, rgb(31, 35, 40)the same, as a stringWritten back in lower case.
Namegap, row_2"gap"For let and repeat ... as.
Choicedashed, a4, center"dashed"One word from the verb's list.
Switchon, offtrue, false
Points0 0, 10 0, 5 8[{ "x": 0, "y": 0 }, ...]Each coordinate is a length.
Stops(#ffffff 0, #000000 1), (red, blue)[{ "color": "#ffffff", "offset": 0 }, ...]Offsets from 0 to 1, or percentages.
Runs(0 4 face "Bold", 6 11 underline color red)[{ "start": 0, "end": 4, "face": "Bold" }, ...]Where each starts and ends, counting characters from 0, then its attributes.

Units. A length can carry px, in, mm or pt straight after its number. A bare number is read in the units in force, which are pixels until a units instruction changes them; the print units convert at 96 pixels to the inch, the ratio every export uses.

Shares of the page. 50% is half of the page, measured along the axis the argument runs on: an x coordinate or a width against the page's width, a y coordinate or a height against its height, and a size with no direction - a radius, a stroke width, a font size - against the shorter side. A percentage is refused where there is no page to measure it against, such as the page's own size or a let.

Strings. Double-quoted. \", \\, \n and \t are escapes; any other backslash is kept as written, so "C:\art\logo.svg" survives. A newline in a text item is written \n.

Colors. # and 3, 4, 6 or 8 hex digits; any of the 148 CSS color names; or rgb(), rgba(), hsl() and hsla(), written with no space before the (. The set is exactly what every output format can paint, the PNG included. none is not a color: the verbs that accept it spell it themselves, background none and fill none.

Gradient stops. A color and an offset, separated by commas inside parentheses. Give every stop an offset, as a number from 0 to 1 or a percentage, or give none and they are spaced evenly.

Runs. A text's runs give stretches of it styles of their own, separated by commas inside parentheses. Each is where it starts and where it ends - two whole numbers, counting the text's characters from 0, the end not included - then character's clauses and flags, and a size, a family and a color of its own: text "Acme Corp" at 20 20 runs (0 4 face "Bold", 5 9 color #c0392b underline). The text's first character sets its base, so the size, family and color font and color gave it may move to a run. A run may first name the Text Configuration it follows, config "<name>", which its other clauses override.

Text Configurations. textconfig "<name>" defines one, a vector editor's Character Style: character's clauses, but kern, with a size, a family and a color, under a name the book keeps. config "<name>" on a text has the whole text follow it, and in a run, that run; what the text or the run has of its own overrides it. A name is the same name in any case, and one no textconfig before it defined is unknown-config. Defined again, a configuration changes for every text that follows it.

Markdown Flow. The markdown flag on a text makes its text Markdown, shown as it renders: text "# Notes\n\n- one\n- **two**" at 40 60 markdown. A heading, code, a quote and a link follow the Markdown configurations, Markdown Heading 1 to 6, Markdown Code, Markdown Code Block, Markdown Quote and Markdown Link, which the book takes in; a textconfig of one of those names, before the text, restyles it. The browser-safe entry reads Markdown with the lexer it is given: importing @isocialpractice/napkin-sketch/markdown gives it marked's, as the command line and @isocialpractice/napkin-sketch/node have. With none, the text shows its Markdown as typed. as marks takes no markdown.

Clean Language Accents. accent <language> colors the next text as a language reads it - keywords, strings, numbers, comments, names - every paragraph of it: accent python then text "x = 1 # one" at 40 60. A language is its highlight.js id or a name it goes by (ts, py); one no word spells goes in quotes, accent "c++"; auto takes the language the code reads most like, and none colors nothing. It waits for the next text, and one nothing takes - another accent, the end of its block or a text drawn as marks comes first - is unused-accent. A Markdown Flow text's fenced code needs no accent: a fence that names its language is colored. The colors come from the highlighter the core is given, as the lexer does: @isocialpractice/napkin-sketch/markdown gives it highlight.js with its 36 common languages, and registerAccentLanguage installs more.

Type. A text is Sideless Typing from its anchor, the top of its first line; box sets it in a rectangle that grows to fit, inside "<path data>" in a shape, and along "<path data>" on a path, both drawn from the anchor; vertical sets it in columns that run right to left. character and paragraph set what every text after them has, as font does, until a reset; align on a text sets its paragraphs' alignment for it alone.

Expressions#

Anything computed goes in parentheses: (i * 40 + 20), (width / 2), (80 * cos(i * 30)). A bare name where a number is expected is an error that says to wrap it, (gap).

FunctionTakesGives
sina numberThe sine of an angle in degrees.
cosa numberThe cosine of an angle in degrees.
tana numberThe tangent of an angle in degrees.
sqrta numberThe square root.
absa numberThe value without its sign.
rounda numberThe nearest whole number.
floora numberThe whole number at or below.
ceila numberThe whole number at or above.
min2 or more numbersThe smallest of two or more values.
max2 or more numbersThe largest of two or more values.
let gap 24
repeat 12 as i {
  circle (200 + 80 * cos(i * 30)) (150 + 80 * sin(i * 30)) 6
}
rect 10mm 10mm (width - 20mm) (gap * 2)

An expression is checked when the script is read, so a missing operand or an unknown function is reported at its column before anything runs. A name that was never set, or a division by zero, can only be found when the expression runs.

The version line#

A script's first instruction names the language version it was written for:

napkin 1

Without it, version 1 is assumed and a version-missing warning says so. A version newer than this build reads is a version-unsupported error that names the newest it does. A version changes only when a script that ran under the old one would run differently under the new one; a new verb or a new optional field is an addition, and a script that uses one reports unknown-verb on an older build rather than drawing something else.

A snippet that is not a whole script - an example in the documentation, a block a program is assembling - is read with { fragment: true }, and then no napkin line is expected.

The budget#

One run of a script has a budget, so a loop that never ends, or a script that would draw more than any output can hold, stops rather than hanging the program that ran it:

LimitDefaultCounts
instructions200,000Instructions run, every pass through a repeat counted
marks50,000Marks drawn
anchors250,000Bezier anchors, across every mark
points1,000,000Points sampled from those anchors for the canvas to paint
depth64Blocks open at once: groups and clips, placed definitions, repeats, wipes and stacks

A run that reaches a limit stops with a budget-exceeded error at the instruction that crossed it, and keeps everything it drew before. Each pass through a repeat costs an instruction of its own, so even an empty body cannot spin forever. A caller that means to draw more raises a limit deliberately: evaluate(text, { limits: { marks: 200000 } }).

Sampled points are the limit that matters for drawings with many curves. A curved segment is sampled to its size, about a point a pixel and never more than 24, so a circle of radius 14 or more comes to 97 points and a thousand of them to 97,000.

The object form#

A script in JSON is an array of instructions. Each instruction is an object with a verb and the fields that verb has; the verb tables list them, ? marking the optional ones. This is the script from A script, line by line, built as JSON:

[
  { "verb": "napkin", "version": 1 },
  { "verb": "page", "width": 400, "height": 300 },
  { "verb": "color", "color": "#1f2328" },
  { "verb": "width", "width": 3 },
  {
    "verb": "group",
    "name": "Figure",
    "body": [
      { "verb": "layer", "name": "Head" },
      { "verb": "circle", "cx": 200, "cy": 80, "r": 30 }
    ]
  },
  {
    "verb": "repeat",
    "count": 3,
    "as": "i",
    "body": [{ "verb": "rect", "x": { "expr": "20 + i * 40" }, "y": 200, "width": 30, "height": 30 }]
  }
]

Verbs#

Document#

The page: its size, background and name, the unit bare numbers are read in, the seed, and more pages.

VerbWrittenFields in JSONWhat it does
napkinnapkin <version>versionThe language version the script was written for. The first line of a script; without it version 1 is assumed, with a warning.
pagepage <paper> [portrait|landscape]
page <width> <height> [portrait|landscape]
paper?, width?, height?, orientation?The page size: a named size (a3, a4, a5, letter, legal, tabloid, square, slide, napkin) or a width and a height, turned portrait or landscape.
backgroundbackground <color>
background none
colorThe page color, or none for a transparent page.
namename "<name>"nameThe page's name, and the file stem it is written under.
unitsunits px|in|mm|ptunitsThe unit bare numbers are read in from here on: px, in, mm or pt. Pixels until changed.
seedseed <n>seedThe seed for the hand-drawn pass. One script and one seed draw the same bytes every run.
newpagenewpage ["<name>"]name?Starts another page the same size, carrying the paint state across.

Layers#

Named layers and nested groups, with opacity, visibility and lock.

VerbWrittenFields in JSONWhat it does
layerlayer "<name>" [opacity <0-1>] [hidden] [locked]name, opacity?, hidden?, locked?A drawing layer. Marks after it go on it until the next layer.
groupgroup "<name>" [opacity <0-1>] [hidden] [locked] { ... }name, opacity?, hidden?, locked?, bodyA group layer. The layer and group lines in its block become its children.
clipclip ["<name>"] { ... }name?, bodyA clip group: everything its block draws shows only inside the block's last closed shape, in paint order, which paints nothing while it clips - the app's Make Clipping Mask. Without a closed shape in it, the block is a plain group.

Paint#

What every mark after it is drawn with: tool, color, width, opacity, fill, gradient, outline, dash, profile, nib, pencil, font, the hand-drawn pass, and the character, paragraph, Text Configuration and code accent of the text after it.

VerbWrittenFields in JSONWhat it does
tooltool pen|marker|copic|pencil|eraser|brushtoolThe kind of mark: pen (the app's Brush; brush is taken for it), marker (translucent, so overlapping passes build up like ink), copic (a broad, angled nib), pencil (a lead through the paper's grain; pencil picks one) or eraser (cuts its line's swath out of the marks drawn before it on its layer, as the app's Eraser does, and draws nothing of its own).
colorcolor <color>colorThe ink color of every mark after it.
widthwidth <length>widthThe stroke width of every mark after it.
opacityopacity <0-1>opacityThe opacity of every mark after it, from 0 to 1.
fillfill <color>
fill none
fillThe fill color of closed shapes after it, or none.
gradientgradient linear <angle> (<color> <offset>, ...)
gradient radial (<color> <offset>, ...)
gradient none
type, angle?, stops?A gradient fill for closed shapes after it: linear at an angle, or radial from the centre out, or none.
strokestroke on|offonTurns the outline on or off. With it off, a closed shape is its fill alone.
stylestyle solid|dashed|dottedstyleThe outline's dash: solid, dashed or dotted.
profileprofile uniform|rounded|tapered|waveprofileHow the width runs along a brush or marker stroke: uniform, rounded, tapered or wave.
nibnib <degrees>angleThe angle of the copic nib, in degrees.
pencilpencil <grade> [<color>]pencil, color?Takes up the Pencil at a grade: graphite alone, 9H to 9B, or charcoal (HB to 6B), compressed (2B to 6B) or vine (hard, medium, soft) and its grade. Every mark after it is drawn with that lead through the paper's grain, in its tone - or in the color given, a colored pencil. The width is the line's own; the app's softer leads draw broader at one tool width.
roughrough <0-1> [passes 1|2] [overshoot <length>]
rough off
amount, passes?, overshoot?The hand-drawn pass for every mark after it: 0 is exact, 1 is clearly drawn by hand, and off is 0. Seeded, so it draws the same way every run.
fontfont "<family>" [<size>]
font <size>
family?, size?The font family and size of text after it.
charactercharacter [face "<face>"] [tracking <n>] [kern <n>] [kerning auto|none] [leading <length>] [spacing <n>%] [caps normal|all|small] [hscale <n>%] [vscale <n>%] [shift <length>] [rotate <degrees>] [highlight <color>] [super|sub] [underline] [strike]
character reset
face?, tracking?, kern?, kerning?, leading?, spacing?, caps?, hscale?, vscale?, shift?, rotate?, highlight?, position?, underline?, strike?, reset?The character attributes of text after it: its face, tracking, kerning, leading, word spacing, capitals, scales, baseline shift, turn and highlight, and whether it is a superscript or a subscript, underlined or struck through. reset puts every one back.
paragraphparagraph [align left|center|right|justify] [indent <left> [<right>]] [first <length>] [before <length>] [after <length>] [hyphenate|nohyphenate] [list bullet|number|none] [level <n>]
paragraph reset
align?, indent?, indentRight?, first?, before?, after?, hyphenate?, list?, level?, reset?The paragraph attributes of text after it: its alignment, its indents, the space before and after it, whether it hyphenates, and a bulleted or numbered list at a level. reset puts every one back.
textconfigtextconfig "<name>" [face "<face>"] [tracking <n>] [kerning auto|none] [leading <length>] [spacing <n>%] [caps normal|all|small] [hscale <n>%] [vscale <n>%] [shift <length>] [rotate <degrees>] [highlight <color>] [super|sub] [underline] [strike] [size <size>] [family "<family>"] [color <color>]name, face?, tracking?, kerning?, leading?, spacing?, caps?, hscale?, vscale?, shift?, rotate?, highlight?, position?, underline?, strike?, size?, family?, color?Defines a Text Configuration, a vector editor's Character Style: a named set of character attributes the book keeps, which text follows with config and its runs with config in their clauses. What a text has of its own overrides it. Defined again under its name, it changes for every text that follows it.
accentaccent <language>languageA Clean Language Accent for the next text: its paragraphs are colored as a language reads them - keywords, strings, numbers, comments, names - live as the app edits them. A language by its id or a name highlight.js knows it by (a name no word spells in quotes, "c++"), auto for the one the code reads most like, or none. It waits for the next text; one nothing takes is reported.

Shapes#

Marks built from Bezier anchors: rectangles, circles, ellipses, lines, polygons, stars, arcs, spirals and the shape library, and marks combined as the Wipe Stacks and the Shape Stacker combine them.

VerbWrittenFields in JSONWhat it does
rectrect <x> <y> <width> <height> [r <radius>]x, y, width, height, radius?A rectangle, with rounded corners when r is given.
circlecircle <cx> <cy> <r>cx, cy, rA circle, drawn as four cubic curves rather than a polygon.
ellipseellipse <cx> <cy> <rx> <ry>cx, cy, rx, ryAn ellipse, from its centre and two radii.
lineline <x1> <y1> <x2> <y2>x1, y1, x2, y2A straight line between two points.
polygonpolygon <x> <y>, <x> <y>, ... [r <radius>]points, radius?A closed shape through a list of points, with rounded corners when r is given.
polylinepolyline <x> <y>, <x> <y>, ...pointsAn open line through a list of points.
starstar <cx> <cy> <outer> <inner> <count>cx, cy, outer, inner, countA star with the given number of points, between an outer and an inner radius.
arcarc <cx> <cy> <r> <from> <to>cx, cy, r, from, toAn open arc of a circle between two angles, in degrees clockwise from three o'clock.
spiralspiral <cx> <cy> <r> <turns>cx, cy, r, turnsA spiral winding out from its centre to the radius over the given number of turns.
shapeshape "<name>" at <x> <y> size <width> [<height>]name, x, y, width, height?A named shape from the built-in library, drawn in a box from x, y. With one size its longer side is that long and it keeps its proportions; with a width and a height it is stretched to fill them.
wipewipe in|out-front|out-back|mid|outer|clean { ... }op, bodyCombines the marks its block draws, as the app's Wipe Stacks do, and draws what is left in their place: in (Wipe In, their union), out-front (Subtract Top from Below: the bottom one less the rest), out-back (Subtract Below from Top: the top one less the rest), mid (Mid Wipe: where all of them overlap), outer (Outer Wipes: where an odd number overlap) or clean (Clean Wipe: every piece of their overlaps, a mark each). What is left is painted as the topmost mark, or as the bottom one for out-front; text, pictures and eraser marks in the block are passed over. The block keeps its paint and transforms to itself, as a group does, and effects written just before it go on what it leaves.
stackstack merge|remove <points> { ... }mode, points, bodyStacks the marks its block draws as the app's Shape Stacker does, at the pieces under its points - the places where the marks overlap and where they do not: merge makes those pieces one shape, painted as the topmost mark over the first point, and each mark keeps what was not merged; remove takes them away from every mark. Text, pictures and eraser marks in the block are passed over. The block keeps its paint and transforms to itself, as a group does, and the points are where the stack line is.

Paths#

Marks written segment by segment, from SVG path data or from a block of steps, curves through points, and paths cut where a point lands on them.

VerbWrittenFields in JSONWhat it does
pathpath "<svg path data>"
path { ... }
d?, body?One mark, from SVG path data or from a block of path steps. Each subpath of a compound path is one piece of the same mark.
movemove <x> <y>x, yStarts a new subpath at a point.
toto <x> <y>x, yA straight segment to a point.
byby <dx> <dy>dx, dyA straight segment by an offset from the current point.
curvecurve <c1x> <c1y> <c2x> <c2y> <x> <y>c1x, c1y, c2x, c2y, x, yA cubic curve to a point, steered by two handles.
smoothsmooth <c2x> <c2y> <x> <y>c2x, c2y, x, yA cubic curve whose first handle mirrors the last one, so the join stays smooth.
throughthrough <x> <y>, <x> <y>, ...pointsA smooth curve through a list of points. Inside a path block it continues the subpath; outside one it is an open mark of its own.
closeclosenoneCloses the current subpath back to its start.
splitsplit <x> <y>x, yCuts the topmost mark drawn so far whose path passes within 4 px of a point, where the point lands on it, as the app's Split does: an open path becomes two, a closed one opens there, and a compound shape gives up the ring that was cut, as an open mark of its own. Nothing moves, and the pieces take the mark's place on its layer. In a wipe or a stack block it cuts among the marks the block has drawn.
smearsmear <width> <strength> <x> <y>, <x> <y>, ...width, strength, pointsA pass of the Smear, a blending stump, along a list of points: it spreads the graphite of every pencil mark drawn so far that it reaches, along the drag, as the app's Smear does. The width is the stump's, scaled by the transform as a width is, and the strength from 0 to 1. In a wipe or a stack block it smears among the marks the block has drawn.
warpwarp <x> <y> <radius> <dx> <dy>x, y, radius, dx, dyLiquify's Warp, as the app's Liquify does it: a brush pushed from the point by dx and dy, carrying what is under its centre the whole way, like clay. The brush reaches the marks drawn so far whose outline passes within its radius of the point, under the transform as a mark drawn there would be, and its effect falls off smoothly to nothing at the rim; the radius scales with the transform. Pencil marks are the Smear's, and text, pictures and eraser marks have no outline to bend: all are left as they are. Each mark it bends is fitted again after, so its anchors stay few. In a wipe or a stack block it bends among the marks the block has drawn.
twirltwirl <x> <y> <radius> <degrees>x, y, radius, amountLiquify's Twirl: turns what is under a brush at the point about its centre, by the angle in degrees there - clockwise on the screen, as rotate turns - and less toward the rim, so distances from the centre are kept. The brush reaches the marks drawn so far whose outline passes within its radius of the point, under the transform as a mark drawn there would be, and its effect falls off smoothly to nothing at the rim; the radius scales with the transform. Pencil marks are the Smear's, and text, pictures and eraser marks have no outline to bend: all are left as they are. Each mark it bends is fitted again after, so its anchors stay few. In a wipe or a stack block it bends among the marks the block has drawn.
puckerpucker <x> <y> <radius> <amount>x, y, radius, amountLiquify's Pucker: draws what is under a brush at the point in toward its centre, a point near the centre by the amount, 0 to 1, of its distance. The brush reaches the marks drawn so far whose outline passes within its radius of the point, under the transform as a mark drawn there would be, and its effect falls off smoothly to nothing at the rim; the radius scales with the transform. Pencil marks are the Smear's, and text, pictures and eraser marks have no outline to bend: all are left as they are. Each mark it bends is fitted again after, so its anchors stay few. In a wipe or a stack block it bends among the marks the block has drawn.
bloatbloat <x> <y> <radius> <amount>x, y, radius, amountLiquify's Bloat: pushes what is under a brush at the point out from its centre, a point near the centre by the amount, 0 to 1, of its distance. The brush reaches the marks drawn so far whose outline passes within its radius of the point, under the transform as a mark drawn there would be, and its effect falls off smoothly to nothing at the rim; the radius scales with the transform. Pencil marks are the Smear's, and text, pictures and eraser marks have no outline to bend: all are left as they are. Each mark it bends is fitted again after, so its anchors stay few. In a wipe or a stack block it bends among the marks the block has drawn.

Transforms#

Translate, rotate, scale and mirror, baked into the anchors of every mark after them, with push and pop to undo them.

VerbWrittenFields in JSONWhat it does
pushpushnoneSaves the transform, the paint state and the units, for the next pop to restore.
poppopnoneRestores the transform, the paint state and the units the last push saved.
translatetranslate <dx> <dy>dx, dyMoves everything drawn after it.
rotaterotate <degrees> [at <x> <y>]angle, cx?, cy?Turns everything drawn after it, clockwise in degrees, about the origin or a point given with at.
scalescale <s> [<sy>] [at <x> <y>]sx, sy?, cx?, cy?Scales everything drawn after it, about the origin or a point given with at. One factor scales both ways.
mirrormirror x|y [at <position>]axis, about?Reflects everything drawn after it: x swaps left and right across a vertical line, y swaps top and bottom across a horizontal one, at 0 unless given.

Control#

Named numbers, reusable definitions, and repetition.

VerbWrittenFields in JSONWhat it does
letlet <name> <value>name, valueNames a number for the expressions after it, in the current units.
definedefine "<name>" { ... }name, bodyRecords a block of drawing under a name without drawing it, for place to draw.
placeplace "<name>" [at <x> <y>] [scale <s>] [rotate <degrees>]name, x?, y?, scale?, rotate?Draws a definition, moved, scaled or turned.
repeatrepeat <count> [as <name>] { ... }count, as?, bodyRuns a block a number of times, counting from 0 in the name given with as.

Text and media#

Text, placed images, linked files, and documents the host supplied.

VerbWrittenFields in JSONWhat it does
texttext "<text>" at <x> <y> [size <size>] [box <width>] [inside "<path data>"] [along "<path data>"] [start <length>] [flip] [effect rainbow|skew|ribbon|stair|gravity] [align left|center|right|justify] [vertical] [config "<name>"] [runs (<start> <end> [config "<name>"] <character clauses>, ...)] [markdown] [as marks]text, x, y, size?, box?, inside?, along?, start?, flip?, effect?, align?, vertical?, config?, runs?, markdown?, asMarks?A text item, set and aligned, and editable in the app: Sideless Typing from its anchor, Sheet Typing in a box or inside a shape, or Path Typing along a path, set in its runs' styles, the whole of it following a Text Configuration with config. With markdown its text is Markdown, shown as it renders: Markdown Flow. With as marks the letters are drawn as brush strokes instead.
imageimage "<asset or data URL>" at <x> <y> [size <width> [<height>]]src, x, y, width?, height?Places an image: an asset the host supplied, by name, or a data URL. Nothing is read from disk.
linklink "<path>" at <x> <y> [size <width> [<height>]] [name "<layer name>"]href, x, y, width?, height?, name?Places a linked file: one selectable element that references the file instead of drawing its contents into the page.
useuse "<document>" [at <x> <y>] [scale <s>] [layer "<name>"]name, x?, y?, scale?, layer?Copies in a document the host supplied, as a group, with its layers and anchors.

Effects#

Effects for the next layer, group or mark - blur, color, shadows, glows, bevels, emboss and feather, Splatter, Warp and 3D - and the blend mode of the next layer or group.

VerbWrittenFields in JSONWhat it does
effecteffect blur <radius>
effect brightness <amount>
effect contrast <amount>
effect saturate <amount>
effect grayscale [<0-1>]
effect sepia [<0-1>]
effect invert [<0-1>]
effect hue-rotate <degrees>
effect opacity <0-1>
effect drop-shadow <dx> <dy> <blur> <color> [spread <0-100>] [blend <mode>] [opacity <0-1>]
effect feather <radius>
effect bevel [inner|outer] [size <n>] [depth <percent>] [angle <degrees>] [altitude <degrees>] [soften <n>] [up|down] [light <color> [<0-1>]] [shade <color> [<0-1>]] [light-blend <mode>] [shade-blend <mode>]
effect emboss [emboss|pillow] [size <n>] [depth <percent>] [angle <degrees>] [altitude <degrees>] [soften <n>] [up|down] [light <color> [<0-1>]] [shade <color> [<0-1>]] [light-blend <mode>] [shade-blend <mode>]
effect inner-shadow <dx> <dy> <blur> <color> [choke <0-100>] [blend <mode>] [opacity <0-1>]
effect outer-glow [<blur>] [<color>] [spread <0-100>] [blend <mode>] [opacity <0-1>]
effect inner-glow [<blur>] [<color>] [choke <0-100>] [edge|center] [blend <mode>] [opacity <0-1>]
effect splatter [preset <name>] [angle <degrees>] [overlap <n> [<variation>]] [width <n>] [curviness <percent> [<variation>]] [spacing <n> [<variation>]] [seed <n>]
effect warp <style> [bend <percent>] [horizontal|vertical] [distort <percent> [<percent>]] [part whole|lower|upper]
effect extrude [<depth>] [rotate <x> <y> <z>] [perspective <degrees>] [shading plastic|diffuse|none|wireframe] [hollow] [light <azimuth> <elevation> [<intensity> [<ambient>]]] [position <x> <y> <z>] [scale <x> <y> <z>]
effect revolve [angle <degrees>] [offset <n>] [axis left|right] [segments <n>] [rotate <x> <y> <z>] [perspective <degrees>] [shading plastic|diffuse|none|wireframe] [hollow] [light <azimuth> <elevation> [<intensity> [<ambient>]]] [position <x> <y> <z>] [scale <x> <y> <z>]
effect none
type, radius?, amount?, angle?, dx?, dy?, blur?, color?, spread?, choke?, blend?, opacity?, style?, size?, depth?, altitude?, soften?, direction?, lightColor?, lightOpacity?, lightBlend?, shadeColor?, shadeOpacity?, shadeBlend?, source?, preset?, overlap?, overlapVariation?, width?, curviness?, curvinessVariation?, spacing?, spacingVariation?, seed?, bend?, orientation?, distortH?, distortV?, part?, hollow?, offset?, axis?, segments?, rotateX?, rotateY?, rotateZ?, positionX?, positionY?, positionZ?, scaleX?, scaleY?, scaleZ?, perspective?, shading?, lightAzimuth?, lightElevation?, lightIntensity?, lightAmbient?, target?, hidden?An effect for the next layer, group or mark the script makes: a CSS filter function - blur, brightness, contrast, saturate, grayscale, sepia, invert, hue-rotate, opacity or drop-shadow, which is Outer Shadow - an appearance effect - feather, bevel, emboss, inner-shadow, outer-glow or inner-glow - or splatter, a vector editor's Scribble, which draws a mark's fill as a loose hatch (on a layer or a group it draws nothing) - or warp, a vector editor's Warp, which bends what takes it by a style, a layer's or a group's marks as one - or extrude and revolve, a vector editor's Classic 3D, which build a mark in depth and draw it as shaded faces (on a layer or a group they draw nothing). Any of them also takes on fill or on stroke, to work on that part of a mark alone, and hidden, to be kept without being drawn. Several stack in order, and none clears what is waiting.
blendblend <mode>modeHow the next layer or group the script makes mixes with what is drawn under it, the page's background included: a Napkin Blend by its name or by Illustrator's - stack or multiply, sheer or screen, cover or luminosity. A blend is a layer's, so it waits for the next layer or group; normal sets one back.

Output#

Hints the renderers read: the box every output is cut to, and one box every page shares.

VerbWrittenFields in JSONWhat it does
cropcrop auto [pad <length>]
crop none
crop <x> <y> <width> <height>
mode, pad?, x?, y?, width?, height?The box every output is cut to: auto fits the ink with room to spare, none keeps the page, or a box given outright.
registrationregistration <x> <y> <width> <height>x, y, width, heightOne box every page is written in, so frames drawn by one script line up when they are played in sequence.

Diagnostics#

A diagnostic is { level, code, message, line?, column?, index?, verb?, expected? }. Text scripts point at a line and column, both 1-based; JSON scripts point at an index path, [3, 0] being the first instruction in the body of the fourth. A program matches on code, which is never renamed or reused. The message is one sentence for a person and may change.

Printed with formatDiagnostic, a diagnostic takes the shape editors and CI parsers already read:

card.napkin:3:1: error unknown-verb: `circl` is not a verb. Did you mean `circle`?
card.napkin.json[1][0]: error expected-length: `x` of `rect` at [1][0]: `ten` is not a length, such as `120`, `"10mm"` or `"50%"`.
CodeLevelMeaning
unknown-verberrorA line starts with a word that is not a verb.
misplaced-verberrorA verb where it cannot appear: a path step outside a path block, a napkin line after the first, a layer inside a path, a newpage inside a group or a definition, a layer, group, clip or use inside a wipe or a stack.
unsupported-verberrorThe verb, or this way of writing it, is known but not drawn by this build or by the host running the script; the instruction is skipped.
expected-numbererrorA number, or an expression in parentheses, was expected.
expected-lengtherrorA length was expected: a number, a number with a unit (10mm, 0.5in, 12pt, 4px), a percentage, or an expression.
expected-stringerrorA double-quoted string was expected.
expected-identifiererrorA bare name was expected: letters, digits and underscores, starting with a letter.
expected-colorerrorA CSS color was expected, such as #1f2328, red or rgb(31, 35, 40).
expected-choiceerrorOne word from a fixed list was expected; the message lists them.
expected-pointserrorA list of points was expected: x y pairs separated by commas.
expected-stopserrorA list of gradient stops was expected: (color offset, color offset, ...).
unexpected-tokenerrorSomething is left on the line after the instruction was read, or a character that cannot start anything.
missing-argumenterrorA required argument is missing; the message names it and shows how the verb is written.
expected-blockerrorA block was expected: { at the end of the line, the instructions it holds, and } to close it.
unclosed-blockerrorA block opened with { is never closed with }.
unexpected-closeerrorA } closes a block that was never opened.
unclosed-stringerrorA string runs to the end of the line without its closing quote.
invalid-expressionerrorAn expression could not be read: an unknown operator, a missing operand, or an unclosed parenthesis.
unit-unknownerrorA number carries a unit the language does not know. The units are px, in, mm and pt, and % for a share of the page.
invalid-valueerrorA value is out of range: an opacity above 1, a negative radius, a star with fewer than 2 points, a percentage where the page cannot measure one.
version-missingwarningThe script does not start with napkin <version>; version 1 is assumed.
version-unsupportederrorThe script names a version newer than this build reads; the message names the newest it does.
unknown-variableerrorAn expression uses a name no let or repeat has set.
unknown-definitionerrorplace names something no define recorded.
unknown-shapeerrorshape names something the shape library does not have.
unknown-asseterrorimage names an asset the host did not supply.
unknown-documenterroruse names a document the host did not supply.
unknown-configerrorconfig names a Text Configuration no textconfig before it defined.
image-size-unknownwarningAn image's size cannot be read from its data, so it is placed square: 100 by 100, or the one size given by itself.
glyph-missingwarningtext as marks has a character the built-in face does not draw; it is left as a gap.
unknown-fieldwarningAn instruction in a JSON script has a field its verb does not have; it is left out.
recursive-definitionerrorA definition places itself, directly or through another definition.
division-by-zeroerrorAn expression divides by zero.
budget-exceedederrorThe script ran more instructions, drew more marks, emitted more anchors or sampled points, or nested deeper than the limit allows; what it drew up to that point is kept.
pop-without-pusherrorpop with nothing saved to restore.
unbalanced-pushwarningA push is never popped before its block ends; the saved state is restored there anyway.
duplicate-definitionwarningA second define replaces an earlier one of the same name.
link-unresolvedwarningA linked file could not be read for an output that has to draw it, so its placeholder was drawn instead.
unused-effectwarningAn effect nothing took: no layer, group or mark followed it before its block, its page or the script ended.
unused-blendwarningA blend nothing took: no layer or group followed it before another blend, its block, its page or the script ended. A blend is a layer's, so a mark alone does not take it.
unused-accentwarningAn accent nothing took: no text followed it before another accent, its block, its page or the script ended, or the text was drawn as marks.
wipe-skippedwarningA wipe's or a stack's block drew marks it cannot combine - text, pictures, linked files, eraser marks - and they are drawn as they are.
wipe-emptywarningA wipe or a stack left nothing: out-front's bottom mark wholly covered by the rest, say, or a stack remove taking every piece.
wipe-failedwarningA wipe or a stack could not combine its block's marks - fewer than two it can combine, more than it takes, or geometry it could not make - so they are drawn as they are.
stack-missedwarningA stack point is on none of the pieces its block's marks make, so it picks nothing; the message says which.
split-missedwarningA split point is within 4 px of no path drawn so far, or lands on the end of an open one, so nothing is cut.
clip-openwarningA clip block drew no closed shape to clip with, so it is a plain group.
smear-missedwarningA smear reaches no pencil mark drawn so far, so it spreads nothing.
liquify-missedwarningA warp, twirl, pucker or bloat reaches no mark it bends drawn so far, so it bends nothing.
erase-skippedwarningAn eraser line reaches text or a picture, which it cannot cut, and passes over it.

Functions#

Exported from @isocialpractice/napkin-sketch, the package's browser-safe entry, and DOM-free; the source is src/core/script/index.ts.

parseScript(text: string, options?: { fragment?: boolean }): ParseResult
validateScript(value: unknown, options?: { fragment?: boolean }): ParseResult
formatScript(script: Instruction[], options?: { indent?: string; comments?: Map<object, string[]> }): string

interface ParseResult {
  ok: boolean;               // no errors: every instruction is in `script`
  script: Script;            // the instructions that were read
  diagnostics: Diagnostic[]; // errors and warnings, in reading order
}

parseExpression(source: string): ExprParse   // { ok: true, node } or { ok: false, code, message, index }
evaluateExpression(node: ExprNode, scope: ExprScope): number   // throws ExpressionError
formatDiagnostic(diagnostic: Diagnostic, file?: string): string

formatScript throws on an instruction it cannot write, which only an unchecked script can contain; check a script built by hand with validateScript first. comments puts # lines before chosen instructions, keyed by the instruction object itself and written at its indent, inside a block too; the parser skips them, as it skips every comment. The verb table itself is exported as VERBS, with verbSpec(name) to look one verb up.

Worked examples#

Reading a script with mistakes. Every mistake is reported, each at its line and column, and the instructions without one are still read:

# expect: unknown-verb, missing-argument, expected-length
napkin 1
circl 50 50 10
rect 0 0 10
color #1f2328 width three
line 0 0 10 10
card.napkin:3:1: error unknown-verb: `circl` is not a verb. Did you mean `circle`?
card.napkin:4:12: error missing-argument: `rect` needs `height`: rect <x> <y> <width> <height> [r <radius>].
card.napkin:5:21: error expected-length: `width` takes a length, such as `120`, `10mm` or `50%`, and got `three`. To use a name, write it in parentheses: `(three)`.

That is what printing each diagnostic gives. The script that comes back holds napkin, color and line, and ok is false:

import { formatDiagnostic, parseScript } from '@isocialpractice/napkin-sketch';

const { ok, script, diagnostics } = parseScript(text);
for (const d of diagnostics) console.error(formatDiagnostic(d, 'card.napkin'));

Building a script in another language. A program writes JSON and hands it over; nothing in it has to know the text syntax. From Python:

import json

script = [{"verb": "napkin", "version": 1}, {"verb": "page", "paper": "slide"}]
for i in range(5):
    script.append({"verb": "rect", "x": {"expr": f"80 + {i} * 360"}, "y": 400, "width": 300, "height": 200})

with open("deck.napkin.json", "w") as f:
    json.dump(script, f)

Read back, it validates to the same instructions as the text it corresponds to, and formatScript writes that text:

napkin 1
page slide
rect (80 + 0 * 360) 400 300 200

Limits#

For agents#

See also#