napkin-sketchdocs 1.0.0 GitHub

Drawing with napkin script#

On this page

API hub · Reference · Quickstart · Cheatsheet

evaluate runs a napkin script and gives back a drawing: a sketch book of pages, each with a layer tree and the marks on it. This page is the reference for what each instruction draws and how state carries through a script. How a script is written - its syntax, values, expressions and diagnostics - is in The napkin script language.

Contents

What this is#

A generated drawing is made of the same things a drawn one is. Every mark is the Stroke the app commits when you draw: Bezier anchors in vector, the points the canvas paints sampled from them, and the paint fields - color, width, fill, gradient, dash, profile, nib. Layers and groups are the app's own layer rows. So a script's drawing opens in the app as editable work, with its layers and anchors intact, and is written out by the app's own SVG and PDF writers, and as a PNG, by one call.

A run never throws for anything in the script. An instruction that cannot run is reported and skipped, and the run carries on; only the budget stops a run, and then it keeps what it drew. One script gives the same book every time it runs: ids are counted rather than random, and the time stamped on the book can be fixed.

The mental model#

 script (text or JSON) --evaluate--> sketch book --renderSketch, renderBook--> SVG, PNG, PDF, .skbk, .jsx

Three kinds of state carry from one instruction to the next - the paint, the transform and the units - along with the names let sets and the layer marks go to. Blocks decide how far a change reaches:

BlockWhat it keeps to itself
group "G" { ... }Everything: paint, transform, units and the current layer come back when it ends.
place "d"Everything, the same way; a definition is a drawing of its own.
repeat n { ... }Nothing: each pass starts where the last one ended, and what the last pass left stays.
push ... popPaint, transform and units, between the two.

So translate in a repeat walks across the page, and push and pop in the body keep a pass to itself.

Reference#

Running a script#

import { evaluate, renderSketch } from '@isocialpractice/napkin-sketch';

const { ok, book, diagnostics, stats, output } = evaluate(text, { name: 'card' });
const svg = renderSketch(book.sketches[0], output);

evaluate takes a script as text, or as a JSON value in the object form, which it checks with validateScript first.

OptionDefaultMeaning
namedrawingThe book's name and its first page's. Later pages are drawing-2, drawing-3, unless newpage names them.
seed0The seed for the hand-drawn pass until a seed instruction names one: a whole number.
limitsthe defaultsRaises or lowers any limit of the budget.
assetsnoneImages a script places by name, as data URLs: { logo: 'data:image/png;base64,...' }.
documentsnonePages or books a script copies in by name with use.
resolveLinknoneReads a linked file, so link can take its size from it; resolveLinkFromDir(folder) is one.
timestampnowThe time stamped on the book and its pages. Fix it for a book that is the same byte for byte on every run.
fragmentfalseRead a snippet: no napkin line is expected.
ResultMeaning
okTrue when nothing was an error, reading or running.
bookThe drawing: always at least one page, however much of the script ran.
diagnosticsEverything reported, reading and running together, in reading order.
statsinstructions, marks, anchors, points and pages: how much the run did.
outputWhat crop and registration asked of the outputs, in pixels. Spread it into the render options to apply it.
seedThe seed the run ended on.

A script that names a language version newer than this build reads is not run at all, since it may mean something this build would draw differently.

drawSvg(source, options) runs a script and gives back its first page as SVG in one call, and in Node, drawFile and drawToFiles from @isocialpractice/napkin-sketch/node run one and write every format to files; see Using napkin-sketch from code.

runScript(script, sink) runs a checked script against any ScriptSink: an object that starts pages, makes layers current, opens and closes groups, and adds marks. evaluate uses a SketchSink, which builds the book; a sink that writes somewhere else gets exactly the same calls.

Pages#

A script starts on a page the size a new sketch opens at in the app, 1280 by 800 pixels, on napkin paper, #fcfaf5. page sizes it: a named size, or a width and a height, turned portrait or landscape. background sets its color, background none makes it transparent, and name names it.

newpage starts another page the same size and color. The paint and the units carry across; the transform starts again from nothing, so each page draws from its own corner. A newpage belongs at the top of a script or in a repeat there, never inside a group or a definition.

