Openrune Format
Specification draft v0.1 · 20 October 2026
Binary format version 1 · Experimental text transport
Openrune is an open format for creating, representing and exchanging custom graphical characters. Each character is called a rune: a monochrome geometric mark designed to sit alongside ordinary text.
A rune contains the geometric information needed to reproduce its shape. Applications that support Openrune can render that shape without downloading an image, installing a font or consulting a central registry. Applications without Openrune support generally show a conventional fallback character if the chosen transport preserves it.
This document describes the proposed shape grammar, canonical representation, binary format, text transport, rendering behaviour and optional Hyperrune mechanism. Version 0.1 is a public draft, not a ratified interoperability standard. Implementations should expect revisions and test against a shared reference implementation and fixtures before claiming conformance.
1. Principles and scope
- Exact geometry. The encoded construction determines the rune's outline, independently of its name or where it appears.
- Self-contained. The rune's geometry travels with its encoded representation; rendering does not depend on a remote asset.
- Typography first. Runes are single-colour graphics that can inherit the surrounding text colour and scale.
- Constrained and inspectable. Rendering uses a fixed set of geometric primitives, not arbitrary embedded SVG, executable code or remotely fetched images.
- Format independent of transport. The binary representation identifies the rune; text, rich-text and other transports can carry it.
- Graceful fallback. When an application does not understand a rune, the transport should retain a readable fallback where possible.
- Open implementation. The format is intended for independent implementations, with no mandatory account, registry or central service.
The v0.1 draft does not specify multicolour runes, animation, free-positioned arbitrary vector paths, grids larger than 8 cells wide or 5 tall, or a central registry. Hyperrunes are an optional extension, not a prerequisite for displaying a rune.
2. Rune model and shape grammar
A rune occupies a rectangular grid of w × h square cells, where 1 ≤ w ≤ 8 and 1 ≤ h ≤ 5. Each cell is one unit wide and high. The origin is at the upper-left corner; x increases rightward and y downward. A cell is empty or contains one geometric primitive.
Why the limit is 8 × 5. A rune is drawn at the height of a capital letter, so it is scaled by its height. In ordinary 16 px text that is roughly 11 to 12 pixels, and detail finer than about 2 pixels cannot be read. A height of 5 cells keeps every cell at about 2 pixels or more at that size, and gives a square grid a true centre cell. Width only makes the rune longer without making the cells smaller, so it may be greater, up to 8, which allows short monograms and wordmarks. Larger grids are not valid runes. The limits can be raised in a later revision without breaking anything, because older software shows the plain fallback character for a rune it cannot read; lowering them would invalidate runes already made.
| Primitive | Code | Parameter | Allowed values |
|---|---|---|---|
| Square | sq | Side as a percentage of cell size | 50, 60, 70, 80, 90, 100 |
| Triangle | tri | Clockwise quarter-turn rotation | 0, 1, 2, 3 |
| Semicircle | semi | Clockwise quarter-turn rotation | 0, 1, 2, 3 |
| Circle | circ | Diameter as a percentage of cell size | 50, 60, 70, 80, 90, 100 |
| Quarter circle | qtr | Clockwise quarter-turn rotation | 0, 1, 2, 3 |
Tone. Every cell is either solid or hatched. A hatched cell is drawn as a fine checker of tiny square dots (see Hatching, section 3), which reads as half-strength ink at text size. A rune has no colour field: both tones take the colour of the surrounding text, and the hatched tone simply gives a mark a second, lighter value. Hatching is drawn as plain shapes, so it works in any font renderer and any browser, with no dependence on colour-font support. Tone is part of the geometry and therefore affects rune identity.
Bridges. Circles in cells that touch, whether side by side, one above the other or corner to corner, may be joined by a waisted bridge formed from tangent arcs. The same shape and the same neck apply in every direction. The rune-level parameter neck determines the waist width relative to the smaller circle's radius. It ranges from 0.10 to 1.00, with a default of 0.55. A bridge is invalid unless both endpoints contain circles. A bridge joins two shapes of the same kind and the same tone: both solid or both hatched. A bridge between a solid shape and a hatched one is invalid. Squares can be bridged too, only to other squares: a bridge between two squares is a straight bar along the line between their centres, neck times the side of the smaller square thick (all of it for Full), so two full-size squares joined by a Full bridge become one rectangle. A circle and a square cannot be bridged.
Column spacing. A rune may adjust the gap between adjacent columns by increments of one-tenth of a cell, from −0.9 to +0.9. This shifts the designated column and every column to its right; row positions do not move. Spacing is part of the geometry and therefore affects rune identity.
3. Geometry and drawing
Cell-local geometry
Each primitive is defined in local coordinates from (0, 0) to (1, 1) and translated to its cell. One clockwise quarter turn transforms a point as follows:
(px, py) → (1 − py, px)
| Primitive | Outline at rotation 0 |
|---|---|
| Square | A square of side size / 100, centred in the cell: vertices (m,m) → (1−m,m) → (1−m,1−m) → (m,1−m) with m = (1 − size/100) / 2. At size 100 it fills the cell. |
| Triangle | Vertices (0,0) → (1,1) → (0,1); fills the lower-left half of the cell. |
| Semicircle | Arc of radius 0.5 from (0,1) over the top to (1,1), closed by the lower horizontal edge. |
| Circle | Centred at (0.5,0.5), radius diameter / 200. |
| Quarter circle | Quarter-disc of radius 1, centred at (0,1), bounded by (0,0), (1,1) and (0,1). |
Bridge construction
For circle centres A and B, radii r1 and r2, centre separation d, and m = min(r1, r2), the intended waist half-width is q = neck × m.
For the ordinary non-degenerate case, the fillet-circle radius is described by:
R = (d²/4 + q² − m²) / (2 × (m − q))
The bridge joins tangent points on the two circles using concave fillet arcs. Full bridge. The formula is undefined at neck = 1.00 (the denominator is zero), so a neck of 0.99 or more is drawn as the Full bridge instead: the waist is as wide as the smaller circle, and the two sides are straight lines touching both circles on the outside. With two circles of the same size it is a pill; with different sizes the sides taper. For unit direction u from A to B, normal n (u turned a quarter turn anticlockwise on screen), and k = (r1 − r2) / d, the side normals are N = k·u ∓ √(1 − k²)·n, and the four corners are A + r1·N and B + r2·N for each of the two normals, joined by straight lines. Rune Maker offers 0.99 as Full. Because 0.99 is below the old limit of 1.00, a decoder that predates Full draws it as a nearly straight, very slightly curved bridge and still decodes the rune; nothing is rejected. The reference tools never write exactly 1.00.
Hatching
A hatched cell is divided into an 8 × 8 grid of small squares, each 1/8 of a cell wide. The squares where column + row is even (counting from 0 at the top-left) are dots; the others are left empty, so the top-left square is always a dot. A shape keeps only the dots whose centre lies inside it, with the shape's edge counted as inside (a tolerance of 1e-9 cells). Curves and slants therefore become fine stair-steps. Each dot is a clockwise square.
- A square keeps the dots inside it: all 32 at size
100, fewer when it is smaller. - A triangle, semicircle or quarter circle keeps the dots inside the shape drawn as in the cell-local geometry above, at its rotation.
- A circle keeps the dots inside its circle.
- A bridge between two hatched circles or two hatched squares is drawn as the dots whose centres lie inside the bridge outline and outside both shapes (the shapes carry their own dots). Its dots lie on the grid of the first circle's cell.
Dot positions follow the cell, so column spacing moves a hatched cell's dots with it. At ordinary text sizes a dot is a fraction of a pixel and the checker blends to an even half tone; enlarged, it shows as a halftone texture. The pattern is part of how a rune is drawn, not of what it encodes: only the tone bit is stored. Implementations should compare output against reference fixtures.
Column positioning and filled outlines
For spacing adjustment s_g (an integer from −9 to +9) at gap g, column x is drawn at:
X(x) = x + (Σ s_g for all g ≤ x) / 10
The drawn width is X(w − 1) + 1. Bridges follow the displaced circle centres. The renderer combines the shapes into a filled path using the non-zero fill rule to obtain a continuous monochrome silhouette. Implementations should compare output against reference fixtures, particularly for overlapping components, shared edges and bridge joins; identical outline generation across independent renderers remains a conformance goal.
4. Canonical form and identity
Canonicalisation produces a deterministic representation of a rune's construction.
- Trim the grid to the smallest rectangle containing its occupied cells, moving that rectangle to
(0,0). - Order cells from left to right, then top to bottom.
- Normalise rotations to
0–3; normalise circle diameters and square sizes to the supported 50–100 range in increments of 10. - Validate bridges: retain only valid joins between circles in touching cells (corner-to-corner joins included), remove duplicates, and sort them deterministically.
- Normalise
neck: round to two decimal places; use0.55when the rune has no bridges. - Normalise column spacing: adjust positions after trimming, retain non-zero valid gap values in ascending order, and omit the field when there is no spacing.
Two runes with the same canonical construction encode to the same geometry bytes. This does not guarantee that visually identical shapes created through different primitive combinations have the same identity. Editors may simplify equivalent constructions, but such editing behaviour is not a substitute for a formally specified equivalence algorithm.
Content-derived identifier
The identifier is rune1- followed by 32 lowercase hexadecimal digits: the first 16 bytes of SHA-256 over the geometry encoding, with optional name and domain metadata omitted and the checksum excluded. Length and relevant flags are recalculated for that identity input.
For the Made Union example, the reported identifier is:
rune1-537f27cbf3d677979c2fda25d6e6622f
A shorter display form may show the first 12 hexadecimal digits; the full identifier is used for protocol comparisons and verification.
The identifier is content-derived, not centrally issued. Changing a rune's geometry changes its identifier. Names, domain claims, presentation colour and ordinary hyperlink destinations do not change it.
5. Binary representation — version 1
A complete encoded rune is at most 255 bytes. The fields appear in the following order:
| Size | Field | Definition |
|---|---|---|
| 1 byte | Header | 0xA1 denotes Openrune binary version 1. |
| 1 byte | Length | Total length including the header and final checksum. |
| 1 byte | Width | 1–8. |
| 1 byte | Height | 1–5. |
| 1 byte | Flags | Bit 0 = sparse cells; bit 1 = name; bit 2 = column spacing; bit 3 = domain; other bits reserved and zero. |
| Variable | Cells | Dense or sparse shape encoding, described below. |
| 1 byte | Bridge count | Number of bridges. |
| Conditional 1 byte | Neck | neck × 100; present only if the bridge count is non-zero. |
| 2 bytes per bridge | Bridge entries | Big-endian 16-bit value. With the top bit clear: cellIndex × 2 + direction, where cellIndex is the left or upper cell and 0 = right, 1 = down. With the top bit set (corner to corner): 0x8000 + upperCellIndex × 2 + direction, where 0 = down and to the right, 1 = down and to the left. Every other bit pattern is invalid. |
| Conditional variable | Spacing | Gap count followed by gap-number/signed-value pairs; present if flag bit 2 is set. |
| Conditional variable | Name | Byte count followed by UTF-8 text; present if flag bit 1 is set. |
| Conditional variable | Domain | Byte count followed by ASCII hostname; present if flag bit 3 is set. |
| 1 byte | Checksum | CRC-8, polynomial 0x07, initial value 0, over all preceding bytes. |
Cell encoding
A cell code uses the upper four bits for shape type: 1 square, 2 triangle, 3 semicircle, 4 circle and 5 quarter circle. Bit 3 (0x08) is the tone: 0 solid, 1 hatched. The lower three bits hold rotation 0–3 for a triangle, semicircle or quarter circle; for a circle the value is (diameter − 40) / 10 (1–6, so 0 is invalid); for a square it is (100 − size) / 10 (0–5, so 0 is a full-size square and 6–7 are invalid). Code 0x00 means an empty cell. For example, a solid full-size square is 0x10, the same square at 80% is 0x12, a hatched full-size square is 0x18, and a hatched full-size circle is 0x4e.
- Dense mode: write a byte for every cell in row-major order (
w × hbytes). - Sparse mode: write the occupied-shape count followed by position/code pairs. For grids above 256 cells, the count and cell indices use two bytes; otherwise they use one byte.
- An encoder chooses the shorter representation; ties use dense mode.
All fields, including optional metadata, must fit within the 255-byte length limit. An encoder should reject marks it cannot represent within that limit rather than truncate them.
Optional spacing
With flag bit 2 set, spacing begins with a gap count (1 to width − 1). Each entry is a gap number (1 to width − 1) followed by a signed byte from −9 to +9, excluding zero. Entries are ordered from left to right with no duplicates.
A gap at position 3 tightened by 0.4 cells is represented as 01 03 fc, where fc is the signed byte −4.
Optional accessible name
A rune may carry a human-readable name of up to 24 characters. The current draft permits ASCII letters, digits, spaces and the characters . ' & -. Names are NFC-normalised, trimmed and use single spaces. The binary field is length-prefixed UTF-8; decoders reject names violating the allowed character set or limits. The name does not influence rune identity.
Optional domain claim
A rune may carry a bare hostname for Hyperrune verification. The domain is untrusted metadata until verified separately.
- Lowercase ASCII; internationalised domain names use punycode.
- At least one dot; maximum 48 bytes.
- DNS labels contain letters, digits or hyphens and must not start or end with a hyphen; the final label contains a letter.
- No scheme, path, query, fragment, port, trailing dot or IP address.
The domain does not influence rune identity or drawing. See Hyperrunes.
Worked example: Made Union
The Made Union rune is represented in the current example as a 5 × 2 grid containing seven shapes, with no bridges or optional metadata, encoded in 17 bytes:
a1 11 05 02 00 | 00 10 20 10 20 | 10 00 10 00 10 | 00 | 61
header/length/dimensions/flags | first row | second row | bridges | CRC
With the optional name Made Union, the reported encoding is 28 bytes; adding the domain madeunion.co increases the reported total to 41 bytes. These example vectors are illustrative until confirmed against published conformance fixtures.
6. Text transport — experimental
The first proposed plain-text transport consists of:
- One visible fallback character.
- A sequence of invisible Unicode variation selectors, encoding the rune's binary bytes.
Suggested fallback characters include ■ (U+25A0), ◆ (U+25C6), ● (U+25CF) and ★ (U+2605). The fallback is chosen when the rune is copied and is not part of the rune's identity.
Byte-to-character mapping
| Binary value | Variation-selector range |
|---|---|
0–15 | U+FE00 through U+FE0F |
16–255 | U+E0100 through U+E01EF, using value − 16 as the offset |
A scanner can identify runs of these selectors, locate the 0xA1 binary header after any leading presentation selectors, read the indicated length, and validate the full payload. Invalid or partial payloads must not be rendered as valid runes. The original visible fallback is retained as the safe representation when decoding fails.
Example sizes
Measurements from the initial sample set, without optional name/domain metadata:
| Mark | Grid | Binary bytes | Unicode code points, including fallback | UTF-16 code units | UTF-8 bytes |
|---|---|---|---|---|---|
| Made Union | 5 × 2 | 17 | 18 | 28 | 64 |
| MU | 6 × 2 | 19 | 20 | 32 | 72 |
| Made | 8 × 2 | 23 | 24 | 38 | 86 |
| Mixed-primitive test mark with bridges | 5 × 4 | 31 | 32 | 45 | 109 |
Although common grapheme-segmentation implementations may treat the fallback-plus-selector sequence as a single grapheme cluster, platforms can still impose byte limits, strip invisible code points, transform text or provide different editing behaviour. A single grapheme cluster is not a promise that every app will treat a rune as one editable character.
Standards and compatibility warning
Unicode variation selectors have established uses, including registered variation sequences. The proposed use for carrying privately defined rune payloads is not an endorsed Unicode extension mechanism. Applications may remove or rewrite these code points, and some systems treat invisible payloads as a security concern.
This transport is experimental and must not be Openrune's only supported interchange mechanism. It does not make a rune universally displayable: the actual geometry requires an Openrune-aware renderer.
7. Other transport candidates
These alternatives could carry the same canonical binary payload. They are proposals, not fully specified interoperable transports in v0.1.
| Transport | Proposed representation | Intended use |
|---|---|---|
| Rich text | Inline element carrying base64url-encoded rune data, together with a plain-text fallback | Editors and websites that preserve HTML or structured inline objects |
| URL | A URL containing a base64url representation of the rune bytes | Applications that remove invisible Unicode data |
| Image | PNG/SVG rendered from canonical data, optionally including the rune identifier as metadata | Systems without inline rune support |
| Private-use font | User-installed font with private-use character mappings | Local experimentation; not a reliable cross-platform exchange format |
All proposed transports must preserve exact geometry and define validation, fallback and clipboard behaviour before they can claim interoperability.
8. Rendering and accessibility
The intended inline rendering model treats a rune as a typographic element aligned with capital letters:
- Height: approximately the surrounding font's cap height, provisionally
0.72em. - Baseline: bottom of the rendered mark aligns with the text baseline.
- Width: proportional to the drawn rune geometry, including column spacing.
- Side spacing: provisionally
0.08emon each side. - Colour: inherited from surrounding text, including hyperlinks and themes; the format contains no colour field.
- Selection and deletion: an Openrune-aware editor should treat the rendered rune as one inline object, preserving its payload when copied.
- Accessibility: expose the rune's available human-readable name; when absent, provide a generic description such as “rune”.
Very wide runes can interrupt the rhythm of running text. Authoring tools may warn when the width-to-height ratio exceeds 4:1.
9. Hyperrunes — optional domain verification
A Hyperrune is a rune carrying a domain claim that a compatible application may verify. Verification can enable a trusted association and optional hyperlink, but it does not alter the underlying rune geometry.
A domain claim alone does not establish ownership or authenticity. The claimed website must publish a verification file at:
https://example.com/.well-known/openrune.json
Example verification file
{
"version": 1,
"runes": [
{
"id": "rune1-537f27cbf3d677979c2fda25d6e6622f",
"name": "Made Union",
"href": "/",
"status": "current"
}
]
}
The recognised keys are:
| Key | Required | Purpose |
|---|---|---|
version | Yes | Verification-file schema version (1). |
runes | Yes | Collection of declared rune entries. |
id | Yes | Full content-derived rune ID. |
name | No | Display name supplied by the domain. |
href | No | Relative path or same-domain URL; external destinations must not be accepted. |
status | Yes | current or replaced. |
replacedBy | No | Full ID of a successor rune when one replaces another. |
A rune is verified for a given domain only when that domain's valid file explicitly lists the rune's full ID. Missing, invalid, unreachable or mismatched verification must not generate a verified badge or an automatic link.
Lookup and navigation safeguards
- Use HTTPS, refuse redirects to different hosts and send no cookies or credentials.
- Apply explicit response-size and time limits. The proposed file-size limit is 64 KB.
- Cache positive and negative results for a bounded time.
- Do not fetch verification records for every rune at page load. Prefer an intentional user action to initiate inspection or verification.
- Privacy consideration: even a lookup initiated by hovering can disclose a reader's network address and interest to the claimed domain. A production implementation must account for that disclosure and should not perform such requests silently.
- Show the verified destination domain before navigation and follow a link only after a user action.
A Hyperrune is not a universally clickable Unicode character. Link behaviour exists only in applications that explicitly implement the Hyperrune extension. An ordinary rune can also be linked by a conventional rich-text editor without carrying any Hyperrune metadata.
10. Decoder and security requirements
A decoder must reject malformed or unsupported inputs rather than guess at missing geometry. At minimum, reject:
- Unknown headers or versions, invalid lengths, truncated data and incorrect checksums.
- Zero-sized grids, a width above 8, a height above 5, or encodings that exceed 255 bytes.
- Unknown flag bits, cell modes, shape codes or illegal parameter values.
- Multiple occupied shapes assigned to one cell or shapes outside grid bounds.
- Runes with no occupied cells.
- Bridges that leave the grid, do not join two circles or two squares, or join shapes of different tones.
- Invalid
neckvalues, gap entries or spacing order. - Names or domain claims outside the declared encoding and validation rules.
- Unconsumed bytes or unexpected trailing data inside the declared binary length.
Drawing must not execute embedded code, evaluate arbitrary SVG/HTML, or fetch external resources. It operates only on validated primitives and geometric parameters.
Rendering and Hyperrune verification are separate operations. A rune must render without network access and without waiting for domain verification. A claimed domain must never be presented as proof of who authored, transmitted or owns a message.
Because the experimental text transport uses invisible characters, applications should treat it as untrusted input. Products incorporating Openrune into AI-assisted workflows may choose to remove the hidden payload from model-facing text while preserving a separate visual representation for users.
11. Early transport observations
An initial manual test on 19 October 2026 copied a Made Union rune through real applications, recovered the text and attempted a byte-for-byte decode. The tests recorded six successful text routes out of seven, with one failure. These are preliminary observations on specific devices and versions, not a universal platform compatibility guarantee.
| Route | Observed result |
|---|---|
| Claude chat title, Mac | Encoded data recovered intact |
| ChatGPT chat title, Mac | Encoded data recovered intact |
| Public Voice post, Mac | Encoded data recovered intact |
| Apple Notes, Mac | Encoded data recovered intact |
| WhatsApp sent to self, Android | Encoded data recovered intact |
| WhatsApp round trip via iPhone, Android ↔ iPhone | Encoded data recovered intact |
| Gmail, Mac | Invisible data removed; only fallback recovered |
A separate custom-font experiment preserved a private-use character in a Claude title but displayed it as an empty box in Safari; that result is not a successful text-transport route.
These tests establish only whether the transported bytes survived particular workflows. They do not establish that unsupported applications displayed the actual mark. Other services, devices, editing workflows and future updates may behave differently.
12. Implementation status and versioning
This document specifies a proposed binary version 1 within the broader v0.1 draft of the Openrune format. These are different version numbers: the first identifies encoded bytes; the second identifies the maturity of the specification.
An initial JavaScript implementation, openrune-core.js, and round-trip tests exist. Independent implementers will need publicly available conformance vectors and cross-implementation tests. This draft is not yet a completed, independently verified interoperability standard.
Corner-to-corner bridges (added 21 October 2026, before any decoders shipped). Bridge entries gained a second form that joins circles at opposite corners of a 2 × 2 block. It uses the top bit of the entry, which was previously invalid, so every rune made without such a bridge keeps exactly the same bytes and the same identifier. A decoder that predates this change will reject a rune that uses one and show its fallback character. Example: a 2 × 2 rune with a full-size circle in the top-left and bottom-right cells, joined by a medium bridge, encodes in 14 bytes as a10e020200460000460137800038.
Two tones (added 21 October 2026). A cell code gained a tone bit (0x08), which was previously unused. Every rune made without hatching keeps exactly the same bytes and the same identifier, and hatching costs no extra bytes. A decoder that predates this change reads a hatched cell as an unknown shape, rejects the rune and shows its fallback character. Example: a 2 × 1 rune with a solid square then a hatched square encodes in 9 bytes as a1090201001018009a.
Square sizes and square bridges; circles from 50% (added 22 October 2026, before any decoders shipped). A square gained a size, using the lower bits of its cell code that were previously required to be zero (0x10 is still a full-size square), and squares can now be bridged to other squares. The smallest circle is now 50%, so the code that meant 40% is invalid. Every rune made without these features keeps exactly the same bytes and the same identifier. A decoder that predates this change rejects a rune that uses a smaller square or a square bridge, and shows its fallback character. Example: two 80% squares side by side, joined by a medium bridge, encode in 12 bytes as a10c0201001212013700008f.
Any incompatible change to binary parsing, canonicalisation or geometry requires explicit versioning and matching test vectors. Experimental transport details and Hyperrune behaviour may be revised as security and interoperability testing continues.
Project: openrune.co
Specification text: CC BY 4.0 (as stated by the project)
Reference implementation: Apache 2.0 where published under that licence