| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
Vector Graphics Format (VGF) Specification
Version: 0.1 (Draft)
Status: Pre-implementation design specification
Table of Contents
- Overview
- Design Principles
- Primitive Types
- File Structure
- Feature Flags
- Dictionary
- Palette Table
- Pattern Table
- Component Table
- Rig Table
- Scene Table
- Animation
- Coordinate Spaces
- Rendering Model
- Field Encoding
- Open Questions
1. Overview
VGF is a structured binary vector graphics format targeting icon-scale assets in web and desktop applications. It is designed around three priorities:
- Bounded rendering cost. Every feature has a statically computable upper bound on rendering work, determined entirely from the file's declared parameters. No feature may introduce unbounded iteration, recursion, or evaluation cost.
- Structural intent. The format encodes what an image is (shapes, constraints, animation hints) rather than how to draw it (procedural drawing commands). Renderers have latitude in how they satisfy the structure.
- Shared assets. Components, rigs, palettes, and patterns are defined in global tables and referenced by index. Multiple scenes within a file share these tables, enabling compact representation of icon sets, glyph libraries, and themed asset collections.
VGF explicitly does not aim to replace general-purpose vector formats (SVG) or animation runtimes (game engines). Scripting, arbitrary filters, and DOM-style programmability are out of scope.
2. Design Principles
2.1 Bounded Cost
The total rendering work for any VGF file is bounded by a function of its declared table sizes, scene instance counts, component shape counts, and pattern bytecode lengths. A renderer may compute this bound before decoding any geometry and reject files that exceed its policy limits.
2.2 No External Dependencies
VGF files are self-contained. Text is represented as shaped components (glyph outlines stored as shapes, characters instanced via the rig system). No font loading, network access, or external resource resolution is required or permitted.
2.3 Declarative Animation
Animation is expressed as parameter tracks: time-varying scalar or vector values that drive rig transforms and material blends. There are no conditionals, callbacks, or general computation in animation data. Application-bound parameters are advisory hints; a renderer that does not support a given hint uses the parameter's declared default.
2.4 Best-Effort Rendering
VGF does not define mandatory feature support levels. A renderer encountering an unknown feature flag or unsupported constraint type should render what it can and skip what it cannot. Files should not rely on any specific renderer behaviour beyond the core 2D path and palette system.
2.5 Separation of Definition and Placement
Components define geometry. Rigs define how components are transformed and constrained. Scenes place rig instances into viewports. These three layers are strictly separated: a component cannot reference a rig, and a rig definition cannot reference a scene.
3. Primitive Types
All multi-byte integers are little-endian. No implicit alignment padding appears in the file format; fields are packed sequentially.
| Type | Size | Description |
|---|---|---|
u8 |
1 byte | Unsigned 8-bit integer |
u16 |
2 bytes | Unsigned 16-bit integer |
u32 |
4 bytes | Unsigned 32-bit integer |
i8 |
1 byte | Signed 8-bit integer |
i16 |
2 bytes | Signed 16-bit integer |
f16 |
2 bytes | IEEE 754 binary16 half-precision float |
f32 |
4 bytes | IEEE 754 binary32 single-precision float |
bool |
1 byte | 0x00 = false, 0x01 = true; other values reserved |
3.1 Variable-Width Coordinate
Within component path data, coordinate values are unsigned integers whose width in bytes is determined by the component's declared grid dimensions:
coord_bytes = ceil(ceil(log2(max(grid_w, grid_h) + 1)) / 8)
This yields 1 byte for grids up to 255 units, 2 bytes for grids up to 65535 units. The width is uniform across all path data within a single component and is computed once from the component header.
3.2 Index Encoding with Implicit Default
Table indices throughout the format use a 1-based offset convention: a stored value of 0 means "not present / use default" and stored value N refers to table entry N - 1. This allows optional fields to encode absence as zero without a separate presence flag.
The bit width of an index field is determined by the size of the table it references, rounded up to the nearest byte:
index_bits = ceil(log2(table_size + 1)) // +1 for the implicit default (0)
index_bytes = ceil(index_bits / 8)
A file with 200 palette entries uses 1-byte palette indices (fits in 8 bits with room for 0). A file with 300 palette entries uses 2-byte indices.
4. File Structure
[File Header]
[Table Directory]
→ Component Table offset + size
→ Rig Table offset + size
→ Palette Table offset + size
→ Pattern Table offset + size
→ Dictionary offset + size (0 if absent)
[Scene Directory]
→ Scene count
→ [Scene offset + size] × scene_count
[Table Data]
[Palette Table]
[Pattern Table]
[Component Table]
[Rig Table]
[Scene Data]
[Scene 0]
[Scene 1]
...
Offsets in the table and scene directories are absolute byte offsets from the start of the file. A renderer may seek to any table or scene without parsing preceding data.
4.1 File Header
| Field | Type | Description |
|---|---|---|
magic |
[4]u8 |
0x56 0x47 0x46 0x00 ("VGF\0") |
version |
u8 |
Format version; currently 1 |
feature_flags |
u32 |
See §5 |
dir_offset |
u32 |
Byte offset to Table Directory |
4.2 Table Directory
| Field | Type | Description |
|---|---|---|
palette_offset |
u32 |
Byte offset to Palette Table |
palette_size |
u32 |
Byte length of Palette Table |
pattern_offset |
u32 |
Byte offset to Pattern Table (0 if absent) |
pattern_size |
u32 |
Byte length of Pattern Table |
component_offset |
u32 |
Byte offset to Component Table |
component_size |
u32 |
Byte length of Component Table |
rig_offset |
u32 |
Byte offset to Rig Table |
rig_size |
u32 |
Byte length of Rig Table |
dict_offset |
u32 |
Byte offset to Dictionary (0 if absent) |
dict_size |
u32 |
Byte length of Dictionary |
4.3 Scene Directory
Immediately following the Table Directory:
| Field | Type | Description |
|---|---|---|
scene_count |
u16 |
Number of scenes in this file |
scenes |
[scene_count]SceneEntry |
Array of scene directory entries |
SceneEntry:
| Field | Type | Description |
|---|---|---|
offset |
u32 |
Byte offset to scene data |
size |
u32 |
Byte length of scene data |
5. Feature Flags
A u32 bitfield in the file header. Renderers should attempt best-effort rendering when encountering set bits they do not support.
| Bit | Name | Description |
|---|---|---|
| 0 | ANIMATION |
File contains animation tracks (§12) |
| 1 | APP_PARAMS |
Rigs declare application-bound parameters |
| 2 | PATTERNS |
Pattern Table is present (§8) |
| 3 | F16_PALETTE |
Palette contains F16×4 entries |
| 4 | GRADIENTS |
Palette contains gradient entries |
| 5 | MULTI_SCENE |
File contains more than one scene |
| 6 | DICTIONARY |
Dictionary is present (§6) |
| 7 | 3D_COORDS |
Reserved: 3D coordinate extension (not defined in this version) |
| 8–15 | — | Reserved; must be zero in version 1 |
| 16–31 | — | Vendor/experimental; renderers must ignore |
6. Dictionary
The dictionary is an optional table of UTF-8 string entries. Every named element in the file (components, rigs, scenes, parameters, palette entries) stores a dictionary index rather than an inline string. Stripping the dictionary produces a valid file; all references become opaque indices.
6.1 Dictionary Structure
| Field | Type | Description |
|---|---|---|
entry_count |
u16 |
Number of string entries |
entries |
[entry_count]DictEntry |
DictEntry:
| Field | Type | Description |
|---|---|---|
length |
u16 |
Byte length of the UTF-8 string |
data |
[length]u8 |
UTF-8 encoded string, not null-terminated |
Dictionary index 0 means "unnamed." Dictionary indices stored elsewhere in the file use the 1-based convention (§3.2).
7. Palette Table
The palette is a flat array of color entries. Entry count is recorded in the palette table header. All palette indices elsewhere in the file use the 1-based convention; index 0 means "transparent / no paint."
7.1 Palette Table Header
| Field | Type | Description |
|---|---|---|
entry_count |
u16 |
Number of palette entries |
Immediately followed by entry_count palette entries.
7.2 Palette Entry
Each entry begins with a 1-byte type tag:
| Tag | Format | Description |
|---|---|---|
0 |
RGBA32 | 8 bits per channel, sRGB + linear alpha |
1 |
F16×4 | Half-float per channel, linear; requires F16_PALETTE flag |
2 |
Linear Gradient | Two palette indices + angle |
3 |
Radial Gradient | Two palette indices + center + radius |
4 |
Pattern Ref | Index into Pattern Table |
RGBA32 entry (5 bytes total):
| Field | Type | Description |
|---|---|---|
tag |
u8 |
0 |
r |
u8 |
Red channel |
g |
u8 |
Green channel |
b |
u8 |
Blue channel |
a |
u8 |
Alpha channel |
F16×4 entry (9 bytes total):
| Field | Type | Description |
|---|---|---|
tag |
u8 |
1 |
r |
f16 |
Red channel |
g |
f16 |
Green channel |
b |
f16 |
Blue channel |
a |
f16 |
Alpha channel |
Linear Gradient entry (6 bytes total):
| Field | Type | Description |
|---|---|---|
tag |
u8 |
2 |
color_a |
palette index | From-color (1-based, must be RGBA32 or F16×4) |
color_b |
palette index | To-color (1-based, must be RGBA32 or F16×4) |
angle |
f16 |
Gradient angle in radians |
Palette index width is determined by palette entry count per §3.2.
Radial Gradient entry:
| Field | Type | Description |
|---|---|---|
tag |
u8 |
3 |
color_a |
palette index | Inner color |
color_b |
palette index | Outer color |
cx |
f32 |
Center X in normalised scene space (§13) |
cy |
f32 |
Center Y in normalised scene space |
radius |
f32 |
Radius in normalised scene space |
Pattern Ref entry:
| Field | Type | Description |
|---|---|---|
tag |
u8 |
4 |
pattern_idx |
pattern index | 1-based index into Pattern Table |
8. Pattern Table
Patterns are mathematical fill functions evaluated per pixel. Each pattern is a small stack-based bytecode program that maps (x, y) coordinates (in component-local normalised space) to a palette index.
8.1 Pattern Table Header
| Field | Type | Description |
|---|---|---|
entry_count |
u16 |
Number of pattern entries |
8.2 Pattern Entry
| Field | Type | Description |
|---|---|---|
color_count |
u8 |
Number of palette indices in the color set |
colors |
[color_count] palette index |
Palette entries available to the program |
instr_count |
u16 |
Number of bytecode instructions |
instructions |
[instr_count]u8 |
Bytecode (see §8.3) |
color_count must be ≥ 1 and ≤ 255. instr_count must be ≤ 256. These limits bound evaluation cost to a fixed maximum per pixel.
8.3 Pattern Bytecode
The evaluator maintains a stack of f32 values. Stack depth must not exceed 16 at any point during execution; a file violating this limit is malformed. Execution always terminates (no jump or loop instructions exist).
The final value on the stack is clamped to [0, color_count - 1], truncated to an integer, and used as an index into the pattern's color set.
| Opcode | Byte | Stack effect | Description |
|---|---|---|---|
PUSH_X |
0x01 |
→ x |
Push current X coordinate |
PUSH_Y |
0x02 |
→ y |
Push current Y coordinate |
PUSH_F32 |
0x03 |
→ v |
Push literal f32; next 4 bytes are the value |
ADD |
0x10 |
a b → a+b |
Add |
SUB |
0x11 |
a b → a-b |
Subtract |
MUL |
0x12 |
a b → a*b |
Multiply |
DIV |
0x13 |
a b → a/b |
Divide; result is 0 if b=0 |
MOD |
0x14 |
a b → a mod b |
Modulo; result is 0 if b=0 |
NEG |
0x15 |
a → -a |
Negate |
ABS |
0x20 |
`a → | a |
FLOOR |
0x21 |
a → floor(a) |
Floor |
FRACT |
0x22 |
a → fract(a) |
Fractional part (a - floor(a)) |
SQRT |
0x23 |
a → sqrt(a) |
Square root; result is 0 if a < 0 |
SIN |
0x24 |
a → sin(a) |
Sine (radians) |
COS |
0x25 |
a → cos(a) |
Cosine (radians) |
MIN |
0x26 |
a b → min(a,b) |
Minimum |
MAX |
0x27 |
a b → max(a,b) |
Maximum |
LT |
0x30 |
a b → a<b |
1.0 if a < b, else 0.0 |
GT |
0x30 |
a b → a>b |
1.0 if a > b, else 0.0 |
EQ |
0x32 |
a b → a==b |
1.0 if a == b (exact), else 0.0 |
SELECT |
0x40 |
cond a b → r |
r = a if cond ≥ 0.5, else b. No branching. |
DUP |
0x50 |
a → a a |
Duplicate top of stack |
SWAP |
0x51 |
a b → b a |
Swap top two values |
POP |
0x52 |
a → |
Discard top of stack |
8.4 Material Resolution for Patterns
When a pattern palette entry is used as a shape's material, the renderer evaluates the pattern bytecode at each pixel using that pixel's coordinates in component-local normalised space (§13.1). The result selects a palette entry from the pattern's color set. That entry must itself be a plain color (RGBA32 or F16×4); nested pattern references are not permitted.
9. Component Table
Components are the atomic geometry units of VGF. Each component defines a grid, a set of shapes (closed paths with materials), and optional named anchor points used by the rig system.
9.1 Component Table Header
| Field | Type | Description |
|---|---|---|
entry_count |
u16 |
Number of component entries |
9.2 Component Entry Header
| Field | Type | Description |
|---|---|---|
dict_name |
dict index | Optional name (0 = unnamed) |
grid_w |
u16 |
Grid width in integer units |
grid_h |
u16 |
Grid height in integer units |
shape_count |
u16 |
Number of shapes in this component |
anchor_count |
u8 |
Number of named anchor points |
byte_size |
u32 |
Total byte length of this component entry |
Followed by shape_count shape entries, then anchor_count anchor entries.
coord_bytes for this component is derived from max(grid_w, grid_h) per §3.1.
9.3 Shape Entry
Each shape is a closed path with a material.
| Field | Type | Description |
|---|---|---|
material |
Material | Variable-length material field (see §9.4) |
winding |
u8 |
Fill rule: 0 = non-zero, 1 = even-odd |
segment_count |
u16 |
Number of path segments |
segments |
[segment_count] Segment |
Path segments (see §9.5) |
Shapes within a component are rendered back-to-front in file order (first shape is furthest back).
9.4 Material Field
The material field uses a variable-size tag-based encoding. The first byte is a field presence bitfield:
| Bit | Field | Description |
|---|---|---|
| 0 | MODE |
Blend mode present; else Normal |
| 1 | PATTERN |
Pattern override present |
| 2 | COLOR |
One or more color overrides present |
| 3–7 | — | Reserved |
Fields are encoded in bit order (lowest bit first) immediately after the presence byte. Absent fields use their implicit defaults (mode = Normal, pattern = none, color = palette index 0 = transparent).
Mode field (u8, present if bit 0 set):
| Value | Mode | Description |
|---|---|---|
0 |
Normal | Standard alpha compositing |
1 |
Background | Render behind existing content |
2 |
Intersect | Clip to intersection with layer below |
3 |
Subtract | Cut from layer below |
4 |
Lighten | Max of source and destination |
5 |
Multiply | Multiply source and destination |
6 |
Clip | Affect mask stack, not color buffer |
Pattern field (pattern index, present if bit 1 set):
A 1-based index into the Pattern Table. If present, the pattern provides the base material; the color set below may override the pattern's declared colors.
Color field (present if bit 2 set):
| Field | Type | Description |
|---|---|---|
color_count |
u8 |
Number of palette indices following |
colors |
[color_count] palette index |
Color overrides |
When a pattern is also present, these colors replace the pattern's declared color set (same indexing). When no pattern is present, color_count must be 1 and the single entry is the shape's fill color.
9.5 Path Segments
Each segment begins with a 1-byte tag:
| Tag | Type | Additional data |
|---|---|---|
0 |
Line | end: [coord_bytes × 2] — endpoint |
1 |
Quadratic | ctrl: [coord_bytes × 2], end: [coord_bytes × 2] |
2 |
Cubic | ctrl1: [coord_bytes × 2], ctrl2: [coord_bytes × 2], end: [coord_bytes × 2] |
3 |
Arc | rx, ry: [coord_bytes] each, x_rot: f16, flags: u8, end: [coord_bytes × 2] |
Coordinates are unsigned integers in the component's local grid space. The path implicitly begins at the endpoint of the previous segment; the first segment begins at the endpoint of the last segment (closing the path).
Arc flags byte: bit 0 = large-arc, bit 1 = sweep direction (matches SVG arc flag semantics).
9.6 Anchor Points
Anchors are named points within a component's grid space, used as attachment targets for rig constraints.
| Field | Type | Description |
|---|---|---|
dict_name |
dict index | Name of this anchor |
x |
[coord_bytes] |
X coordinate in grid space |
y |
[coord_bytes] |
Y coordinate in grid space |
A component always has an implicit anchor center at (grid_w / 2, grid_h / 2) even if no anchors are declared.
10. Rig Table
Rigs bind components to a transform skeleton. A rig definition specifies which component it uses, a rest transform, optional constraints, optional parameter declarations, and optional material overrides.
10.1 Rig Table Header
| Field | Type | Description |
|---|---|---|
entry_count |
u16 |
Number of rig entries |
10.2 Rig Entry Header
| Field | Type | Description |
|---|---|---|
dict_name |
dict index | Optional name |
component_idx |
comp index | 1-based index into Component Table |
parent_rig_idx |
rig index | 1-based index into Rig Table; 0 = no parent |
rest_translation |
[f32; 2] |
Rest X, Y in parent space (or scene space if no parent) |
rest_rotation |
f32 |
Rest rotation in radians |
rest_scale |
f16 |
Rest scale relative to parent/scene minimum dimension |
origin_offset |
[f16; 2] |
Local origin offset for rotation/scale, in component grid fractions |
constraint_count |
u8 |
Number of constraints |
param_count |
u8 |
Number of declared parameters |
material_count |
u8 |
Number of material overrides |
byte_size |
u32 |
Total byte length of this rig entry |
Followed by constraint entries, then parameter declarations, then material overrides.
Rig entries may not form cycles through parent_rig_idx. A renderer must validate the parent chain is acyclic at load time.
10.3 Constraints
Each constraint begins with a 1-byte type tag:
Axis Lock — restrict movement to a line:
| Field | Type | Description |
|---|---|---|
tag |
u8 |
0 |
angle |
f32 |
Axis angle in radians |
Distance — maintain distance to another rig's anchor:
| Field | Type | Description |
|---|---|---|
tag |
u8 |
1 |
target_rig |
rig index | 1-based rig index |
target_anchor |
u8 |
Anchor index in target rig's component; 0 = center |
distance |
f32 |
Distance in scene space |
Look-At — orient toward another rig's anchor (affects rotation only):
| Field | Type | Description |
|---|---|---|
tag |
u8 |
2 |
target_rig |
rig index | 1-based rig index |
target_anchor |
u8 |
Anchor index in target rig's component; 0 = center |
Rotation Joint — constrain rotation to a range:
| Field | Type | Description |
|---|---|---|
tag |
u8 |
3 |
min_rot |
f32 |
Minimum rotation in radians (parent-relative) |
max_rot |
f32 |
Maximum rotation in radians (parent-relative) |
Translation Path — constrain translation to follow a parametric path:
| Field | Type | Description |
|---|---|---|
tag |
u8 |
4 |
segment_count |
u8 |
Number of path segments |
segments |
[segment_count] TranslationSegment |
Scene-space path |
Each TranslationSegment is a cubic Bézier: [f32; 2] × 4 (start, ctrl1, ctrl2, end) in scene space. The rig's position on the path is driven by a scalar parameter in [0, 1].
10.4 Parameter Declarations
Parameters are scalar or vector values that can be driven by animation tracks or application bindings.
| Field | Type | Description |
|---|---|---|
dict_name |
dict index | Parameter name |
param_type |
u8 |
0 = scalar f32, 1 = Vec2 (f32×2), 2 = angle f32 |
default_value |
f32 or f32×2 | Default value(s) |
min_value |
same type | Hard minimum (enforced by renderer) |
max_value |
same type | Hard maximum (enforced by renderer) |
source_hint |
u8 |
Advisory source (see below) |
Source hints (advisory; renderers may ignore):
| Value | Hint | Suggested binding |
|---|---|---|
0 |
None | No hint; use default or animation track |
1 |
Timer | Seconds elapsed since scene start |
2 |
CursorPos | Normalised cursor position in scene viewport |
3 |
CursorAngle | Angle from scene center to cursor, in radians |
4 |
ScrollOffset | Application scroll offset, normalised |
5–255 |
Reserved |
10.5 Material Overrides
Material overrides allow a rig instance to substitute palette entries for specific shapes within its component.
| Field | Type | Description |
|---|---|---|
shape_idx |
u8 |
0-based index of shape within the component; 255 = all shapes |
color_slot |
u8 |
Which color slot in the shape's material to override; 255 = all |
palette_idx |
palette index | Replacement palette entry (1-based) |
11. Scene Table
Scenes are viewports containing a list of rig instances. They are the top-level renderable units of a VGF file.
11.1 Scene Entry Header
| Field | Type | Description |
|---|---|---|
dict_name |
dict index | Optional name |
bg_palette_idx |
palette index | Background fill; 0 = transparent |
instance_count |
u16 |
Number of rig instances in this scene |
anim_count |
u16 |
Number of scene-level animation tracks |
byte_size |
u32 |
Total byte length of this scene entry |
Followed by instance_count rig instance entries, then anim_count animation track entries (§12).
11.2 Rig Instance
| Field | Type | Description |
|---|---|---|
rig_idx |
rig index | 1-based index into Rig Table |
translation |
[f32; 2] |
Scene-space translation, overrides rig rest |
rotation |
f32 |
Scene-space rotation in radians, overrides rig rest |
scale |
f16 |
Scale override; 0 = use rig rest scale |
param_count |
u8 |
Number of parameter value overrides |
params |
[param_count] InstanceParam |
Parameter value overrides |
mat_override_count |
u8 |
Number of material overrides |
mat_overrides |
[mat_override_count] MaterialOverride |
Per-instance material overrides |
Rig instances within a scene are rendered back-to-front in file order.
InstanceParam:
| Field | Type | Description |
|---|---|---|
param_idx |
u8 |
0-based index of parameter in rig's parameter list |
value |
f32 or f32×2 | Value to use (width determined by param type) |
MaterialOverride has the same structure as §10.5.
12. Animation
Animation is expressed as keyframe tracks on parameters. A track binds to a specific parameter (either on a rig or on a scene-level rig instance) and provides a sequence of timed keyframes.
Animation appears in two contexts:
- Rig-level animation: defined inside a Rig entry, animates that rig's own declared parameters.
- Scene-level animation: defined inside a Scene entry, animates the parameters of specific rig instances in that scene.
12.1 Animation Track
| Field | Type | Description |
|---|---|---|
target |
AnimTarget | What this track animates (see below) |
loop |
u8 |
0 = hold last keyframe, 1 = loop, 2 = ping-pong |
keyframe_count |
u16 |
Number of keyframes |
keyframes |
[keyframe_count] Keyframe |
AnimTarget (2 bytes):
| Field | Type | Description |
|---|---|---|
target_type |
u8 |
0 = rig parameter, 1 = material blend parameter, 2 = scene instance parameter |
target_idx |
u8 |
Parameter index within the target |
12.2 Keyframe
| Field | Type | Description |
|---|---|---|
time_ms |
u32 |
Time in milliseconds from track start |
value |
f32 or f32×2 | Keyframe value (width determined by parameter type) |
interp |
u8 |
Interpolation to next keyframe: 0=step, 1=linear, 2=ease-in-out |
12.3 Material Blend Animation
When target_type is 1 (material blend), the target parameter is a scalar [0, 1] blend factor. At render time the renderer resolves both the from and to materials per pixel (evaluating patterns and gradients as needed) and linearly interpolates the resulting linear-space RGBA values using this factor.
MaterialBlendTarget (replaces the target_idx byte for type 1):
| Field | Type | Description |
|---|---|---|
shape_idx |
u8 |
Shape within the component; 255 = all |
color_slot |
u8 |
Color slot; 255 = all |
from_palette |
palette index | From-material |
to_palette |
palette index | To-material |
Both palette entries are resolved per pixel. The blend factor drives a linear interpolation in linear RGB space. Palette entry types (RGBA32, F16, gradient, pattern) are resolved independently; the result of each resolution is a linear RGBA value at that pixel before blending.
13. Coordinate Spaces
VGF uses three coordinate spaces, each strictly layered.
13.1 Component Grid Space
Integer coordinates, origin at top-left, Y increasing downward. Range [0, grid_w] × [0, grid_h]. Used exclusively for path segment data within component definitions. The implicit center of a component is (grid_w / 2, grid_h / 2).
13.2 Scene Space (Normalised)
Float coordinates used for rig placement and scene-level values (gradient centers, path constraints). Origin at the center of the viewport. Axes are scaled so that 1.0 equals half the viewport's minimum dimension (i.e., min(viewport_width_px, viewport_height_px) / 2).
Examples for a 200×100 px viewport (minimum dimension = 100, half = 50):
(0, 0)— viewport center(1, 0)— 50px right of center(-1, 0)— 50px left of center(0, 1)— 50px below center(0, -1)— 50px above center(2, 1)— right edge, bottom edge (for this aspect ratio)
A rig at (0, 0) with scale 1.0 fills the viewport's minimum dimension exactly (the component's grid maps to [-1, 1] on both axes before the rig's own scale and origin offset are applied).
13.3 Component Local Normalised Space
Used by pattern bytecode and per-pixel evaluation. Float coordinates mapping the component grid to [0, 1] × [0, 1], origin at top-left. Pattern programs receive (x, y) in this space.
13.4 Transform Evaluation Order
For a rig instance at render time:
- Start with parent rig's resolved transform (or identity if no parent).
- Apply scene instance translation override (or rig rest translation if zero).
- Apply origin offset (translate so that the declared origin is at the current position).
- Apply rotation (scene instance rotation override or rig rest rotation).
- Apply scale (scene instance scale override, or rig rest scale if zero).
- Undo origin offset translation.
- Apply animation-driven parameter adjustments (additive on top of the above).
- Enforce constraint bounds.
Component grid coordinates are then mapped into this resolved transform to produce viewport pixel positions.
14. Rendering Model
This section describes the normative rendering intent. Renderer implementations may use any technique that produces equivalent results.
14.1 Scene Rendering
- Fill viewport with background palette entry (or transparent if index 0).
- For each rig instance in back-to-front order: a. Resolve the rig's component reference. b. Resolve the instance transform (§13.4), including all constraints. c. Allocate a local compositing buffer sized to the transformed component's bounding box (clipped to viewport). d. Render the component into the buffer (§14.2). e. Composite the buffer into the scene accumulation buffer using normal alpha compositing.
14.2 Component Rendering
- Allocate local buffer (transparent).
- For each shape in back-to-front order: a. Rasterize the closed path using the declared winding rule. b. Evaluate the material per pixel (resolve palette entry; evaluate pattern bytecode if a pattern is active; apply animation blend factor if a material blend is active). c. Composite into the local buffer using the shape's declared blend mode.
- Return the flattened local buffer.
14.3 Material Evaluation Per Pixel
Given a pixel at component-local normalised coordinates (x, y):
- If a material blend animation is active, evaluate both
fromandtomaterials at(x, y)and interpolate linearly in linear RGB space using the blend factor. - Otherwise, resolve the single material:
- If pattern: evaluate bytecode at
(x, y), select color from pattern's color set. - If gradient: evaluate gradient function at
(x, y), interpolate between the two gradient colors. - If plain color: use directly.
- If pattern: evaluate bytecode at
- Convert to linear RGB if not already (RGBA32 inputs are sRGB; apply gamma decode).
14.4 Depth and Layering
In 2D mode (feature bit 3D_COORDS not set), layering is determined entirely by render order (back-to-front). No depth buffer is used.
When 3D_COORDS is set, the behaviour is renderer-defined. This version of the specification does not define 3D rasterization behaviour.
15. Field Encoding Summary
This section collects the index-width rules for quick reference.
| Field context | Width formula |
|---|---|
| Palette index | ceil(log2(palette_entry_count + 1) / 8) bytes |
| Component index | ceil(log2(component_entry_count + 1) / 8) bytes |
| Rig index | ceil(log2(rig_entry_count + 1) / 8) bytes |
| Pattern index | ceil(log2(pattern_entry_count + 1) / 8) bytes |
| Dict index | ceil(log2(dict_entry_count + 1) / 8) bytes |
| Coord (per component) | ceil(log2(max(grid_w, grid_h) + 1) / 8) bytes |
All index fields use the 1-based convention: 0 encodes "absent / default," value N refers to entry N - 1.
All widths computed from the above formulas yield either 1 or 2 bytes for any practical table size. A renderer may reject files requiring wider index fields (e.g., a component table with more than 65534 entries).
16. Open Questions
The following design points are not yet resolved in this version of the specification. They are recorded here for discussion prior to a v0.2 draft.
16.1 Delta-encoded path coordinates. Storing each coordinate relative to the previous point could reduce coordinate byte width on large grids. This adds parser statefulness but may be worthwhile for compactness. Could be an optional per-component flag.
16.2 Scene-space integer coordinates. Rig placement transforms use f32, which is consistent with the rendering pipeline. An alternative is a scene-level integer grid (similar to components) with a declared resolution, using the same variable-width encoding. This would make scene authoring more consistent with component authoring but complicates the transform pipeline.
16.3 Gradient compatibility for material blending. When blending between two gradient palette entries, the current spec evaluates each gradient independently per pixel. This is correct but more expensive than blending gradient parameters directly. A restriction requiring matching gradient types for blending could allow parameter-level interpolation.
16.4 Explicit maximum scene depth. The spec currently bounds scene graph depth by file structure. An explicit limit (e.g., rig parent chains ≤ 8 deep) would make worst-case rendering cost more statically predictable. This may be worth adding as a validation rule.
16.5 Pattern index vs. field in shape material. Currently patterns are referenced via a dedicated palette entry type (tag 4). An alternative is a direct pattern index field in the material, distinct from the palette. This would simplify the palette type system at the cost of a larger shape material structure.
16.6 Animation on palette entries vs. material blend targets. The current design holds palette entries constant and animates materials via blend targets (two palette entries + a factor). A looser model would allow animation tracks to target palette entries directly. Held constant: GPU-friendly, simpler. Animatable: more expressive but harder to implement efficiently.
16.7 Versioning and deprecation policy. The current spec defines feature flags as advisory (best-effort rendering). A stricter model with "required" flags (like PNG's critical chunk bit) would allow files to signal that certain features must be supported for correct rendering. This trades strictness for compatibility.