page a5 landscape
background #ffffff
name "front"
circle 50% 50% 30%
newpage "back"
rect 10% 10% 80% 80% r 12

Layers and groups#

Marks go to the current layer. A page starts with Layer 1, which is left out of the finished page when nothing was drawn on it and the script named layers of its own.

layer "Paper"
rect 0 0 100% 100%
group "Figure" opacity 0.9 {
  layer "Body"
  ellipse 200 260 60 90
  layer "Head"
  circle 200 140 40
}
layer "Paper"
line 0 400 400 400

Clipping masks#

clip ["<name>"] { ... } makes a clip group, as the app's Make Clipping Mask does: a group, named Clip Group unless given a name, whose content shows only inside the last closed shape its block draws, in paint order. That shape is the clipping path. It paints nothing while it clips, and keeps its paint for the app's Release Clipping Mask to show.

clip "Window" {
  fill #27486d
  rect 20 20 200 140
  stroke off
  circle 120 90 60
}

Paint#

Each paint instruction sets one field of every mark after it:

InstructionMark fieldNotes
tooltoolpen, the app's Brush (tool brush is the same: the app calls the pen the Brush, and files keep pen); marker, translucent; copic, a broad angled nib; pencil, a lead through the paper's grain; eraser, which cuts rather than draws: see Erasing.
colorcolorAny color the language accepts.
widthwidthScaled by the transform's average scale.
opacityopacityLeft unset, the tool's own default applies, as in the app.
fillfillClosed marks only.
gradientgradientClosed marks only; a linear gradient's angle turns with the drawing.
stroke offnoStrokeA closed shape drawn as its fill alone.
stylestrokeStylesolid, dashed or dotted.
profileprofileBrush (pen) and marker marks only.
nibnibAngleCopic marks only; the angle turns with the drawing.
penciltool, pencil, colorThe Pencil at a grade; see Pencils.
rough-Draws the marks after it by hand; see The hand-drawn pass.
fontfontFamily, fontSizeThe family and size of text after it.
charactertypeset.runsThe character attributes of text after it: its face, tracking, kerning, leading, word spacing, capitals, scales, baseline shift, turn and highlight, a superscript or a subscript, an underline and a strikethrough. character reset takes them off.
paragraphtypeset.parasThe paragraph attributes of text after it: its alignment, indents, the space before and after it, hyphenation, and a bulleted or numbered list. paragraph reset takes them off.
textconfigthe book's textConfigsA Text Configuration: character's clauses, with a size, a family and a color, under a name; see Text.
accenttypeset.parasThe next text's code colored as a language reads it; see Text.

Every mark is flagged sharpened, so the app's auto-sharpen leaves a generated shape as it is.

Pencils#

pencil <grade> [<color>] takes up the app's Pencil at a grade: every mark after it is drawn with that lead through the paper's grain, in the grade's tone, as the app draws it (core/pencil.ts).

width 3
pencil 2H
line 20 20 380 20
pencil 6B
line 20 50 380 50
width 6
pencil vine soft
line 20 90 380 90
pencil 2B #3b6e8f
line 20 130 380 130

Transforms#

translate, rotate, scale and mirror change where everything after them lands. Each applies on top of the ones before it, in the frame they left, the way a canvas transform does. Angles turn clockwise, and rotate, scale and mirror work about the origin unless given a point with at.

A transform is baked into the anchors of each mark as it is drawn, never written out as a transform of its own. A transform is affine, so mapping a curve's anchors and both of their handles maps the curve itself exactly. A stroke's width scales with the transform, and a copic nib and a linear gradient's axis turn with it.

push saves the transform, the paint and the units, and pop puts them back. A pop with no push in its block is an error, and a push left open when its block ends is restored there, with a warning.

repeat 12 as i {
  push
  rotate (i * 30) at 200 200
  line 200 200 200 80
  pop
}

Blocks, names and definitions#

define "petal" {
  ellipse 0 (0 - size) (size / 3) (size)
}
let size 40
repeat 8 as i {
  place "petal" at 200 200 rotate (i * 45)
}
let size 15
place "petal" at 200 200

