napkin-sketchdocs 1.0.0 GitHub

The .skbk file format#

A sketch book is a human-readable JSON document:

{
  "format": "napkin-sketch",
  "version": 4,
  "name": "notes",
  "sketches": [
    {
      "id": "sk_…",
      "name": "unnamed",
      "width": 1280,
      "height": 800,
      "background": "#fcfaf5",
      "layers": [
        { "id": "ly_…", "name": "Layer 1", "opacity": 1, "visible": true, "locked": false }
      ],
      "strokes": [
        {
          "id": "st_…",
          "tool": "pen",
          "color": "#1f2328",
          "width": 3,
          "layer": "ly_…",
          "sharpened": true,
          "points": [{ "x": 12, "y": 34, "pressure": 0.6 }],
          // Optional: Bézier anchors - a Vector Path's, or a freehand
          // stroke's fitted curves ("fitted": true, each anchor with the pen's
          // "pressure" there). "points" are sampled from them.
          "vector": {
            "anchors": [
              { "p": { "x": 12, "y": 34 }, "hOut": { "x": 20, "y": 30 }, "pressure": 0.6 },
              { "p": { "x": 60, "y": 80 }, "hIn": { "x": 52, "y": 70 }, "pressure": 0.4 }
            ],
            "fitted": true
          }
        }
      ],
      "createdAt": "…",
      "updatedAt": "…"
    }
  ],
  "createdAt": "…",
  "updatedAt": "…"
}

Files are saved atomically (write-then-rename) so an interrupted save cannot corrupt an existing book.

Version 4 is what napkin-sketch writes (SKETCHBOOK_VERSION in src/core/types.ts): a text item may carry a "typeset", its runs, paragraphs, frame or path, and the book its "textConfigs", described below. Version 3 brought a layer's "group": true, a group row, and "parent", the id of the group it sits in, and a stroke's "fill" color. Files of versions 1 to 3 open unchanged, and saving writes them as version 4. A file whose version is above this build's - one a newer napkin-sketch saved - still opens, with a report that some parts of it may not show; saving it here writes only what this build knows of it. Since 1.0.0-alpha.4.6.0 a vector may say "fitted": true and its anchors may carry a "pressure"; both are optional, and a build from before drops them, keeping a correct stroke of points. A mark may be "tool": "pencil", a Pencil mark with its "pencil"; a build from before reads it as a Brush mark in its tone, and a Pencil mark may keep the Smear's passes over it in "smudges", which a build from before leaves unsmeared. A group may name its clipping path in "clip": a build from before draws the group unclipped and the clipping path as an ordinary mark, and a clip that names no closed mark inside the group is dropped on load. Since 1.0.0-alpha.5.0.0 a layer may carry a "blend" and a group an "isolate"; a build from before draws the layer at Normal.

Migrating from version 1: older .skbk files load unchanged — each page gains a single default layer and every stroke is assigned to it. Files from version 2 on also allow "tool": "image" strokes carrying an image data URL plus imageWidth / imageHeight for placed raster imports, and "tool": "copic" strokes carrying a nibAngle (degrees) for the rotatable broad nib. A pen or marker stroke may carry a "profile" of "rounded", "tapered" or "wave"; an absent profile is Default, and a value napkin does not know is dropped on load rather than failing it. "profileMirrored": true beside a profile swaps its two sides, which is how a mirrored Wave is saved.

"pen" is the Brush. The app calls the tool the Brush; the file keeps the value it has always written, "tool": "pen", and a mark saved as "brush" loads as a "pen" mark.

Optional fields. Everything the example leaves out is optional, and a build that does not know a field draws the mark without it. As src/core/types.ts defines them:

FieldOnHolds
sizeModea page"endless", filling the window (the default), or "sized", the exact width and height
group, parenta layera group row; the id of the group a layer sits in
pencila Pencil mark{ "medium", "grade" }, the pencil it was drawn with: graphite 9H to 9B, charcoal HB to 6B, vine Hard, Medium or Soft, compressed 2B to 6B; its tone is the mark's color, and a pencil there is not reads as graphite HB
smudgesa Pencil markthe Smear's passes, in order: each { "path", "width", "strength" }, the drag's fitted anchors in page units (with each one's pressure), the stump's width and its strength from 0 to 1; a pass with fewer than two anchors is dropped on load
clipa groupthe id of a closed mark inside it, its clipping path: the group shows only inside it, and it paints nothing while it clips
effectsa layer or a markthe effects drawn over its finished picture, in order: blurs, shadows and color shifts, and Feather, Bevel, Emboss, Inner Shadow, Outer Glow and Inner Glow; and splatter, which draws a mark's fill, or its stroke, as a hatch before any of them, with angle, overlap, overlapVariation, width, curviness, curvinessVariation, spacing, spacingVariation and seed; and warp, which bends a mark, or all a layer or a group holds, before any of them, with style (arc, arch, unroll, shell, windy, breezy, curvy, chart, puffy, expand, cram or twirl), orientation (horizontal or vertical), bend, distortH and distortV (percents, -100 to 100) and part (whole, lower or upper); and 3d, which builds a mark in depth and draws it as shaded faces, with kind (extrude or revolve), depth, caps, angle, offset, axis (left or right), segments, rotation, position and scale (each three numbers, X, Y and Z), perspective, shading (plastic, diffuse, none or wireframe) and light (azimuth, elevation, intensity and ambient). Each may carry a target of "fill" or "stroke", to work on that part of a mark alone, and "on": false, to be kept without being drawn; a field an appearance effect leaves out is its default. A build from before leaves out an effect it does not know, and draws one aimed at a part, or switched off, on the whole mark
blenda layerhow it mixes with what is under it: a Napkin Blend's name, "stack" to "cover"; absent is Normal, and a vector editor's name, such as "multiply", reads as its napkin one
isolatea grouptrue for Isolate Blending: the blends inside it mix only with each other
opacitya mark0 to 1; absent, the tool's own (1 for pen - the Brush - and text, 0.38 for marker)
fill, gradienta closed marka flat fill color, or a linear or radial gradient in its place
strokeStylea mark"solid", "dashed" or "dotted"
noStrokea shapetrue for a fill-only shape; color and width are kept for the outline to come back
text, fontSize, fontFamily, textBoxWidtha text itemits text, and its base: the size, family and color of every character no run says otherwise of, always those of its first character. textBoxWidth is the width a box from before wraps at (absent or 0: it grows to fit), and is written for a rectangle frame that grows, so a build from before wraps it too
textConfigsthe bookits Text Configurations, a vector editor's Character Styles, in the order the Type panel lists them: each { "id", "name", "attrs" }, the character attributes it sets - those a run may have but kern. Names are one each, in any case: a later repeat is dropped on load. One no text follows is kept
typeseta text itemeverything a text is beyond its base: its kind - "point" (Sideless Typing), "area" (Sheet Typing in its frame) or "path" (Path Typing along its path) - and its orientation ("vertical" for columns); its runs, sorted and apart, each { "start", "end", "attrs" }, a stretch of the text and the character attributes it has that the base does not (fontStyle, fontSize, leading, kerning, kern, tracking, wordSpacing, caps, position, hScale, vScale, baselineShift, rotation, underline, strike, color, highlight) - a run that follows a Text Configuration names its id in config, and has as its own only what it has beyond the base and the configuration, its overrides; its configs, a copy of the book's definition of each configuration its runs follow, so the text looks the same apart from the book, which the book's list overrides on load, a configuration only a text carries joining the list; and its paras, one entry a paragraph, each the paragraph attributes it has (align, leftIndent, rightIndent, firstIndent, spaceBefore, spaceAfter, hyphenate, list, shade) or null. A frame is { "anchors", "autoHeight", "inset", "fill", "stroke", "width" } and a path { "anchors", "closed", "start", "end", "flip", "align", "effect" }, their anchors relative to the text's first point, so moving the text moves them. Every number is held to a vector editor's range on load, and runs past the text are cut. A build from before draws the text in its base style, as point type or in its box
image, imageWidth, imageHeightan image itema data URL, and the size it is placed at
linkan image item{ "href", "kind" }: a linked file, drawn from where it is
vector.closeda vector pathtrue for a closed path
sharpeneda marktrue once auto-sharpen has run on it