pentoolopen source Menu

.pen file format, versions 1–3

A .pen file is UTF-8 JSON. The format is deliberately inspectable and safe for humans, scripts, and generative agents to edit.

{
  "format": "pentool",
  "version": 1,
  "name": "Example",
  "canvas": { "width": 1200, "height": 800, "background": "#ffffff" },
  "layers": [{
    "id": "layer-1",
    "name": "Artwork",
    "visible": true,
    "locked": false,
    "paths": [{
      "id": "path-1",
      "d": "M 100 100 C 200 20 300 180 400 100",
      "stroke": "#111827",
      "stroke_width": 8,
      "fill": "none",
      "closed": false
    }]
  }]
}

Path d uses standard SVG path syntax. Layer order is back-to-front; path order inside a layer is also back-to-front. Unknown top-level fields should be preserved by tools that edit documents. Version 1 supports solid CSS colors. External resources, scripts, filters, and raw SVG markup are not part of the format.

Limits enforced by the renderer:

  • canvas: 1–16,384 px on each axis
  • layers: at most 1,000
  • paths: at most 100,000 total
  • HTTP request: at most 16 MiB

Version 2: editable text and embedded fonts

Pentoolgg v0.3.0 keeps file format version 2 unchanged. Agent discovery and batch editing are tool behaviors, not a schema migration. Within every layer the explicit render order is the layer's paths array followed by its texts array; zero is the back of each stack. Use separate layers when paths and text must be interleaved. Unknown fields are retained by supported CLI edit operations.

Version 2 paths also support stroke_linecap (butt, round, square), stroke_linejoin (miter, round, bevel), and stroke_miterlimit (1–1000, default 4). Omitted caps/joins default to round to preserve older artwork. New paths made through the CLI/browser default to butt/miter. Native rendering and SVG export use these properties directly. Styling upgrades v1 files to v2.

Version 1 path-only documents remain readable. New documents use version 2; adding text or fonts upgrades older documents. Older v0.1 binaries reject version 2 instead of silently removing typography.

Each layer can have a texts array. Paths draw first, then text objects, both back-to-front within their arrays. Use separate layers to interleave paths and text. IDs must be unique across paths and text within the same layer.

{
  "id": "title",
  "content": "Editable text\nSecond line",
  "x": 100,
  "y": 200,
  "font_family": "Atkinson Hyperlegible",
  "font_size": 48,
  "font_weight": 400,
  "italic": false,
  "fill": "#111827",
  "align": "left",
  "letter_spacing": 0,
  "line_height": 1.2,
  "transform": [1, 0, 0, 1, 0, 0]
}

Positions reference the first line's baseline. Newlines are explicit; wrapping is not automatic. align is left, center, or right, relative to x. Size and spacing use canvas units; leading is a font-size multiplier. The six transform values follow SVG matrix order [a,b,c,d,e,f]. Transform operations compose this matrix without changing content or typography. Identity matrices may be omitted. Locked layers protect both text and paths.

Top-level fonts is an optional array of { "id": "brand", "data": "<base64>" } resources containing valid TTF/OTF data. Family/style metadata is read from the font. Embedded fonts are prioritized over bundled and installed faces. The bundled default is not duplicated in .pen files. Font embedding is not a license grant: only distribute fonts whose license permits it.

Additional limits: 100,000 text objects; one million UTF-8 bytes per content; font size 0.1–4,096; weight 100–900; leading 0.1–10; at most 64 embedded fonts; 8 MiB base64 per font and 16 MiB total base64 font data. Geometry/typography numbers must be finite and text must contain XML-safe characters. Documents near these limits may exceed the HTTP body limit once JSON overhead is included.

Version 3: pages

Version 3 replaces the single top-level canvas and layers fields with an ordered pages array. Each page owns its canvas and ordered layers. Fonts remain document-wide so pages can share an embedded face without duplicating its bytes.

{
  "format": "pentool",
  "version": 3,
  "name": "Product",
  "pages": [{
    "id": "desktop",
    "name": "Desktop",
    "canvas": { "width": 1440, "height": 1024, "background": "#ffffff" },
    "layers": []
  }],
  "fonts": []
}

Page IDs are unique and stable. Page order is presentation order and does not affect rendering. A document contains 1–1,000 pages; each page contains at least one layer and retains the existing per-page layer limits. The path and text limits apply across the complete document.

Pentoolgg continues to read v1/v2 documents as a virtual page-1. Adding a page upgrades the document to v3 without changing the original canvas or artwork. Commands that operate on canvas content accept global --page <id> selection.

Import and composition rules

v0.4 imports copy source layers into one destination page. A prefix is applied to layer and object IDs by default; disabling it makes any collision an error. Source layer order and object order are retained. Placement scale, rotation, and translation are applied to editable geometry and text transforms rather than flattening the result. The destination canvas remains unchanged unless --expand-canvas is supplied.

Embedded fonts are document-wide. Byte-identical font data is reused; a different font whose resource ID collides receives a deterministic numbered ID. Unknown JSON fields on imported layers and objects are preserved. Imports are copies, not live links to the source file.