Shapes#

Every shape is Bezier anchors, never a polygon of samples:

ShapeOutline
rectFour corners, or with r four quarter arcs between straight sides; the radius is held to half the shorter side.
circle, ellipseFour anchors on the axes, from three o'clock clockwise, with handles 4/3 (sqrt 2 - 1) of the radius along the tangents.
line, polylineStraight segments.
polygonStraight sides, closed. With r, every corner is rounded by an arc of that radius that touches both of its sides, whatever the corner's angle; a corner's radius is held so its arc takes no more than half of either side.
starTwice as many corners as points, the first point straight up.
arcOpen, in pieces of at most a quarter turn; clockwise when the end angle is greater, the other way when it is smaller, and no more than a full turn.
spiralArchimedean, winding clockwise from its centre, as cubic pieces of a quarter turn.
shapeA shape from the library, fitted to a box.

The circle, the ellipse and the rounded rectangle are built with the same code the SVG importer builds those elements with, so a generated circle and an imported one are the same curve. Every arc and every rounded corner is pieces of at most a quarter turn with handles 4/3 tan(a/4) of the radius long, a being the piece's angle, so a rounded polygon with square corners is the rounded rect of the same size, anchor for anchor.

fill #ffe08a
polygon 200 40, 360 260, 40 260 r 16
polygon 400 60, 560 60, 560 220, 400 220 r 30
star 700 160 100 45 5

The shape library#

shape "<name>" at <x> <y> size <size> draws a named shape from the built-in library, from x, y down and to the right. With one size, the shape's longer side is that long and it keeps its proportions; with a width and a height, it is stretched to fill them. Names are read in any case. A name the library does not have is reported as unknown-shape, with the names it most likely meant.

shape "cube-isometric" at 40 40 size 160
fill #ffe08a
shape "cylinder-perspective" at 240 40 size 160
shape "star" at 440 40 size 160 80

A shape is one mark or several, drawn in the current paint, and each mark shows only what the shape's source showed. An outline the source left unfilled stays unfilled when a fill is set. A backdrop - a part the source filled without an outline - is left out when nothing is filled, and drawn with the fill and no outline when something is. So with no fill the isometric and perspective objects are line drawings, and with one they are solid.

Erasing#

tool eraser makes the lines after it erase, as the app's Eraser does: each cuts its swath - the width it is drawn at, all along it - out of the marks drawn before it on its layer, and draws nothing of its own (core/erase.ts).

layer "Card"
color #1f2328 width 3 fill #ffe08a
rect 20 20 200 120 r 12
line 20 160 220 160
tool eraser
width 24
line 120 0 120 180

Combining shapes#

wipe <op> { ... } combines the marks its block draws as the app's Wipe Stacks - a vector editor's Pathfinder - combine them, and draws what is left in their place:

OpPathfinderWhat is leftPainted as
inUniteeverything the marks coverthe topmost mark
out-frontMinus Frontthe bottom mark, less the restthe bottom mark
out-backMinus Backthe top mark, less the restthe top mark
midIntersectwhere all of them overlapthe topmost mark
outerExcludewhere an odd number of them overlapthe topmost mark
cleanDivideevery piece of their overlaps, a mark eachthe topmost mark over the piece

Stacking shapes#

stack merge|remove <points> { ... } stacks the marks its block draws as the app's Shape Stacker - a vector editor's Shape Builder - stacks them. The marks make pieces: every place where they overlap, and every place where they do not. Each point picks the piece under it.

