Skip to main content

porta

porta is two things:

  1. A DSL for concisely defining a relational floorplan; and
  2. A software package for deterministically converting a floorplan into a clean SVG map.
room hall    "Great Hall" 40x20 root
room parlor  "Parlor"     20x20 left-of hall
room kitchen "Kitchen"    20x20 right-of hall
room study   "Study"      ?x20  down-of parlor

The porta DSL

porta floorplans are defined in .porta files adhering to a strict specification. Each room is defined in a single line relative to one or more anchor rooms, forming a dependency graph all the way back to the initial root room. This model creates a 1-to-1 correspondence between the floorplan and the final SVG map it produces, allowing porta to rapidly and deterministically generate one from the other.

Files are read line by line: a # starts a comment that runs to the end of the line (a # inside a quoted name is literal), and blank lines are ignored.

Any statement can be continued onto the next physical line by ending it with a backslash, separated from the last token by whitespace:

room pn "" 40x10 root
room pw "" 10x20 down-of pn
room pe "" 10x20 down-of pn align=end
room ps "" 40x10 down-of pw \
    down-of pe
block passages "Passages" glyph=pn \
    pn pw \
    pe ps

Continued lines parse as one statement, and errors still point at the physical line of the offending token. A backslash anywhere else — mid-line, glued to a token, or followed by a comment — is an error, as is a blank (or comment-only) line inside a continued statement.

For more on the porta DSL specification, see the following documentation:

  • Rooms — the room statement, room positioning, auto-dimensions (?), disconnected components, and the layout resolution procedure.
  • Windows — interior and exterior windows, widths and offsets.
  • Doors — default doors & how to modify them, and the door statement for explicitly adding non-default doors.
  • Blocks — the block statement for joining multiple rooms into a single non-rectangular space.
  • Dividers — the divider statement for drawing a dividing line between two members of a block.
  • Links — the link statement for joining disconnected components into one continuous plan.
  • Stairs — the stairs statement for flights leading up or down off a floor, or between levels within one.

The porta tool

Once a valid floorplan has been written, porta draw converts it into an SVG map in a two-step process. First, the dependency graph is traversed and the relations in the floorplan are converted into a coordinate system. Second, that coordinate system is used to deterministically generate an SVG file.

porta draw plan.porta -o plan.svg

Override SVG appearance with a grouped JSON file:

porta draw plan.porta --style sepia.json -o plan.svg
{
  "page": {"background": "#fff8e7", "line_color": "#332211"},
  "typography": {
    "text_color": {"value": "#654321", "description": "Sepia ink"}
  }
}

default_style.json documents the supported settings. Both plain values and value/description records are accepted, including mixtures. Omitted settings retain their defaults; groups merge without replacing their other settings. Unknown keys and invalid values produce an error. With --debug-ascii, --style applies label settings and ignores visual settings. See glyphs for label schemes and numbering starts.

Window and scale fills follow the background; symbols and grid use the shared line color, with separate grid opacity. Font fitting uses Palatino-based estimates, so other fonts may fit differently in the SVG viewer.

The solved coordinate system can also be viewed and debugged directly as an ASCII grid:

porta draw plan.porta --debug-ascii

A plan that can't be solved (due to overlaps, missing anchors, gaps between a room and its anchor, etc) will fail and raise an error.

Installation

porta requires Python 3.14 or newer and is published on PyPI:

pip install porta

Alternatively, install it from a checkout of this repo. Add the [dev] extra to pull in the test and lint tools as well:

pip install -e .          # editable install
pip install -e ".[dev]"   # ... plus pytest, ruff, and mypy

For development with uv, set up the environment from the repository root:

uv sync --extra dev

Why porta?

porta is built to be:

  • Text-based — a plan is plain text: editable in any editor, diffable, and version-controlled alongside your campaign notes. No WYSIWYG, no binary files.
  • Concise — a terse syntax with strong defaults; a whole plan is a handful of short lines.
  • Deterministic — no procedural generation and no randomness; the same plan always renders the same map.
  • Relational — rooms are placed against other rooms, never drawn by hand or given raw coordinates.
  • Spatial — the output is a flush, to-scale floor plan in tabletop conventions, not a flowchart or connectivity graph.
  • Fast — placement is a single pass over a dependency graph, with no constraint solver to run.
  • AI-friendly — terse, text-only, and predictable, so a language model can read and write plans reliably (this one falls out of the rest).

No existing tool hits all of these:

  • Procedural and AI generators (Watabou, Inkarnate) aren't deterministic or controllable — they invent a layout instead of rendering yours.
  • GUI map editors (Dungeondraft, Dungeon Scrawl) aren't text: mouse-driven, binary, and kept apart from your notes.
  • Diagram tools (Mermaid, Graphviz, D2) give connectivity, not a spatial, to-scale map.
  • Hand-written SVG is text, but neither concise nor relational — verbose and easy to get wrong.

Metadata

Release files for porta 1.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for porta 1.8.0
File Size Uploaded
porta-1.8.0.tar.gz 190.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for porta 1.8.0
File Interpreter ABI Platform
porta-1.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 250.8 kB

Release files / porta-1.8.0.tar.gz

Download URL porta-1.8.0.tar.gz
Size 190.7 kB
Tags Source
SHA-256 checksum
How to use checksums
cfc728e8966d8167bc5b6576f0affa976f7c4204a7f75405924f76ea27b0efc5
BLAKE2b-256 checksum
How to use checksums
3338796e88b4b9017b806104a9c27b773b362622857ecf67ba2aafb9a301f9d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / porta-1.8.0-py3-none-any.whl

Download URL porta-1.8.0-py3-none-any.whl
Size 60.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
041a7b300a972f16955986cbea710f71261b5e837b298d1dc247a7c9c823f12f
BLAKE2b-256 checksum
How to use checksums
9dc3223e75b5588e492cb3aa5e01da9e5f49f67fce16e567e04bfe24202f5934
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.8.0 This release

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page