fill #d0342c
stack merge 60 100, 125 100 {
  circle 100 100 60
  fill #27486d
  circle 150 100 60
}
stack remove 355 100 {
  fill #ffe08a
  rect 260 40 120 120
  fill #27486d
  circle 380 100 60
}
fill #d0342c
wipe clean {
  circle 100 100 70
  fill #27486d
  circle 180 100 70
}
fill #ffe08a
wipe out-front {
  rect 300 30 140 140
  fill none
  width 14
  line 290 180 450 20
}
ShapeNatural sizeMarksBackdropsRead from
square231.43 x 231.431-shapes.svg#square
rectangle482.09 x 231.431-shapes.svg#rectangle
circle272.06 x 272.061-shapes.svg#circle
ellipse514.62 x 212.941-shapes.svg#ellipse
triangle231.43 x 231.441-shapes.svg#triangle
right-triangle209.91 x 231.441-shapes.svg#right-triangle
hexagon164.76 x 142.681-shapes.svg#polygon
star233.42 x 2221-shapes.svg#star
horizontal195.31 x 01-shapes.svg#horizontal
vertical0 x 195.311-shapes.svg#verticla
45-degree138.11 x 138.111-shapes.svg#_45-degree
15-degree188.66 x 50.551-shapes.svg#_15-degree
arc79.66 x 103.41-shapes.svg#curve
spiral147.292 x 130.5611-shapes.svg#curve-2
wheel-isometric249.57 x 262.71231isometric-objects.svg#wheel-isometric
sphere-isometric275.74 x 275.741-isometric-objects.svg#sphere-isometric
cube-isometric296.23 x 264.397-isometric-objects.svg#cube-isometric
cylinder-isometric246.64 x 199.925-isometric-objects.svg#cylinder-isometric
wheel-perspective200.615 x 260.11631perspective-objects.svg#wheel-perspective
sphere-perspective262.48 x 275.741-perspective-objects.svg#sphere-perspective
cube-perspective267.9 x 223.0671perspective-objects.svg#cube-perspective
cylinder-perspective246.64 x 209.9251perspective-objects.svg#cylinder-perspective

The natural size is the shape's extent in its source, in pixels, measured over its curves rather than its handles, and it gives the proportions that one size keeps. A line has no extent across itself, so horizontal and vertical sit in the middle of the box they are given.

The shapes come from the SVG files in ai-helper/vectors/skills/vector-graphics/assets: every named group or element at the top of shapes.svg, isometric-objects.svg and perspective-objects.svg is one shape. npm run shape-library reads them into src/core/script/shape-library.json with the SVG importer's own code, so a library shape has the anchors importing its file would give it, and a test fails when the committed library and the files disagree. To add a shape, add a named group to one of those files and run it again.

Paths#

A path is one mark, however many subpaths it has.

path {
  move 40 40
  to 160 40
  to 100 140
  close
  move 90 70
  to 110 70
  to 100 90
  close
}

Curves through points#

through draws a smooth curve through every point it is given, with an anchor at each one, so the curve can be reshaped point by point in the app. At a point inside the list the curve runs parallel to the line from the point before it to the point after it, and each handle reaches a third of the way along its own segment, so points spaced unevenly do not throw it into loops. The first and last segments are the parabolas that meet their neighbours. Two points make a straight line.

Outside a path, through is an open mark of its own. Inside one, it carries on from the current point. After a curve or a smooth it sets off along the mirror of the last handle, so the join stays smooth, and a smooth after it mirrors its last handle in turn.

color #c0392b
through 40 200, 140 120, 240 200, 340 120, 440 200
path {
  move 40 300
  curve 80 220 160 220 200 300
  through 280 360, 360 300
  smooth 460 220 500 300
}

Splitting paths#

split <x> <y> cuts the topmost mark drawn so far whose path passes within 4 px of the point, where the point lands on it, as the app's Split - a vector editor's Scissors - cuts it. Nothing moves, and the pieces take the mark's place on its layer, each keeping every paint property.

line 20 60 220 60
split 120 60
fill #e9c46a
rect 260 20 120 80
split 320 20
split 320 100

Smearing#

smear <width> <strength> <x> <y>, <x> <y>, ... runs a pass of the app's Smear, a blending stump, along the points: every Pencil mark drawn so far that it reaches keeps the pass, and draws with its graphite spread along it (core/smudge.ts). Tone moves from where it is thick to where it is thin, the paper's tooth fills in and edges soften; the darkness is kept.

pencil 4B
repeat 12 as i {
  line (20 + i * 6) 20 (40 + i * 6) 140
}
smear 24 0.6 10 80, 150 80

Liquify#

warp <x> <y> <radius> <dx> <dy>, twirl <x> <y> <radius> <degrees>, pucker <x> <y> <radius> <amount> and bloat <x> <y> <radius> <amount> press one of the app's Liquify brushes once at the point, on every mark drawn so far that it reaches (core/liquify.ts). What the brush bends falls off smoothly from its centre to nothing at its radius, so a mark is bent where the brush is and left exactly as it was past it.

rect 40 40 160 100
warp 120 40 30 0 24
circle 300 90 50
bloat 340 90 40 0.5
line 60 200 180 200
twirl 120 200 70 90

The hand-drawn pass#

rough <amount> draws every mark after it as a hand would: rough 0.3 is barely there, rough 1 is clearly drawn by hand, and rough off is exact again. It is paint, so it carries to the marks after it, and a group, a placed definition or a push and pop keep it to themselves.

seed 7
rough 0.6
fill #ffe08a
rect 40 40 160 120
fill none
circle 320 100 60
line 420 60 560 160
rough 1 passes 2
polygon 40 240, 200 240, 120 360

The pass moves each mark's anchors on the page, after any transform, so a shape scaled up wobbles no more than one drawn at that size. What it draws is anchors like any other mark, editable in the app.

Each part reaches this far at rough 1, and proportionally less at a smaller amount:

PartAt rough 1
Anchor driftUp to 5 px, and no more than 15% of the way to a neighbouring anchor
Bow of a straight segmentUp to 0.57 times the square root of its length: 5.7 px for a 100 px segment, 11.4 px for a 400 px one
Turn of a curve's handlesUp to 6 degrees, both handles of an anchor alike
Stretch of a curve's handlesUp to 20% longer or shorter
Overshoot1.5 line widths past each end, and no more than 25% of the segment it extends
Join of a closed lineUp to 60% of the anchor drift from where the line began
Fill under a lineRoughened 50% as much as the line
Second pass60% of the width and 55% of the opacity
Wobble length80 px: anchors closer together than this drift together

The wobble is seeded. The same script and the same seed draw the same bytes on every run. The seed is the last seed instruction's, or options.seed, or 0, and another seed draws the same drawing by another hand. Each mark's wobble comes from the seed and the mark itself, so adding, moving or removing one mark leaves every other mark's wobble as it was, and the same shape drawn twice wobbles differently each time.

It costs more. A bowed segment samples to as many as 24 points where a straight one took one, and a filled shape or a second pass adds marks. The budget counts all of it, and the marks of one shape are drawn together or not at all.

Text#

text "<words>" at <x> <y> places a text item: live text the app sets in the current font and edits as it edits any text, set by the type layout every output shares. at is the top of the first line: its left end, or with align center or align right its middle or its right end, and align justify spreads a line across its box. size sets this text's size, and font "<family>" <size> sets both for all the text after it. \n in the string starts a new paragraph.

font "Georgia" 28
text "Acme Corp" at 320 40 align center
text "A caption that wraps inside a box of its own" at 40 100 size 16 box 180
text "Around the bend" at 40 300 size 18 along "M0 0 C60 -60 180 -60 240 0"

character and paragraph set what every text after them has, as font does, until character reset and paragraph reset. runs (...) gives stretches of one text styles of their own, each from where it starts to where it ends, counting the text's characters from 0:

character tracking 50
paragraph align justify hyphenate
text "A paragraph set justified, its long words hyphenated where they must be" at 40 160 size 14 box 200
character reset
paragraph reset
text "Acme Corp" at 40 40 runs (0 4 face "Bold", 5 9 color #c0392b underline)
character clauseSets
face "<face>"The face in the family: "Bold", "Italic"
tracking <n>, kern <n>The space between all the letters, or between one pair, in thousandths of an em
kerning auto|noneThe font's own kerning, or none
leading <length>The line spacing; with none, it follows the size
spacing <n>%The space between words, 100% the font's own
caps normal|all|smallCapitals as typed, all capitals, or small capitals
hscale <n>%, vscale <n>%The letters' width and height
shift <length>, rotate <degrees>The baseline moved up, and each letter turned
highlight <color>A color behind the letters
super, sub, underline, strikeA superscript or a subscript; a line under or through
paragraph clauseSets
align left|center|right|justifyThe alignment, the last line of a justified paragraph set left
indent <left> [<right>], first <length>The indents, and the first line's
before <length>, after <length>The space before and after each paragraph
hyphenate, nohyphenateWhether long words break with a hyphen
list bullet|number|none, level <n>A bulleted or numbered list, and how deep, 1 to 9
paragraph list bullet
text "Widgets\nGadgets\nGizmos" at 40 220 size 14
paragraph list number level 2
text "Order\nShip" at 240 220 size 14
paragraph reset

textconfig "<name>" defines a Text Configuration, a vector editor's Character Style: character's clauses with a size, a family and a color, under a name the book keeps. A text follows one with config "<name>", and a run with config "<name>" first among its clauses; what the text or the run has of its own overrides it. Defined again, it changes for every text that follows it, before or after, as the app's Type panel restyles every text that follows one changed there:

textconfig "Callout" face "Bold" color #c0392b
text "Acme Corp" at 40 220 config "Callout"
text "Sale today" at 40 260 runs (5 10 config "Callout" underline)

markdown makes a text Markdown Flow: its text is Markdown, and it is drawn as the Markdown renders - headings, emphasis, code, lists, task lists, quotes, rules, links and tables - the Markdown itself kept in the text, so the app edits it as Markdown. A textconfig named for one of the Markdown configurations restyles what it styles:

textconfig "Markdown Heading 1" color #27486d
text "# Acme Corp\n\nSales are **up**:\n\n- Widgets\n- Gadgets" at 40 300 size 14 markdown

The browser-safe entry reads Markdown with the lexer it is given, so the core holds no Markdown reader of its own. Import @isocialpractice/napkin-sketch/markdown once to give it marked's; the command line and @isocialpractice/napkin-sketch/node read with it already:

import { drawSvg } from '@isocialpractice/napkin-sketch';
import { markdownToType } from '@isocialpractice/napkin-sketch/markdown';

const { svg } = drawSvg('napkin 1\ntext "# Notes\\n\\n- one" at 40 60 markdown\n');
markdownToType('**Acme** Corp').text; // 'Acme Corp'

accent colors the next text as a language reads it - keywords, strings, numbers, comments, names - in Napkin Light on a light page and Napkin Dark on a dark one, each color at least 4.5:1 against its code shade. The colors are worked out as the text is drawn, so the app keeps coloring the text as it is edited:

font "Consolas" 14
accent python
text "def total(prices):\n    return sum(prices)  # in cents" at 40 380

The highlighter comes the way the lexer does: @isocialpractice/napkin-sketch/markdown gives the API highlight.js with its 36 common languages, and registerAccentLanguage installs any other of its 193:

import { accentRuns } from '@isocialpractice/napkin-sketch';
import { registerAccentLanguage } from '@isocialpractice/napkin-sketch/markdown';
import fortran from 'highlight.js/lib/languages/fortran';

registerAccentLanguage('fortran', fortran);
accentRuns('x = 1', 'fortran').language; // 'fortran'

as marks draws the letters instead, as the built-in face's brush strokes: one mark in the current paint, which turns and mirrors with the drawing and goes through the hand-drawn pass like any other.

color #c0392b width 2
rough 0.6
text "Drawn by hand" at 40 40 size 32 as marks

The face draws every printable ASCII character. Anything else - an accented letter, an arrow - is left as a gap and reported as glyph-missing. Each line is aligned by itself, so a centred block is centred line by line.

Images#

image "<name>" at <x> <y> places the image the host supplied under that name in options.assets, a data URL, and image "data:image/png;base64,..." places one written into the script. Nothing is read from disk: a path or a web address is reported as unknown-asset, with how to pass the image in instead.

The examples on this page name logo, a 40 by 20 PNG, and badge, a small card. The documentation test hands both to every example, as a host would hand over its own.

image "logo" at 40 40
image "logo" at 120 40 size 120
image "logo" at 280 40 size 60 60

Copying a document in#

use "<name>" copies in a document the host supplied under that name in options.documents - a page, or a book, whose first page is used - as a group of its own, named after the document or after layer "<name>".

use "badge" at 40 40
use "badge" at 300 40 scale 0.5 layer "Small badge"

Linked graphics#

link "<path>" at <x> <y> size <width> <height> places a file by reference: one image item, on a layer of its own named for the file or by name "<layer name>", that stands for the file rather than holding it - what a design tool calls a linked file, as opposed to an embedded one. The file's contents are neither drawn into the drawing nor broken into layers, and nothing is read to place it.

link "assets/logo.svg" at 40 40 size 160 80 name "Logo"

Effects#

effect <name> <arguments> gives the next layer, group or mark the script makes an effect: a CSS filter function - a blur, a shadow, a shift in its color - or one of the appearance effects a vector editor reaches for: Feather, Bevel, Emboss, Inner Shadow, Outer Glow and Inner Glow - or Splatter, which draws a mark's fill as a loose hatch, Warp, which bends what takes it by a style, or 3D - extrude and revolve - which builds a mark in depth. Several effect lines stack in the order they are written, and the next thing made takes them all; effect none lets go of any still waiting.

effect drop-shadow 4 4 8 #00000066
rect 40 40 200 120 r 12
fill #4a7fb5
effect bevel size 6 on fill
effect outer-glow 10 #ffcc00 blend normal on stroke
rect 280 40 160 100 r 12
effect sepia
effect blur 1
image "logo" at 40 200 size 120
effect grayscale
layer "Archive"
circle 400 260 40
EffectWrittenDoes
blureffect blur <radius>A Gaussian blur. The radius is its standard deviation, as the length in CSS's blur() is.
brightnesseffect brightness <amount>Multiplies every color: 1 leaves it as it is, 0 is black.
contrasteffect contrast <amount>Pushes colors away from middle gray, or below 1 toward it: 0 is gray.
saturateeffect saturate <amount>0 is gray, 1 is as it is, 2 twice as vivid.
grayscaleeffect grayscale [<0-1>]Toward gray by the amount, all the way with none given.
sepiaeffect sepia [<0-1>]Toward sepia by the amount, all the way with none given.
inverteffect invert [<0-1>]Toward the inverse by the amount, all the way with none given.
hue-rotateeffect hue-rotate <degrees>Turns every hue around the color wheel.
opacityeffect opacity <0-1>Fades what carries it, at its place in the list.
drop-shadoweffect drop-shadow <dx> <dy> <blur> <color> [spread <0-100>] [blend <mode>] [opacity <0-1>]Outer Shadow: a shadow of its shape, moved across and down, blurred by CSS's blur radius, in the color. spread firms its edge first, as Photoshop's Spread does; blend mixes it with what is under the mark; opacity fades it.
feathereffect feather <radius>Fades it from clear at its edge to solid radius inside.
beveleffect 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>]A slope size wide inside the shape, or outside it, lit from angle - counterclockwise from the right, so 120 is the upper left - at altitude above the page. Its light screens and its shade multiplies; down presses the shape in, and depth steepens the slope.
embosseffect emboss [emboss|pillow], then the bevel's optionsThe slope straddles the edge, or, as a pillow, dips into a groove round it.
inner-shadoweffect inner-shadow <dx> <dy> <blur> <color> [choke <0-100>] [blend <mode>] [opacity <0-1>]A shadow cast inside the shape from its edge, as if the shape were a hole. choke firms its edge.
outer-gloweffect outer-glow [<blur>] [<color>] [spread <0-100>] [blend <mode>] [opacity <0-1>]A glow around the shape.
inner-gloweffect inner-glow [<blur>] [<color>] [choke <0-100>] [edge|center] [blend <mode>] [opacity <0-1>]A glow inside the shape, from its edge or from its center.
splattereffect splatter [preset <name>] [angle <degrees>] [overlap <n> [<variation>]] [width <n>] [curviness <percent> [<variation>]] [spacing <n> [<variation>]] [seed <n>]A vector editor's Scribble: the mark's fill drawn as one loose hatch, back and forth at angle, spacing apart, in lines width wide, each turn carried overlap past the edge and rounded by curviness. A preset - default, childlike, dense, loose, moire, sharp, sketch, snarl, swash, tight or zig-zag - sets them all, and the options after it change them; the second number after overlap, curviness or spacing is how much it varies, from seed.
warpeffect warp <style> [bend <percent>] [horizontal|vertical] [distort <percent> [<percent>]] [part whole|lower|upper]A vector editor's Warp: what takes it bent by a style laid over its box - arc, arch, unroll, shell, windy, breezy, curvy, chart, puffy, expand, cram or twirl, or the vector editor's name for one (bulge, flag, wave, fish, rise, fisheye, inflate, squeeze, twist, arc-lower, arc-upper, shell-lower, shell-upper). bend (-100 to 100, 50 when left out) and the two distort percents may carry a %; vertical bends along the height; part is Arc's or Shell's edge.
extrudeeffect 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>]A vector editor's Classic 3D Extrude & Bevel: the mark set depth deep (50 when left out) and drawn as shaded faces, turned about X, then Y, then Z (-18, -26 and 8 when left out, Off-Axis Front), with perspective up to 160 degrees. hollow leaves the caps off. light is where the light comes from, degrees round from the right and up off the page, and how strong it and the light all round are, 0 to 1.
revolveeffect revolve [angle <degrees>] [offset <n>] [axis left|right] [segments <n>], then the extrude's optionsThe mark swept round an upright axis at its left or right edge, offset off it, angle degrees round (360 when left out) in segments steps, 0 working them out.

Blend modes#

blend <mode> gives the next layer or group the script makes a blend mode: how what it paints mixes with what is drawn under it, the page's background included. A mode goes by its Napkin Blend name or by the name a vector editor and CSS give it - stack or multiply - and the writer writes the napkin name.

background #f4e9d8
layer "Card"
rect 40 40 200 120
blend stack
layer "Shadow"
circle 200 140 60
blend sheer
group "Glow" {
  circle 120 80 30
}
Napkin nameOrMixes
normalNot at all: covers what is under it
soakdarkenThe darker of the two colors
stackmultiplyMultiplied: always darker, as ink stacks on paper
scorchcolor-burnDarker, with more contrast
drylightenThe lighter of the two colors
sheerscreenScreened: always lighter
bleachcolor-dodgeLighter, with less contrast
foldoverlayMultiplying the darks and screening the lights under it
lightsoft-lightAs a soft light
heavyhard-lightAs a hard light
messydifferenceThe difference between the two
avoidanceexclusionThe difference, with less contrast
tidyhueIts hue, with the lightness and saturation under it
brightsaturationIts saturation, with the hue and lightness under it
dyecolorIts hue and saturation, with the lightness under it
coverluminosityIts lightness, with the hue and saturation under it

Worked examples#

A card. Layers for the paper, the art and the frame, a rounded plate, and a row of dots drawn with a repeat:

napkin 1
page 400 250
background #ffffff
layer "Plate"
color #326478 fill #326478
rect 20 20 360 210 r 16
layer "Dots"
stroke off fill #ffe08a
repeat 5 as i {
  circle (80 + i * 60) 125 18
}
layer "Frame"
stroke on fill none color #1f2328 width 2
rect 10 10 380 230 r 22
import { evaluate, renderSketch } from '@isocialpractice/napkin-sketch';

const result = evaluate(card, { name: 'card' });
if (!result.ok) throw new Error('the card has errors');
const svg = renderSketch(result.book.sketches[0]);
const png = renderSketch(result.book.sketches[0], { format: 'png' });

A pattern. A definition placed around a circle, the placing angle and the size both worked out in expressions:

napkin 1
page 400 400
define "spoke" {
  line 0 0 0 (0 - length)
  circle 0 (0 - length) 6
}
repeat 16 as i {
  let length (80 + 40 * (i % 2))
  place "spoke" at 200 200 rotate (i * 22.5)
}

Limits#

For agents#

See also#