WAI Extension: Vector Graphics (wai.graphics.vector)
Mirrored from the canonical text at commit 4ab66d24 ().
Status: Draft. This extension defines
wai.graphics.vector: titles, credits and other authored graphics carried as integer drawing instructions, which the sink rasterises into an overlay layer. This revision defines:
- the model, the conformance criteria and what they cover (§1);
- the payload format
WVG1, the outline-set formatWOS1, the digest sidecar, and the order in which a sink refuses a payload (§2);- the arithmetic: the clock, track evaluation, placement, the load-time interval check and cost, flattening, coverage and compositing, written as the raster stage family RS1–RS9 (§3).
Sections 4 to 6 and Annexes A and B are headings in this revision. Their text lands in later revisions of this Draft: outline sets and the default stroke set (§4, Annexes A and B), text and accessibility (§5), and packages and floors (§6). Where §1–§3 depend on them, they say what they depend on.
What a reader can build from this text. A sink that loads a
WVG1payload and theWOS1sets it names, refuses exactly the payloads §2.7 refuses with the code §2.7 gives, and renders each listed size, rate and frame to the bytes §3 defines; and a verifier that checks rendered frames against a digest sidecar (§2.10).The string
wai.graphics.vectoris not registered in this revision: SPEC §5 registers it once its conformance corpus is complete (§1.8). Reference implementation: thewai-vectorcrate (wai/wai-vector). Conformance:wai/vector-conformance/. This Draft precedes both.The keywords MUST, MUST NOT, SHOULD and MAY are as defined in RFC 2119 and RFC 8174.
1. Scope and conformance
1.1 Model
A wai.graphics.vector payload is a WVG1 (§2.2–§2.4): a fixed-width file of drawing instructions over a tick clock. It draws filled paths and runs of pre-shaped glyphs under animated tracks, inside isolated groups that may carry a clip path. Glyph outlines come from outline sets in the WOS1 format (§2.1). The payload names each set by SHA-256 and exact length, and either carries it (inline) or leaves the sink to obtain it (held).
The sink rasterises the instructions with integer arithmetic alone (§3) into an overlay layer: a premultiplied RGBA buffer, RGBA8P (§2.9), at a size the payload lists, for a frame rate it lists, at one frame. The payload also carries its words: text blocks in reading order, each with a language, a direction, a writing mode and a purpose, and clusters that map each run’s glyphs to the bytes of its block (§2.3). The sink never shapes, reorders or breaks text; the authoring tool does (§5).
Composition. The overlay is a separate object. A sink draws it into a surface of its own, above whatever it accompanies, and the platform composites that surface with source-over.
- The overlay never reads a pixel of anything below it.
- The payload defines no blend with anything below its own layer: no operation reads another plane, and there is no colour-management, scaling or display directive.
- Where video is decoded on a protected path that the application cannot read, the overlay layer stays outside that path. The composite is inside it, and is never claimed (§1.4).
Out of scope
- How the platform scales, blends, colour-manages or presents the overlay layer, and when a frame reaches the screen.
- Shaping, bidirectional reordering and line breaking (§5).
- Any outline format other than
WOS1: the sink never parses a font file (§4).
1.2 Conformance targets
This extension states requirements for three things.
- A sink: a program that loads a
WVG1and renders its frames. It conforms when it refuses every payload §2.7 refuses, reporting the first code §2.7 gives, and when, for every payload it accepts, its decode is overlay-equivalent and text-equivalent (§1.3). - An encoder: a program that writes a
WVG1. It conforms when every payload it writes passes every check of §2.7 at every size it lists, and meets the encoder rules of §2.5 and §5. - A verifier: a program that checks a payload, its sets and its digest sidecar. It conforms when it reports what a conforming sink reports, and in addition the encoder rules a sink does not check (§2.5, §5).
1.3 Overlay-equivalence and text-equivalence (normative)
A sink’s decode of a WVG1 payload is overlay-equivalent when, for each listed size z, listed rate r and frame g of the document that it renders, the bytes it produces are exactly R(P, S, z, r, g), the function §3.10 defines over the payload P and the outline sets S that P names. The bytes are the buffer RGBA8P (§2.9) at the size’s width and height. Overlay-equivalence covers that buffer and nothing after it.
A sink is text-equivalent when the projection it computes for every listed rate r and frame g, and the transcript it computes, are byte-identical to the JSON that §5 defines over P.
Exposure is a separate requirement (§5). A presenting sink MUST expose the projection to the platform’s accessibility interface. What the platform’s assistive technology then says, and when, is not claimed.
Once registered (§1.8), the capability’s conformance is SPEC §7’s bit-exact decode-equivalence for a StandardDefined capability. For this capability, that criterion is the two equivalences above, over the overlay layer and the computed text only.
1.4 What is covered
| stage | covered |
|---|---|
instructions → outlines → coverage → the overlay’s own compositing → RGBA8P at a listed size, rate and frame | yes (overlay-equivalence) |
RGBA8S, the straight-alpha view of the same buffer (§2.9) | yes, as a second named output |
| the projection and the transcript, as computed JSON (§5) | yes (text-equivalence) |
| exposure of the projection to the platform’s accessibility interface | a requirement (§5); what assistive technology says is not covered |
| the hand-off of the buffer to the platform’s graphics interfaces | no |
| scaling of the layer for presentation, blending over video or anything else below it, colour management, any mapping of the layer to a high-dynamic-range display, the panel | no |
when frame g reaches the screen | no |
| a frame in which the sink suppressed caption items (§5) | no: it is not the published frame, and the sink’s receipt says so (§2.10) |
| an unlisted size or rate | not rendered (size_unlisted, rate_unlisted, §2.7) |
What is claimed, and what is not
| claimed | not claimed |
|---|---|
| the instruction bytes, by content hash | the composite with video, or with anything else below the layer |
| each outline set, by SHA-256 over its whole bytes | the hand-off to the platform’s graphics interfaces, which may store a premultiplied buffer lossily |
the RGBA8P buffer of each rendered frame: checked at the probe frames of a digest sidecar, and at every frame in verify mode (§2.10) | scaling, colour management and display mapping of the layer |
| the projection and the transcript (§5) | when a frame reached the screen, or its timing against the video |
| anything about the video’s frames |
The wording rule. Every statement of this capability’s exactness names the overlay layer. None describes the screen, the picture, or what a viewer sees.
1.5 Fabrics
This revision states the exact decode on two fabrics: native CPU and WebAssembly. Every stage of §3 has a fixed width (§3.15), so a GPU can compute the same integers; a frame a GPU renders that matches the conformance corpus is evidence of that, not a claim this revision makes. A later revision states the GPU fabric.
1.6 Outline sets named in the payload
- No parameter set. Once registered as
StandardDefined(§1.8),wai.graphics.vectoris not one of the classes that decode against a parameter set (SPEC §3.1). Apriorpin on an envelope that names it is governed by SPEC §3.1’s rule for such a pin: an encoder MUST NOT write one, a verifier reports it (prior_not_applicable), and a sink ignores it. This extension adds no refusal for it. - The payload names its sets. Each
SETrecord (§2.3) names one outline set by SHA-256 over its whole bytes, and states its exact length. Those digests govern the decode. - A set is content. An outline set is like a font subset embedded in a document: it is not a model, a parameter set or code. The sink parses a fixed outline format and runs nothing from it. Carrying a set inline is therefore payload data, and the rule of the SPEC preamble that a model is never transported in the envelope that decodes against it does not reach it.
- Never substitute. A glyph ID means nothing without its exact set, so a sink MUST NOT render a run from any set other than the one its
SETrecord names. A set it cannot obtain refuses the payload (outline_not_held, §2.7).
1.7 What the decode reads
The decode reads the payload, and the outline sets the payload names by SHA-256 inside its own bytes. It reads no clock, random source, platform setting or any other input. The output is a function of the payload’s bytes and of bytes whose digests the payload fixes (§3.11, P6).
1.8 Registration (pending)
SPEC §5 registers wai.graphics.vector once the conformance corpus is complete. The registration will record:
- class
StandardDefined, fidelityLossless, intentreplicate, and role standalone; - a new informational
mediavalue,graphics: vector graphics and text that the sink draws from instructions. SPEC §8 lets a MINOR revision add a value of an informational field. The overlay is notvideo, so no companion that binds to video applies to it; - three statements:
- No post-decode directive (determinism-tiers §2, What a tier covers). The payload carries no post-decode processing directive. It has no operation that reads another plane, no blend with anything below the overlay layer, and no colour-management, scaling or display directive.
- What the decode reads (§1.7). The decode reads the payload and the outline sets it names by SHA-256 inside its own bytes. It decodes against no parameter set (SPEC §3.1).
- The intent is
replicate. The instructions are the authored source, and §3’s rendering is their defined reconstruction.
Until then, no envelope carries wai.graphics.vector as a registered capability, and SPEC’s registry does not list it.
2. Payload formats
2.0 Common rules (both formats)
- Integers are little-endian and fixed-width, two’s complement for the
i*types. There are no varints, no floating-point values, and no padding beyond the fields listed. - Zero fields. Every field named
zero, and every bit a field reserves, MUST be 0. - Tables are consumed in order, and no record carries an offset. An owner states a count, and the records it owns are the next ones in their table. No offset can point anywhere it should not, and no two records share bytes.
- The total length is exact. It is computed from the header’s counts before anything is allocated, and it MUST equal the payload’s length. Trailing bytes are refused.
- Caps bound every count before anything is allocated, as integer-operators §3 does. A count past its cap is
too_large. - Every refusal is named (§2.7). The checks run in the stated order, and the first failure is reported.
- Each step finishes over everything it reads before the next step starts.
- Within a step, failures are found in file order: table order, then record order, then field offset.
- Within one field, its own range is checked first, then any rule that relates it to earlier fields of its record. So a rule between two fields of one record is checked at the later field.
- A rule that reads another record or another table is checked only after every record has passed its own checks.
- Points. A point is a pair of
i16coordinates in outline units: twice the parent font’s units for a glyph, or the path’s own units for a path. y points down. - Notation is §3.0’s: integer-operators §2, and
R_rat.2^kis 2 to the powerk.
2.1 WOS1: the outline set
The sink reads this format, never a font file.
Layout: header (32 bytes) · glyph table (16 × n_glyphs) · contour ends (2 × contours_total) · points (5 × points_total) · notes (notes_len). The exact length is 32 + 16·n_glyphs + 2·contours_total + 5·points_total + notes_len. At the caps that is 31,457,312 bytes, below 2^25.
Header (32 bytes)
| off | size | field | rule |
|---|---|---|---|
| 0 | 4 | magic | the ASCII bytes WOS1 |
| 4 | 4 | format | u32 = 1 |
| 8 | 2 | upem2 | u16: twice the parent’s units per em. Informative: the sink never reads it, and any value is accepted |
| 10 | 2 | flags | u16. Bit 0: overlaps were removed (informative). Bits 1–15 are reserved |
| 12 | 4 | n_glyphs | u32, 1 ..= 65,536 |
| 16 | 4 | points_total | u32, ≤ 2^22 |
| 20 | 4 | contours_total | u32, ≤ 2^22 |
| 24 | 4 | notes_len | u32, 1 ..= 2^20 |
| 28 | 4 | zero |
Glyph record (16 bytes)
| off | size | field | rule |
|---|---|---|---|
| 0 | 2 | gid | u16: the parent font’s glyph ID. Strictly increasing across records |
| 2 | 2 | n_contours | u16, ≤ n_points; at least 1 when n_points ≥ 1 |
| 4 | 2 | n_points | u16, 0 ..= 16,383 |
| 6 | 2 | zero | |
| 8 | 8 | xmin, ymin, xmax, ymax | i16 each: exactly the minimum and maximum, over all of the glyph’s points, on-curve and control alike, of x and of y. All four are 0 for a glyph with no points |
Contour ends. For each glyph in glyph-record order, n_contours values, u16: the index, within the glyph, of each contour’s last point. They strictly increase, and the last equals n_points − 1. Contour i of a glyph is its points from one past the previous contour’s end (0 for the first) to its own end.
Points. For each glyph in glyph-record order, n_points points of 5 bytes: x: i16, y: i16, tag: u8, where tag is 0 for an on-curve point, 1 for a quadratic control and 2 for a cubic control. The origin is the glyph’s pen position on the baseline. An implied on-curve point of a quadratic outline (the midpoint of two consecutive quadratic controls) is an integer at twice font units, and is stored explicitly.
The contour grammar (normative; paths use it too, §2.3). Let a contour’s points be P_0 … P_{m−1}, m ≥ 1, and let index m mean P_0 again, which closes the contour. P_0 MUST be on-curve. Walk from i = 0 while i < m. From the on-curve point P_i:
P_{i+1}on-curve: emitline(P_i, P_{i+1}), and continue fromi + 1;P_{i+1}a quadratic control:P_{i+2}MUST be on-curve. Emitquad(P_i, P_{i+1}, P_{i+2}), and continue fromi + 2;P_{i+1}a cubic control:P_{i+2}MUST be a cubic control andP_{i+3}on-curve. Emitcubic(P_i, P_{i+1}, P_{i+2}, P_{i+3}), and continue fromi + 3.
Anything else is malformed. The walk never needs an index past m. A contour of one point emits nothing.
Orientation. Reversing every contour of a fill leaves its coverage unchanged (§3.11, P1). Reversing some contours changes which regions are holes, as under any non-zero rule, so a set orients its outer contours one way and its holes the other way (§4).
Notes. UTF-8 text inside the set’s digest: key=value lines, a blank line, then the parent font’s full licence text. §4 defines the keys. A sink checks only that the notes are valid UTF-8.
Load checks, in order
- Fewer than the header’s 32 bytes; the magic,
format, thezerofield and the reserved bits offlags:outline_malformed. - The header’s counts
n_glyphs,points_total,contours_totalandnotes_len, one at a time in field order, each against its whole range before the next: a count above its cap istoo_large, andn_glyphs = 0ornotes_len = 0isoutline_malformed. Son_glyphs = 0withpoints_totalpast its cap isoutline_malformed. - The exact length differs from the number of bytes in the set:
outline_malformed. - Each glyph record, in order:
n_points ≤ 16,383(points_limit); thenn_contours ≤ n_points,n_contours ≥ 1whenn_points ≥ 1, and thezerofield (outline_malformed). - The
gids strictly increase. - The glyph records’
n_pointssum topoints_total, and theirn_contourstocontours_total. - Each glyph’s contour ends strictly increase, and the last equals
n_points − 1. - Every
tagis 0, 1 or 2. - Every contour meets the contour grammar.
- Each glyph’s box is exact.
- The notes are valid UTF-8 (RFC 3629).
Steps 5–11 report outline_malformed. A sink makes no other check of a set. Whether the notes hold what §4 requires is the authoring tool’s obligation, which a verifier checks.
2.2 WVG1: header (80 bytes)
| off | size | field | rule |
|---|---|---|---|
| 0 | 4 | magic | the ASCII bytes WVG1 |
| 4 | 4 | format | u32 = 1 |
| 8 | 4 | tb | u32: ticks per second, 1 ..= 2^20 (120,000 recommended, §3.1) |
| 12 | 4 | t0 | u32: the document’s first tick, 0 ..= 2^31 − 1 |
| 16 | 4 | dur | u32: the document’s length in ticks, 1 ..= 2^31 − t0 |
| 20 | 2 | design_w | u16: the design canvas’s width in design pixels, 16 ..= 4096 |
| 22 | 2 | design_h | u16: its height, 16 ..= 4096. (4·design_w·design_h ≤ 2^26 follows from the two ranges.) |
| 24 | 1 | n_sizes | u8, 1 ..= 8 |
| 25 | 1 | n_rates | u8, 1 ..= 4 |
| 26 | 1 | n_sets | u8, 0 ..= 64 |
| 27 | 1 | n_tables | u8, 0 ..= 32 |
| 28 | 2 | n_paints | u16, 0 ..= 4096 |
| 30 | 2 | n_texts | u16, 0 ..= 65,535 |
| 32 | 2 | n_tracks | u16, 0 ..= 65,535 |
| 34 | 2 | n_paths | u16, 0 ..= 4096 |
| 36 | 4 | n_items | u32, 1 ..= 65,536 |
| 40 | 4 | n_keys | u32, ≤ 2^22 |
| 44 | 4 | n_glyphs | u32, ≤ 2^20 |
| 48 | 4 | n_clusters | u32, ≤ 2^20 |
| 52 | 4 | path_contours | u32, ≤ 2^20 |
| 56 | 4 | path_points | u32, ≤ 2^22 |
| 60 | 4 | strings_len | u32, ≤ 2^22 |
| 64 | 4 | inline_len | u32, ≤ 2^25: the bytes of the inline sets |
| 68 | 4 | held_len | u32, ≤ 2^26: the bytes of the held sets, as their SET records state them. inline_len + held_len ≤ 2^26 |
| 72 | 8 | zero |
The exact length is
80 + 8·n_sizes + 8·n_rates + 40·n_sets + 68·n_tables + 4·n_paints + 8·n_tracks + 12·n_keys + 16·n_paths + 2·path_contours + 5·path_points + 20·n_texts + 48·n_items + 12·n_glyphs + 12·n_clusters + strings_len + inline_len
and it MUST be at most 2^26 (too_large). So a sink reads at most 2^26 bytes of payload, and obtains, hashes and parses at most 2^26 bytes of sets for one payload.
2.3 WVG1: tables, in file order
SIZES · RATES · SETS · TABLES · PAINTS · TRACKS · KEYS · PATHS · PATH_ENDS · PATH_POINTS · TEXTS · ITEMS · GLYPHS · CLUSTERS · STRINGS · INLINE
Each table holds the number of records its header count states: n_sizes SIZE records, n_rates RATE records, and so on; path_contours PATH_END records, path_points PATH_POINT records; strings_len bytes of STRINGS and inline_len bytes of INLINE. The fields of each record are listed in offset order.
| record | bytes | fields and rules |
|---|---|---|
SIZE | 8 | w: u16, h: u16: each 16 ..= 4096; 8·w ≥ design_w and w ≤ 4·design_w; w·design_h = h·design_w (the same aspect, exactly). cost: u32: the exact per-frame cost at this size (§3.4(b)). Strictly ascending by w, so a width names one entry. (4·w·h ≤ 2^26 follows from the side ranges.) |
RATE | 8 | num: u32, den: u32: each 1 ..= 2^20; gcd(num, den) = 1; num ≤ 240·den; den·tb divisible by num, so P = den·tb/num ticks per frame is an integer. Strictly ascending by num/den, compared as num_a·den_b < num_b·den_a |
SET | 40 | carriage: u8: 0 held, 1 inline. zero: [u8; 3]. length: u32: the exact byte length of the WOS1, 32 ..= 2^25, for both carriages. sha256: [u8; 32]: the SHA-256 (FIPS 180-4) of the whole WOS1. The lengths of the inline sets sum to inline_len, and those of the held sets to held_len |
TABLE | 68 | E[0..17]: u32, an easing table in Q16: E[0] = 0, E[16] = 65536, and every E[i] ≤ 65536. A table need not be monotone |
PAINT | 4 | r, g, b, a: u8, premultiplied and sRGB-encoded: r, g, b ≤ a |
TRACK | 8 | kind: u8: 1 opacity, 2 translation, 3 matrix entry. zero: u8. zero: u16. n_keys: u32, 1 ..= 4096. The track’s keys are the next n_keys records of KEYS, and the tracks’ n_keys sum to the header’s n_keys |
KEY | 12 | t: u32: a tick, < 2^31. value: i32. interp: u8: 0 hold, 1 linear, or 2 + j for easing table j, with j < n_tables. zero: [u8; 3]. Within a track, keys strictly ascend in t, and the last key’s interp is 0. value by the track’s kind: opacity 0 ..= 255; translation −2^22 ..= 2^22, in design subpixels; matrix entry −2^20 ..= 2^20, in Q16 |
PATH | 16 | n_contours: u16, at least 1. n_points: u16, 1 ..= 16,383; then n_contours ≤ n_points, checked at n_points, the later field (§2.0). zero: u32. xmin, ymin, xmax, ymax: i16, exact over the path’s points as in WOS1. Its contour ends are the next n_contours records of PATH_ENDS, and its points the next n_points records of PATH_POINTS. The paths’ n_contours sum to path_contours, and their n_points to path_points |
PATH_END | 2 | u16: as a WOS1 contour end, within its path |
PATH_POINT | 5 | x: i16, y: i16, tag: u8: as a WOS1 point, in path units. A path’s contours meet §2.1’s contour grammar |
TEXT | 20 | a text block, in reading order. purpose: u8, 1 ..= 255 (§5; 13 is decorative). dir: u8: 0 left-to-right, 1 right-to-left. lang_len: u8, 1 ..= 63. mode: u8, the writing mode: 0 horizontal-tb, 1 vertical-rl, 2 vertical-lr. group: u16: 0 ungrouped. ruby: u16: 0 none; ruby = r ≥ 1 annotates block r − 1, so r ≤ n_texts. ruby_start: u32 and ruby_len: u32: a byte range of the base block’s string; both 0 when ruby = 0, and ruby_len ≥ 1 otherwise. str_len: u32, 1 ..= 4096. The blocks’ lang_len + str_len sum to strings_len |
ITEM | 48 | §2.4 |
GLYPH | 12 | gid: u16. zero: u16. x: i32, y: i32: the glyph’s pen position in design subpixels, relative to the run’s origin, each in [−2^23, 2^23). The run items, in item order, take their n_glyphs records in turn, in visual order. The runs’ n_glyphs sum to the header’s n_glyphs |
CLUSTER | 12 | glyph_count: u16. zero: u16. byte_first: u32. byte_len: u32. The run items, in item order, take their n_clusters records in turn, in glyph order: a run’s first cluster covers its first glyph_count glyphs, the next cluster the following ones, and so on. The runs’ n_clusters sum to the header’s n_clusters |
STRINGS | strings_len | for each TEXT in order, its language tag (lang_len bytes), then its string (str_len bytes) |
INLINE | inline_len | the inline WOS1 sets, concatenated in SET order: each inline set’s bytes are the next length bytes |
A text block’s string is its words in logical order, UTF-8. Its language tag is a BCP 47 tag; und is allowed. §5 defines how a sink exposes blocks, and the purpose vocabulary. This section uses two of its codes: 0 is refused, and 13 is decorative, text used as ornament, which is never exposed.
2.4 ITEM (48 bytes) and the scene
Items are listed in paint order: the first is drawn first, at the bottom. An item is a group begin (kind 0), a group end (1), a path fill (2) or a run fill (3).
| off | size | field | group begin (0) | group end (1) | path fill (2) | run fill (3) |
|---|---|---|---|---|---|---|
| 0 | 1 | kind | 0 | 1 | 2 | 3 |
| 1 | 1 | rule: 0 non-zero, 1 even-odd | the clip’s rule; 0 with no clip, checked at ref, the later field (§2.0) | 0 | the path’s rule | 0 (glyphs are non-zero) |
| 2 | 2 | zero | ||||
| 4 | 2 | ref | a clip path index (< n_paths), or 0xFFFF for no clip | 0 | a path index (< n_paths) | a set index (< n_sets) |
| 6 | 2 | paint | 0xFFFF | 0 | a paint index (< n_paints) | a paint index (< n_paints) |
| 8 | 2 | text | 0xFFFF | 0 | the path’s text alternative, a block index (< n_texts), or 0xFFFF for none | the block this run draws part of (< n_texts; required) |
| 10 | 2 | zero | ||||
| 12 | 2 | n_glyphs | 0 | 0 | 0 | 1 ..= 4096 |
| 14 | 2 | n_clusters | 0 | 0 | 0 | 1 ..= 8192 |
| 16 | 4 | t_on | a tick | 0 | a tick | a tick |
| 20 | 4 | t_off | t_on < t_off ≤ 2^31 | 0 | as a group begin | as a group begin |
| 24 | 4 | unit_scale | the clip path’s, 1 ..= 2^24; 0 with no clip | 0 | 1 ..= 2^24 | 1 ..= 2^24 |
| 28 | 14 | track slots m00, m01, m10, m11, tx, ty, op: u16 each, a track index (< n_tracks) or 0xFFFF for absent | m00 … m11 absent; tx, ty and op each a track or absent | all 0 | each a track or absent | each a track or absent |
| 42 | 6 | zero |
A field whose rule for the item’s kind is a fixed value (0, or 0xFFFF) and that holds another value is out of range (field_range, §2.7). Only the fields named zero are zero fields.
Rules
- Slot typing.
m00,m01,m10andm11name tracks of kind 3;txandtytracks of kind 2;opa track of kind 1. An absent slot means the identity for the matrix (65536 on the diagonal,m00andm11; 0 off it), 0 for a translation, and 255 for the opacity. - Groups balance, and nest at most 4 deep. Each group end closes the innermost group begin still open; there is no group end with no group open, and no group is open after the last item. An outermost group is at depth 1, and no group begin is at depth 5. A group is isolated: its children composite into a cleared target, which is composited into its parent at the group’s end (§3.9). A group carries a translation, an opacity and an optional clip path, and no matrix.
- Activity. An item is active at tick
τwhent_on ≤ τ < t_off. An inactive group begin skips everything up to its matching end. The document’s window[t0, t0 + dur)bounds frames, not item windows. unit_scaleis Q16 design subpixels per outline unit, or per path unit for a path or a clip.- A run draws its
n_glyphsglyphs from the set itsrefnames, at itsGLYPHpositions, and owns itsn_clustersclusters. It renders part of exactly one block, itstext. A block may be drawn by several runs: its lines, its bidirectional runs, and runs in other sets. - A path’s
textnames its text alternative, for example a wordmark’s words. It MUST NOT name a block of purpose 13 (decorative).
2.5 Canonical rules (one encoding per scene)
A payload has one encoding for each scene. A sink checks these rules at load (§2.7: rule 1 at steps 1, 3 and 4, rule 6 at step 5(f), and rules 2–5 at step 5(g)), and a verifier also checks them by a round trip: parse, write, compare.
- Zero fields are 0, and the length is exact.
- First-reference order.
- Walk the items in order, skipping group ends. In each item take
ref(as a path for kinds 0 and 2, as a set for kind 3; skipping0xFFFF), thenpaint(skipping0xFFFF), then the seven track slots in order, skipping0xFFFF. - In each pool (paths, sets, paints and tracks), the entries’ first references in that walk MUST appear in the order 0, 1, 2, …, and every entry of each pool MUST be referenced.
- Then walk the tracks in index order, and each track’s keys in order. Each key with
interp ≥ 2references tableinterp − 2. The tables’ first references MUST appear in the order 0, 1, 2, …, and every table MUST be referenced.
- Walk the items in order, skipping group ends. In each item take
- No duplicates. No two sets have the same
sha256. No two paints are equal; no two tracks are equal (the samekindand the same sequence of keys); no two paths are equal (the samePATHrecord, contour ends and points); and no two tables are equal.- A sink MUST detect duplicates by sorting or hashing each pool, never by comparing every pair: the tracks a payload can hold within every cap make about 2^31 pairs.
- Text blocks keep reading order, so rule 2 does not order them. Instead:
- every block MUST be named by the
textof at least one run or path, since a block nothing draws could never be exposed; - group numbers are assigned in order of first use: a block’s nonzero
groupis at most one more than the largestgroupof any earlier block; - the blocks of one nonzero group are contiguous in block order, so
[1, 2, 1]is refused; - a
rubylinkrnames an earlier block (r − 1is less than the block’s own index), and[ruby_start, ruby_start + ruby_len)lies within that block’s string, with both ends on code-point boundaries.
- every block MUST be named by the
- Clusters of a run.
- Their
glyph_counts sum to the run’sn_glyphs. - Each range
[byte_first, byte_first + byte_len)lies within the string of the run’s block, with both ends on code-point boundaries. glyph_count = 0only withbyte_len ≥ 1: characters that no glyph draws.byte_len = 0only withglyph_count ≥ 1: an inserted glyph, such as a hyphen at a break.
- Their
- Tags and strings.
- A language tag is ASCII letters, digits and hyphens, with no leading, trailing or doubled hyphen. This is a syntax check only: well-formedness under RFC 5646 and validity against its registry are encoder rules a verifier checks.
- A string is valid UTF-8 (RFC 3629) and holds no C0 control except U+000A, no U+007F and no C1 control (U+0080–U+009F).
Encoder rules a verifier checks and a sink does not are listed with the text they govern (§5): among them one bidirectional paragraph per block, one joining sequence per run, no zero-glyph cluster for a visible character, and every byte of a block’s string covered by a cluster.
2.6 Caps and bounds at a glance
| quantity | cap | purpose |
|---|---|---|
| payload | ≤ 2^26 bytes, computed before allocation | a bounded parse |
| sets, inline and held | each length ≤ 2^25; inline_len + held_len ≤ 2^26 | a bounded fetch, hash and parse |
| points per glyph and per path | ≤ 16,383 | the no-wrap theorem (§3.7) |
| glyphs per run | ≤ 4,096 | the run sum S ≤ 2^29 (§3.8) |
| fill instances (path items, group clips, glyph records) | ≤ 65,536 items and ≤ 2^20 glyph records | load work linear in them; a reused path or glyph counts once per instance in the cost |
| per-frame cost at each listed size | ≤ 2^32 − 1, stated in SIZE.cost (§3.4(b)) | bounded work per frame |
| design canvas and listed sizes | each side 16 ..= 4096; 1/8 ≤ w/design_w ≤ 4 | the tensor cap of integer-operators §3 on [4, h, w]; the placement widths (§3.3) |
| group depth | ≤ 4 | buffers per frame |
| track values | opacity 0 ..= 255; translation ±2^22 design subpixels; matrix entry ±2^20 (Q16) | the placement widths |
unit_scale | 1 ..= 2^24 (Q16) | the placement widths |
| glyph positions | −2^23 ..= 2^23 − 1 design subpixels | the placement widths |
| ticks | tb ≤ 2^20; every tick < 2^31, and t_off ≤ 2^31 | the interpolation widths |
| block strings | 1 ..= 4096 bytes each; all strings and tags ≤ 2^22 bytes | bounded exposure |
| digest sidecar | ≤ 2^17 bytes; 1 ..= 1,024 probes; ≤ 32 clips; every integer ≤ 2^31 − 1 | a bounded parse (§2.10) |
2.7 Refusals and their order
A sink refuses a payload at load, before the first frame, and reports the first failure in this order. Each check reads only the payload, the set bytes the sink holds, and values earlier steps have checked, so two conforming sinks that hold the same bytes report the same code.
The envelope. No refusal. A prior on an envelope that names this capability is governed by SPEC §3.1 (§1.6).
Load, once per payload
-
payload_malformed: a payload shorter than the header’s 80 bytes; the magic,format, or the header’szerofield. -
too_large, in field order: a count or length field of the header above its cap (n_sizes,n_rates,n_sets,n_tables,n_paints,n_paths,n_items,n_keys,n_glyphs,n_clusters,path_contours,path_points,strings_len,inline_len,held_len); theninline_len + held_len > 2^26; then the computed exact length above 2^26. -
payload_malformed: the payload’s length differs from the computed exact length. -
The record pass. One pass, in file order: first the header’s remaining fields, then every record of every table, each record’s fields in offset order.
- The header:
tb,t0,dur,design_w,design_h, andn_sizes,n_ratesandn_itemsat least 1, each against its range in §2.2 (field_range). - Each record: a nonzero
zerofield or reserved bit ispayload_malformed; an enumeration or a value out of its range isfield_range; aPATHwhosen_pointsexceeds 16,383 ispoints_limit. - The ranges include every index against the header count it indexes (
ref,paint,text, the track slots,interpagainstn_tables,rubyagainstn_texts),purpose = 0, aPATH_POINTtag other than 0, 1 or 2, and §2.4’s fixed values for the item’s kind.
A field’s rule reads only the header and earlier fields of its own record. A
SIZE’scostand aKEY’svaluehave no rule in this pass. - The header:
-
Cross-record structure, in this order, each check over all records in file order. Every check reports
payload_malformedunless noted.- (a) The sum rules: the
SETlengths, by carriage, toinline_lenandheld_len; theTRACKn_keyston_keys; thePATHn_contourstopath_contoursandn_pointstopath_points; theTEXTlang_len + str_lentostrings_len; the runs’n_glyphston_glyphsandn_clusterston_clusters. - (b) Ordering: the
SIZEs strictly ascend byw, and theRATEs bynum/den; within each track, keys strictly ascend int; the last key of each track hasinterp0. - (c) Typing,
field_range: eachKEY’svaluelies in the range its track’skindallows (§2.3), overKEYSin order; then each track slot of eachITEMnames a track of the kind the slot requires (§2.4), overITEMSin order. - (d) Group balance and depth (§2.4).
- (e) Each path’s contour ends (strictly increasing within the path, the last equal to
n_points − 1), its contour grammar (§2.1), and its exact box. - (f)
STRINGS: each language tag’s syntax, and each string’s validity (§2.5 rule 6). - (g) The canonical rules of §2.5: rule 2 (first reference), rule 3 (duplicates), rule 4 (text blocks: referenced, group numbering, group contiguity, ruby links), rule 5 (clusters).
- (h) A path item’s
textnaming a block of purpose 13 (decorative).
- (a) The sum rules: the
-
Sets, one at a time in
SETorder, each through these checks in this order before the next set:outline_not_held: a held set the sink cannot obtain under its resolution policy (for example, only the sets bundled with the sink, a cache, or a fetch by digest);outline_mismatch: the bytes obtained are not exactlylengthlong, or do not hash to the record’ssha256. A sink reads at mostlength + 1bytes of a held set, and hashes a set before it parses it, whatever its source. An inline set is itslengthbytes ofINLINE;- the
WOS1checks of §2.1, in their order (outline_malformed,too_large,points_limit).
A later revision that lets a key log revoke a held set adds two checks before these, in the order SPEC §3.1 uses for parameter sets. Their names,
outline_revokedandoutline_not_authorised, are reserved for it. -
glyph_missing: aGLYPHrecord’sgidnames no glyph record of its run’s set, over runs in item order and their glyphs in order. -
geometry_range: the load-time interval check (§3.4(a)) fails at some listed size. -
The cost (§3.4(b)), for each listed size in
SIZEorder:too_largeif the computed cost exceeds 2^32 − 1; otherwisepayload_malformedif it differs fromSIZE.cost.
The digest sidecar (§2.10) is checked after the payload loads. Its codes, sidecar_malformed and sidecar_mismatch, never refuse the payload: the sink ignores the sidecar and presents the overlay.
Render. These are request errors, not refusals of the payload. A conforming sink renders only listed sizes and rates, and frames in range.
size_unlisted: a request for a size noSIZElists.rate_unlisted: a request for a rate noRATElists.frame_range: a frameg(a non-negative integer) outside the document at that rate. A frame is in range when⌈t0/P⌉ ≤ g ≤ ⌊(t0 + dur − 1)/P⌋. The sink decides this by those divisions, never by formingg·Pfor an uncheckedg, becausePcan reach 2^40.
The one render-time outcome. layer_mismatch: a sink that holds a valid digest sidecar computed a different SHA-256 for a probe frame rendered as published (§2.10). It MUST stop presenting the overlay layer, present its floor if it holds one (packaging §7; §6) and otherwise nothing, and report the code in its receipt.
Host policy (not conformance). A sink MAY decline at load a payload it cannot afford, as resource_declined, and present its floor. It decides by comparing the SIZE.cost of the size it would render with its own budget. It MAY decline on the stated value before computing anything; a sink that presents the payload MUST have verified that value (step 9). Declining is not a refusal of the payload, and it is never part of an exact replay.
The product rule. Every refusal of a payload happens before the first frame, and a conforming payload is refused at no listed size, rate or frame. The only render-time outcome, layer_mismatch, needs a sidecar that passed its own checks.
2.8 Versioning
Under SPEC §8, once a capability string is registered its payload format is fixed.
format = 1is the only valuewai.graphics.vectoradmits, andWOS1format = 1is fixed with it.- A successor (gradients, a sink stroker, group matrices, effects, image paints, colour glyphs) is a new string, which cites this core by reference and adds operators. It never widens this one, as
wai.quantum.circuit2is a new string besidewai.quantum.circuit(SPEC §5). - There are no feature bits. Capability advertisement and SPEC §4’s fallback work on strings, and a string whose meaning depended on bits would make “supports
wai.graphics.vector” ambiguous.
2.9 The canonical buffers
-
RGBA8Pis the overlay layer’s buffer: row-major, top row first, 4 bytes per pixel in the order R, G, B, A, with no row padding:4·w·hbytes. The values are premultiplied and sRGB-encoded, never linearised. Every pixel satisfiesR, G, B ≤ A(§3.9). A 1920×1080 frame is 8,294,400 bytes. -
RGBA8Sis a second named output, for consumers that take straight alpha:(0, 0, 0, 0)whereA = 0;- otherwise each colour channel is
R_rat(255·c, A) = ⌊(255·c + ⌊A/2⌋)/A⌋, which lies in [0, 255], and alpha is unchanged.
It is exact as a buffer. A platform that premultiplies it again afterwards rounds in its own way, which is outside the claim (§1.4).
-
Digests in conformance material. A conformance vector that states the SHA-256 of a frame names the buffer
RGBA8Pwith its width and height, row-major, premultiplied, in the byte order R, G, B, A, as SPEC §7, Digests, requires for an output it does not otherwise name. A sequence of frames states each frame’s digest and the clip digest SPEC §7 defines. -
reconstruction-binding §2 defines its binding over
RGB8. A binding overRGBA8Pis not defined in this revision.
2.10 The digest sidecar
Frame digests travel in a digest sidecar beside the payload, never inside it, so adding a size or a rate changes the sidecar and never the payload’s hash.
{"clips":[[1920,1080,25,1,"<64 hex>"]],"instr":"<64 hex>","probes":[[1920,1080,25,1,0,"<64 hex>"],[1920,1080,25,1,749,"<64 hex>"]],"schema":"wai.graphics.digests/1"}
The format (normative)
- Form. One JSON object (RFC 8259) in UTF-8, with exactly the four members below, written in ascending byte order of their keys, with no whitespace. Integers are decimal, with no sign, fraction, exponent or leading zero. A digest is 64 lowercase hexadecimal digits. Nothing precedes or follows the object.
schemais the stringwai.graphics.digests/1.instris the SHA-256 of theWVG1payload’s bytes.clipsis an array of 0 ..= 32 entries[w, h, num, den, clip], at most one per listed (size, rate), in strictly ascending order of(w, h, num, den).clipis the SHA-256 of the 32-byte SHA-256 digests of every frame of the document at that size and rate, concatenated in ascending order ofg(SPEC §7, Digests).probesis an array of 1 ..= 1,024 entries[w, h, num, den, g, digest], in strictly ascending order of(w, h, num, den, g), so with no duplicates.digestis the SHA-256 of frameg’sRGBA8Pat that size and rate.
Limits and binding
- Size. The sidecar is at most 2^17 bytes. The largest canonical sidecar, with 1,024 probes and 32 clips at extreme values, is 111,715 bytes.
- Integers. Every integer, as written, is at most 2^31 − 1, so no reader forms a value it cannot hold.
- Each
(w, h)MUST be a listed size, and each(num, den)a listed rate. - Each probe’s
gMUST be in the document’s range at its rate, decided by division as in §2.7. - Binding.
instrMUST equal the SHA-256 of the payload the sink loaded.
Sidecar checks run after the payload loads, in this order:
sidecar_malformed: over 2^17 bytes; not this schema’s canonical form; a count, an integer, a size, a rate or agout of range; entries out of order or repeated.sidecar_mismatch:instrdiffers from the payload’s SHA-256, as for a stale or a substituted sidecar.
On either code the sink ignores the sidecar, keeps presenting the overlay, and records the code in its receipt.
Sink rules
-
A sink that holds a valid sidecar MUST hash every probe frame it renders as published, and compare the digest with the probe’s. At the first mismatch it stops presenting the overlay (
layer_mismatch, §2.7). -
It MUST NOT accept a frame on a 64-bit FNV-1a, or any digest other than SHA-256, alone (SPEC §7, Digests).
-
It MAY hash every frame it renders and check each clip digest it can complete: verify mode. Only a sink in verify mode writes a
reconstruct-sessionclaim (jwp-receipts §2.4.12), because that claim commits to every presented output by SHA-256. -
Suppressed frames. A frame in which the sink suppressed items to honour the viewer’s caption settings (§5) is not the published frame. For a probe at such a frame the sink MUST do one of two things:
- (a) render the frame as published, off-screen, for the check alone; or
- (b) skip the probe, and record
probe_skipped, with the reason, in its receipt.
It MUST NOT compare a suppressed frame with a published digest. Both outcomes conform, so exactness never overrides the viewer’s caption settings.
The authoring tool’s probe set. The tool builds the probe set deterministically, over the (size, rate) pairs it is asked to cover (by default every listed pair):
- Candidates, in priority order, for each pair:
- the document’s first frame
⌈t0/P⌉and last frame⌊(t0 + dur − 1)/P⌋, when the document has a frame at that rate, that is when the first is at most the last; - for each item, in item order, whose effective window (§3.4(b)), the ticks
a ≤ τ < b, holds at least one frame, that is⌈a/P⌉ ≤ ⌈b/P⌉ − 1: the first frame inside it,⌈a/P⌉, and the last,⌈b/P⌉ − 1; - for each key of each track, the first frame at or after the key’s tick
t,⌈t/P⌉, if that frame is in range.
- the document’s first frame
- The tool takes level 1, then level 2, then level 3. Within a level it takes candidates in ascending order of
(w, h, num, den, g), drops any it already holds, and stops at 1,024. The sidecar then lists the probes in its canonical order. - A document with no frame at any pair it covers has no candidate.
probesholds at least one entry, so the tool writes no sidecar for it.
A probe set is detection, not proof.
Delivery
- In a package, the sidecar is an entry named by its content hash, like every entry (packaging; §6), so a substituted sidecar is a package-level failure.
- Outside a package, a sink SHOULD fetch the sidecar from the same authenticated origin as the payload.
- A substituted sidecar whose
instris correct can only make a sink stop presenting the overlay (layer_mismatch). The floor then carries the words (§6).
Why probes, not every frame. At 1920×1080 and 25 fps, hashing every frame means hashing 207,360,000 bytes per second. The claim that every frame equals the reference is carried by the conformance corpus and its CI; probes catch a non-conforming renderer or a tampered set in the field.
2.11 Text digests
text_hashis the SHA-256 of theTEXTStable followed by theSTRINGSarea, exactly as carried. The format is canonical, sotext_hashcommits to every block’s purpose, direction, writing mode, language, group, ruby link and string, in reading order. A receipt or a clearance record can bind the words of a whole document with it.- A block’s identity is SHA-256(
"wai:text-block"‖0x01‖purpose‖dir‖mode‖lang_len‖ the tag ‖str_lenasu32little-endian ‖ the string), where the first term is those 14 ASCII bytes, andpurpose,dir,modeandlang_lenare one byte each.- It omits the positional fields (
groupandruby), so one line has the same identity in every document that carries it.
- It omits the positional fields (
- Slicing a document into pieces gives each piece its own payload hash and
text_hash(§6).
2.12 The JSON authoring form (informative)
The reference tool converts between WVG1 and an integer JSON form with one member per field, which makes vectors reviewable. The JSON form is never a payload, and a conforming sink never reads it.
3. Arithmetic (normative)
3.0 Notation, units and stages
The notation is integer-operators §2: ⌊a/b⌋ and ⌈a/b⌉ for b > 0, a mod b, R_half_up(p, s), isqrt, and fits iN. Every value is a mathematical integer, and no formula has an implicit width. This extension adds one function:
R_rat(n, d) = ⌊(2n + d)/(2d)⌋ford > 0: the rationaln/drounded half up, a tie toward +∞. Equivalently,q + (2r ≥ d ? 1 : 0)withq = ⌊n/d⌋andr = n − q·d.
R_rat and R_half_up are non-decreasing in their first argument, and map an exact multiple to its quotient. §3.2 and §3.4 rely on both.
| unit | meaning |
|---|---|
| outline unit (ou) | a WOS1 or path coordinate, i16; y down |
| design subpixel (ds) | 1/256 of a design pixel on the design_w × design_h canvas; y down; the origin at the top left |
| device subpixel (sp) | 1/256 of an output pixel at a listed size (w, h). Pixel (x, y) is the square [256x, 256x + 256) × [256y, 256y + 256) |
Stages and widths. Each stage computes at a fixed width, as the stage operators of integer-operators §7.4 do.
| stage | per | work | width | runs on |
|---|---|---|---|---|
| driver | item, frame | the clock, tracks, placement, the load-time interval check and the cost | i64 | CPU, Wasm |
| geometry | point, curve | point placement; flattening (RS1) | i64; flattening also i32, by an identity | CPU, Wasm; a GPU later |
| coverage | segment, cell, pixel | the row split (RS2), the cell split and accumulation (RS3), the scan (RS4), quantisation (RS5), the glyph-wise union (RS6) | i32 (u32 for the column product and the run sum) | CPU, Wasm, GPU |
| composite | pixel | paint × coverage × opacity and source-over (RS7), the group pop (RS8), the straight-alpha view (RS9) | i32 (every value < 2^24) | CPU, Wasm, GPU |
3.1 The clock
- For a listed rate
(num, den),P = den·tb/numticks per frame, an integer by §2.3. - Frame
gsits at tickτ(g) = g·P, on the same tick timeline ast0, the item windows and the keys. The document’s frames at that rate are thegwitht0 ≤ g·P < t0 + dur. - Frame digests are keyed by (size, rate,
g). Becausegis global, a piece cut from a document yields the same frame digests as the whole (§3.11, P7). tb = 120000makesPan integer for 24000/1001, 24, 25, 30000/1001, 30, 48, 50, 60000/1001, 60, 100, 120000/1001 and 120 fps.tb = 90000does not for 24000/1001, 60000/1001 and 120000/1001.- Informative. A sink takes
gfrom its media clock, for exampleg = ⌊(t_media − offset)·num/den⌋, clamped to the document. When a frame reaches the screen is the platform’s, and is not claimed.
3.2 Track evaluation (driver, i64)
A track’s value at tick τ, for its keys (t_i, interp_i, v_i), i = 0 … n − 1:
τ ≤ t_0givesv_0, andτ ≥ t_{n−1}givesv_{n−1}.- Otherwise take the
iwitht_i ≤ τ < t_{i+1}, and letΔ = t_{i+1} − t_i,r = τ − t_iandδ = v_{i+1} − v_i. Byinterp_i:- hold (0):
v_i; - linear (1):
v_i + R_rat(δ·r, Δ); - table
j(2 + j), withE = E_j:u = ⌊r·2^16/Δ⌋, which lies in [0, 65535];k = ⌊u/4096⌋;φ = u mod 4096;e = E[k] + ⌊(E[k+1] − E[k])·φ/4096⌋, which lies in [0, 65536]; the value isv_i + R_half_up(δ·e, 16).
- hold (0):
Properties
- Convexity. Every value lies between
v_iandv_{i+1}, and each key’s value is reached exactly, becauseR_ratandR_half_upare non-decreasing and exact on multiples, ande ∈ [0, 65536]. So an opacity track stays in 0 ..= 255 and no clamp is needed, and the load-time interval check (§3.4(a)) can use the keys’ range. - Integer speeds stay on the grid. With keys on frame ticks and
δ = 256·k·NoverNframes, framemof the move lands exactly onv_i + 256·k·m. - Widths. Linear:
2·δ·r + Δ < 2^56. Table:r·2^16 < 2^47, the table step< 2^28, the value step< 2^40. All arei64, on the driver.
3.3 Placement (driver, i64)
At a listed size (w, h), with dw = design_w, for a fill item at tick τ:
M = (m00, m01, m10, m11), in Q16, from its matrix slots (§3.2; the identity where absent);O = T_item + Σ T_g, in ds: the item’s translation(tx, ty)plus the translations of every group that encloses it, an exact integer sum;s = unit_scale.
The composed linear map, one per item per frame, in Q16 device subpixels per outline unit:
L_ij = R_rat(M_ij · s · w, dw · 2^16) for each of the four entries.
The origin of glyph record k, at its run-relative position (g_x, g_y) in ds:
t_k.x = R_rat((O_x·2^16 + m00·g_x + m01·g_y)·w, dw·2^16);t_k.y = R_rat((O_y·2^16 + m10·g_x + m11·g_y)·w, dw·2^16).
A path is one glyph record at (0, 0), so its origin is R_rat(O·w, dw) on each axis.
A point p = (p_x, p_y), in outline units, of glyph record k lands at the device point
d = t_k + (R_half_up(L00·p_x + L01·p_y, 16), R_half_up(L10·p_x + L11·p_y, 16)).
A group’s clip path is placed in the same way, with M the identity, O the sum of the translations of the group and of every group that encloses it, and s the group’s unit_scale.
Why this form (informative)
- Every glyph of a run shares one
L, so equal glyphs at equal subpixel phase (t_k mod 256on each axis) have identical device outlines up to a whole-pixel shift. A sink may therefore cache a glyph’s coverage under the key (set digest,gid,L,t_k mod 256), and the cache cannot change a byte (§3.11, P2). - A run’s positions and outlines transform rigidly together under
M, each origin rounded once. - At the design size with
Mthe identity,L = sandt = O: the design size is the identity map.
Widths at the caps of §2.6: L’s numerator < 2^58; |L| ≤ 2^30, so L fits i32; the point sum, with its rounding term, < 2^47; the origin’s numerator < 2^58. All are i64, on the driver.
3.4 The load-time interval check and the cost
(a) The interval check
At load, for every listed size, and for every path item, every glyph record of every run and every group with a clip, whether or not it is ever active, a sink computes the intervals below. It refuses the payload (geometry_range, §2.7 step 8) unless every end lies in [−2^22, 2^22 − 1].
- Track intervals. For each slot,
[min, max]over its track’s key values. An absent slot is its default, as a one-point interval. - Origin.
O’s interval is the componentwise sum of the translation intervals of the item and of every group that encloses it (for a clip: of the group and of every group that encloses it). L.L_ij ∈ [R_rat(lo·s·w, dw·2^16), R_rat(hi·s·w, dw·2^16)], fromM_ij ∈ [lo, hi]. For a clip,Mis the identity.- Per glyph record
kof a run, or the path’s or clip’s single record at(0, 0), on each axis, with that axis’s two matrix entriesm_1, m_2(m00, m01for x;m10, m11for y) and its two entriesL_1, L_2ofL:- the origin numerator’s interval is
[O]·2^16 + [m_1]·g_x + [m_2]·g_y, with[O]the interval ofOon that axis, where an interval times a scalar is the interval between the two end products, and a sum of intervals is the interval of the sums of the ends; - the origin’s interval is
R_rat(end·w, dw·2^16)at each end; - the point term’s interval is the corner hull of
[L_1] × [xmin, xmax]plus the corner hull of[L_2] × [ymin, ymax], thenR_half_up(·, 16)at each end. The box is the glyph’s exact box in its set (§2.1), or the path’s (§2.3). The corner hull of two intervals is the interval from the least to the greatest of the four products of their ends; - the device interval on that axis is the origin’s interval plus the point term’s.
- the origin numerator’s interval is
- Every end of every device interval MUST lie in
[−2^22, 2^22 − 1].
Soundness. Five facts make the check sound:
- (a) every evaluated track value lies in its key interval (§3.2);
- (b)
R_ratandR_half_upare non-decreasing; - (c) a product of two values lies in the corner hull of their intervals, and a sum in the sum of their intervals;
- (d) every point of a glyph or path, on-curve or control, lies in its exact box;
- (e) every flattened vertex is the floor of a convex combination of device control points, so it stays within their range on each axis (§3.5).
So every device coordinate of every frame lies in the checked range, and after the check every segment has |Δx|, |Δy| < 2^23.
Determinism. The rule is this interval computation, not the true range. It may refuse a document whose true range fits, and every sink refuses the same documents.
Authoring consequence (informative). At any listed size the range is ±16,384 output pixels. A long credit roll is therefore authored as blocks, each with its own short tracks and window, its keys covering only its time on and near the canvas; never as one long strip.
(b) The cost
The caps bound every allocation, but not the work of one frame: items may reuse one path, and every glyph record is its own fill. So each listed size states its per-frame cost, a function of the caps and of the intervals of (a) alone, computed once at load with no per-frame evaluation. For each listed size z = (w, h):
-
The box of a fill instance. A fill instance is a path item, a group’s clip, or one glyph record of a run. From its device interval
[x_lo, x_hi] × [y_lo, y_hi](step 4 of (a)):c_lo = max(0, ⌊x_lo/256⌋),c_hi = min(w − 1, ⌊x_hi/256⌋), andbw = max(0, c_hi − c_lo + 1);r_lo = max(0, ⌊y_lo/256⌋),r_hi = min(h − 1, ⌊y_hi/256⌋), andbh = max(0, r_hi − r_lo + 1).
The box is empty when
bw = 0orbh = 0. -
A fill’s cost is
c(f) = p·(2^10 + bw + bh) + bw·bh, wherepis the stored point count of its outline: theWOS1glyph’sn_points, or thePATH’s. A glyph with no points costsbw·bh. -
An item’s cost:
- a path item:
c(f); - a run item: the sum of
c(f_k)over its glyph records, plusRW·RH, whereRW = max c_hi − min c_lo + 1andRH = max r_hi − min r_lo + 1are taken over the glyph records whose box is not empty;RW·RHis 0 when every box is empty; - a group begin:
w·h, plusc(f)of its clip if it has one; - a group end: 0.
- a path item:
-
Effective windows. An item’s effective window is its
[t_on, t_off)intersected with the window of every group that encloses it and with[t0, t0 + dur). It may be empty. -
The cost of a size.
cost(z) = w·h + max over every integer tick τ of Σ cost(item), the sum taken over the items whose effective window containsτ(0 when none does).- The maximum is over every tick, not only frame ticks, so the cost is the same at every rate.
- It is computed by sorting the windows’ edges. At equal ticks, ends come before starts, because windows are half-open.
- Every quantity fits
i64: a fill’s cost is below 2^28, a run’s below 2^40, and a sum over at most 2^20 + 2^17 instances below 2^58.
-
The refusal.
cost(z)MUST be at most 2^32 − 1 (too_large), and MUST equalSIZE.costfor that size (payload_malformed). These are §2.7 step 9.
The cost counts every instance’s own points, so a path that many items reuse counts once per use.
What the cost bounds (informative). Suppose an implementation uses two freedoms §3.11 grants: it skips rows outside the canvas, and it folds the columns left of the canvas into one contribution per row piece. Then the procedure’s per-frame operation count for a fill (its segments, row pieces and cell sub-pieces, and the pixels it scans, quantises and composites) is at most 3·c(f):
- Segments
σ ≤ 343·p. Lines number at mostp. A quadratic uses 2 stored points and flattens to at most 2^9 segments; a cubic uses 3 and flattens to at most 2^10 (§3.5). - Row pieces number at most
σ + p·(bh + 1). A placed, flattened outline has at mostpy-monotone pieces (§3.7), so it crosses each of thebh + 1row boundaries at mostptimes. - Sub-pieces number at most
2·(row pieces) + p·(bw + 1), by the same argument on x. - Pixels:
bw·bh.
Summed, that is at most p·(1376 + 3·bh + bw) + bw·bh ≤ 3·c(f). An implementation that does not clip may do more work than the cost bounds. A GPU implementation lays out at most cost(z) records per frame, so u32 scratch offsets suffice.
Host policy. The ceiling makes every conforming document’s work finite and the same at every sink. It does not say that a device can render the document at speed. A sink compares SIZE.cost with its own budget, set from its own measurements, and may decline (resource_declined, §2.7). The authoring tool reports cost(z) for every listed size.
3.5 Flattening (RS1)
Every curve of every contour is flattened into a polyline. All points here are device points (§3.3).
Level
- A line is one segment.
- A quadratic
(P0, P1, P2):M = max over the two axes of |P0 − 2·P1 + P2|, andnis the least integern ≥ 0withM ≤ 64·4^n. - A cubic
(P0, P1, P2, P3):M = max over the two axes and over i ∈ {0, 1} of |P_i − 2·P_{i+1} + P_{i+2}|, andnis the least integern ≥ 0with3·M ≤ 64·4^n. - This bounds the chord deviation by 16 subpixels per axis, by the uniform-subdivision bound
d(d − 1)/8 · M/4^nfor a curve of degreed. - Inside the device range,
n ≤ 9for a quadratic andn ≤ 10for a cubic. Flattening never refuses.
Vertices (the closed form). The curve becomes the polyline P0, V_1, …, V_{2^n}, with, for k = 1 … 2^n and on each axis:
- quadratic, with
B = P1 − P0andA = P0 − 2·P1 + P2:V_k = P0 + ⌊(2^(n+1)·k·B + k²·A)/4^n⌋. The numerator is below 2^43; - cubic, with
B = P1 − P0,C = P0 − 2·P1 + P2andD = P3 − 3·P2 + 3·P1 − P0:V_k = P0 + ⌊(3·4^n·k·B + 3·2^n·k²·C + k³·D)/8^n⌋. The numerator is below 2^57.
V_{2^n} equals the end point exactly, so every contour closes. A contour’s polyline is its first point, followed, for each line, quadratic or cubic the contour grammar emits (§2.1), in order, by the line’s end point or the curve’s vertices V_1 … V_{2^n}. It ends at its first point, and its segments join consecutive points.
The 32-bit form (an identity). A fabric without i64 MAY compute the same vertices by a quotient–remainder forward difference. Each numerator and each of its forward differences in k is held as a pair (q, r) with 0 ≤ r < 2^b, where b = 2n for a quadratic and 3n for a cubic; the initial pairs come from shifts and masks, and each step adds pairs and carries once. Every value fits i32, and the vertices equal the closed form.
3.6 Row split and cell split (RS2, RS3)
Rows (RS2). Each segment (x0, y0) → (x1, y1) of a polyline is split at the pixel-row boundaries y = 256·r.
- A segment with
y0 = y1is skipped. - Otherwise let
(xt, yt)be the endpoint with the smaller y and(xb, yb)the other, withdx = xb − xtanddy = yb − yt > 0. The x at a row boundaryyisX(y) = xt + ⌊(y − yt)·dx/dy⌋: the floor of the exact rational, so the same from either endpoint and independent of the segment’s direction.X(yt) = xtandX(yb) = xb. - For each pixel row
rfrom⌊yt/256⌋to⌊(yb − 1)/256⌋, the row piece runs fromy_top = max(yt, 256·r)toy_bot = min(yb, 256·r + 256), with x fromX. It keeps the segment’s direction: from(X(y_top), y_top)to(X(y_bot), y_bot)wheny0 < y1, and the other way wheny0 > y1. Its row-local y isy − 256·r, so both ends’ y lie in [0, 256] and differ. - The 32-bit form (an identity). The first boundary after
ytis at most 256 from it, so⌊(y_b1 − yt)·dx/dy⌋forms a product below 2^31. Each later boundary adds⌊256·dx/dy⌋and carries a remainder in[0, dy). Every value fitsi32.
Cells (RS3). A row piece from (xa, ya) to (xb, yb), in row-local y with ya ≠ yb, is split at the column boundaries.
-
If
xa = xb, it is one sub-piece. -
Otherwise it is split at every column boundary
xc = 256·cstrictly betweenxaandxb, taken in order fromxatowardxb. The crossing’s y isy(xc) = ya + ⌊(xc − xa)·(yb − ya)/(xb − xa)⌋, the fraction’s signs normalised so that its denominator is positive. That is the floor of the exact y atxcon the line through the row piece’s two ends, so it is the same from either end.- The 32-bit form (an identity). Form
t = |xc − xa|·|yb − ya|andu = |xb − xa|in unsigned arithmetic, and takeya + ⌊t/u⌋whenyb > ya, orya − ⌈t/u⌉whenyb < ya. The product is below2^23·2^8 = 2^31.
- The 32-bit form (an identity). Form
-
The sub-pieces join
(xa, ya), the crossings in order, and(xb, yb). A sub-piece from(x_i, y_i)to(x_{i+1}, y_{i+1})lies in cellc = ⌊min(x_i, x_{i+1})/256⌋, with:fa = x_i − 256·candfb = x_{i+1} − 256·c, both in [0, 256];cover = y_{i+1} − y_i, with|cover| ≤ 256;area = cover·(fa + fb), with|area| ≤ 2^17.
A sub-piece with
cover = 0contributes nothing.
3.7 Accumulation and scan (RS3, RS4), and why nothing wraps
One fill is one glyph record of a run (§3.8), one path item, or one group’s clip.
The delta buffer. For one fill at a size (w, h), d[r][c] holds i32 values, zero to start, for 0 ≤ r < h and 0 ≤ c < w. For each sub-piece of the fill in a row r with 0 ≤ r < h:
c < 0:d[r][0] += 512·cover(the left fold);0 ≤ c < w:d[r][c] += 512·cover − area, and, ifc + 1 < w,d[r][c + 1] += area;c ≥ w: nothing.
Sub-pieces in rows outside [0, h) contribute nothing.
The scan (RS4). v[r][c] = Σ_{j ≤ c} d[r][j]. A full pixel is 512·256 = 2^17.
Density. For a fill and a row r, D_r = Σ over the fill's segments of |[yt, yb) ∩ [256·r, 256·r + 256)|: the y-length of each segment inside the row.
Theorem (no wrap, in any order). If D_r ≤ 2^22 − 1, then three kinds of value have magnitude at most 512·D_r ≤ 2^31 − 1: every partial sum of the contributions to one d[r][c], in any subset and any order; every final d[r][c]; and every sum of a contiguous range d[r][a..b], and so every prefix v and every partial sum that a tree or a sequential scan forms.
- Sketch. The sub-pieces of one segment in one row share a sign, and their
|cover|sums toδ_s ≤ 256. Each of the segment’s contributions to one cell is bounded by512·|cover|of its sub-piece, so they sum to at most512·δ_s. The segment’s share ofv[r][c]lies insgn·[0, 512·δ_s], so any contiguous range of its contributions is bounded by512·δ_s. Summing over segments gives512·D_r. Clipping on the right and the left fold change neither bound. ∎
The point cap makes the condition hold for every fill.
- A y-monotone polyline adds at most 256 to
D_rin any row. - The flattened polyline of a curve has no more changes of y-direction than its control polygon: sampling a monotone stretch at
k/2^nkeeps it monotone, and the floor is non-decreasing. Placement cannot add a change either, because its rounding is monotone: a strict rise can become flat, never a fall. - A closed control polygon of
ppoints has at mostpchanges of y-direction, whatever affine map placed it. - So
D_r ≤ 256·p. Withp ≤ 16,383,D_r ≤ 2^22 − 256, and every partial sum stays within512·D_r ≤ 2^31 − 2^17.
Consequences
- No value wraps, in any order, on any fabric. The rule of integer-operators §6 that an implementation MUST NOT depend on a value wrapping is met with no exception.
- Integer atomic addition on a GPU gives the true sum in any order.
- Even-odd is exact:
vis exact, sov mod 2^18is. - No refusal and no validity rule depends on a frame’s density.
3.8 Coverage: glyph runs, paths and clips (RS5, RS6)
A glyph run: the glyph-wise saturating union.
- Each glyph record
kof a run is its own fill: the contours of its glyph, placed at its origin (§3.3), filled under the non-zero rule. Its value at a canvas pixel isv_k, andc_k = min(|v_k|, 2^17). v_kis defined by the procedure.v_k(x, y)is the value §3.6 and §3.7 give for the glyph’s own fill on an unbounded grid: every row and every column, with no canvas clipping and no left fold.- Equivalently,
v_kis2^17times the signed area, within the pixel, of the polygon whose vertices are the §3.6 crossings: the snapped polygon, not the outline. - For example, the triangle (0, 0)–(512, 3)–(0, 3) gives
|v|= 1280 and 512 in its two pixels, where2^17times the exact area within each pixel is 1152 and 384. A sink that computes exact areas, in a glyph cache for example, produces other bytes, and does not conform.
- Equivalently,
- On every canvas pixel,
v_kequals the value §3.7’s left fold computes over the canvas. The contributions of the columns left of the canvas sum into column 0, which is the prefix they would have formed, and the columns at or pastwand the rows outside[0, h)never reach a canvas pixel. The left fold is how a sink normally computesv_k. S = Σ_k c_kover the run’s glyph records, at each canvas pixel. With at most 4,096 glyphs,S ≤ 2^29, which fitsi32andu32.- The run’s coverage is
α = R_half_up(255·min(S, 2^17), 17).
What the union does (informative).
- Where glyphs abut, each covering part of a pixel, the sum gives the pixel its full coverage, and no seam appears.
- Where glyphs overlap, the sum over-covers the partly covered edge pixels of the overlap: a darker knot. Exact union coverage needs a path boolean at the sink, which this capability does not have.
- The union applies within one run only. Across runs, fills composite by source-over, so splitting a joining sequence across runs would bring back the seam where its glyphs abut. The encoder keeps each joining sequence in one run (§5).
- Order and caching. Each
c_kdepends only on its glyph and its placement, so it can be cached, and a sum of non-negative terms is the same in any order.
A path is one fill over all its contours, under its rule:
- non-zero:
α = R_half_up(255·min(|v|, 2^17), 17); - even-odd:
m = v mod 2^18,m′ = min(m, 2^18 − m), andα = R_half_up(255·m′, 17).
Every product is below 2^25. Both rules are even in v, so reversing every contour of a fill does not change its coverage (§3.11, P1). A full pixel gives 255, and half a pixel 128. The non-zero rule is the chain abs, clamp(0, 2^17), then rescale with multiplier 255, shift 17 and half_up (integer-operators §5.4).
A clip is a path fill under the group’s rule, placed as §3.3 says. Its α is the mask m of §3.9.
3.9 Paint, opacity, compositing, groups and clips (RS7, RS8)
div255(x), for x ∈ [0, 65025], is ⌊257·(x + 128)/2^16⌋. It equals ⌊(2x + 255)/510⌋, the half-up rounding of x/255, over that whole range. It is the arithmetic of rescale with rq pre-bias 128, multiplier 257, shift 16 and floor (integer-operators §5.1, §5.4), restated here so that this family needs no graph. Its largest intermediate is below 2^24.
A fill at tick τ, with opacity o (its op track, §3.2) and premultiplied paint p, over coverage α at each pixel:
p′_ch = div255(p_ch·o)forch ∈ {R, G, B, A}, once per item per frame;s_ch = div255(p′_ch·α);- source-over into the current target
T:T_ch ← s_ch + div255(T_ch·(255 − s_A)).
A group. Its begin pushes a target cleared to (0, 0, 0, 0), and its children composite into that target. At its end, with G the popped target, m the clip’s coverage at each pixel (255 when the group has no clip) and o_g the group’s opacity:
k = div255(m·o_g);s_ch = div255(G_ch·k);- source-over into the parent, as for a fill.
The root starts at (0, 0, 0, 0) at every pixel, and is the frame’s RGBA8P.
No clamp is needed. div255 is non-decreasing and div255(255·x) = x, so c ≤ a ≤ 255 holds through every product and every source-over.
The order is part of the definition. Other orders of these operations, such as applying the opacity after the coverage, give other bytes, so only this order conforms.
Blending space. sRGB-encoded 8-bit values. Blending in linear light is a successor string (§2.8).
3.10 The frame procedure
R(P, S, z, r, g), for a payload P, its sets S, a listed size z = (w, h), a listed rate r and a frame g in range (§2.7):
τ = g·P_r.- Clear the root to
(0, 0, 0, 0). - For each item in order:
- group begin: if it is active at
τ, push a cleared target; otherwise skip every item up to and including its matching end; - path fill or run fill: if it is active at
τ, evaluate its tracks (§3.2), place it (§3.3), flatten (§3.5), split and accumulate each glyph record or the path (§3.6–§3.7), formα(§3.8), and composite (§3.9); - group end (of a group that was active): place the clip if there is one, form its
α, pop, and composite (§3.9).
- group begin: if it is active at
- Emit the root as
RGBA8P(§2.9).
A group’s translation and opacity, and its clip’s placement, are evaluated at τ like any track.
3.11 Properties, freedoms and prohibitions
| # | property | why |
|---|---|---|
| P1 | Orientation independence of a whole fill. Reversing every contour of a fill leaves every α unchanged. Reversing one contour of a fill with several contours does not, in general: it changes which regions are holes | the crossings are floors of exact rationals, the same from either end; every contribution negates; the quantisers are even in v |
| P2 | Whole-pixel translation equivariance. Moving a fill by (256·kx, 256·ky) moves its coverage by (kx, ky) pixels exactly | every rounding is a floor of a quantity whose integer part shifts by an integer |
| P3 | Order independence. Accumulation, the scan and the run sum give the same integers in any order | §3.7’s theorem; the run’s terms are non-negative |
| P4 | Premultiplied closure: c ≤ a ≤ 255 | §3.9 |
| P5 | Culling invariance. Skipping a glyph whose device box misses the canvas changes nothing | each row of a closed contour wholly left of the canvas folds a zero sum into d[r][0]; everything else is outside [0, h) or at columns ≥ w |
| P6 | The output is a function of (P, S, z, r, g) alone | no clock, random source or platform input is read |
| P7 | Slicing invariance | frames are keyed by the global g, and inactive items draw nothing |
An implementation MAY, as integer-operators §6 allows its operators:
- cache any intermediate wherever the definition yields the same integers. Examples: a glyph’s coverage under (set,
gid,L, phase); a credit roll moving a whole number of pixels per frame, as a copy of a cached strip (P2).- A roll moving
sdesign pixels per second lands on whole pixels at ratefand output widthWexactly whens·W/(design_w·f)is an integer. Caching across a document’s rates needs that at every listed rate and size, so where such caching matters an author writes one document per family of rates;
- A roll moving
- skip rows outside the canvas, and fold the columns left of the canvas into one contribution per row piece. These are the two freedoms under which §3.4(b)‘s cost bounds the work;
- partition work, reorder or regroup sums, skip zero contributions, compute in any width that holds the bounds (§3.12), cull, render later frames speculatively, and batch disjoint fills on a GPU.
An implementation MUST NOT:
- use floating-point arithmetic for any value this section defines;
- use an instruction whose result the instruction set leaves to the implementation; on Wasm, that means no relaxed SIMD;
- depend on a value wrapping;
- read any input outside
(P, S, z, r, g).
3.12 Bounds
The caps of §2.6 fix every bound below; none depends on a parameter set.
| value | bound | width | stage | basis |
|---|---|---|---|---|
a WOS1 or path point | |p| ≤ 2^15 | i16 | input | §2.1 |
matrix keys, translations, unit_scale, glyph positions | the caps of §2.6 | i32 | input | §2.7 |
linear interpolation 2·δ·r + Δ | < 2^56 | i64 | driver | §3.2 |
table easing: r·2^16; the table step; the value step | < 2^47; < 2^28; < 2^40 | i64 | driver | §3.2 |
L’s numerator; |L| | < 2^58; ≤ 2^30 | i64; i32 | driver | §3.3 |
the point sum L·p with its rounding term | < 2^47 | i64 | driver | §3.3 |
| the origin’s numerator | < 2^58 | i64 | driver | §3.3 |
| the interval check’s end products | as the three rows above | i64 | driver | §3.4(a) |
a fill’s cost c(f); a run’s cost; cost(z) before its check | < 2^28; < 2^40; < 2^58 | i64 | driver | §3.4(b) |
a sidecar’s or a request’s g against its range | decided by division, never as g·P | i64 | driver | §2.7 |
| a device coordinate | −2^22 ..= 2^22 − 1 | i32 | — | §3.4(a) |
| the flattening numerators, quadratic and cubic | < 2^43 (2^44 with the 4^n·P0 term); < 2^57 | i64 | RS1 | §3.5 |
| flattening, the 32-bit form | < 2^31 | i32 | RS1 | §3.5 |
| the row crossing: closed form; 32-bit form | < 2^46; < 2^31 | i64; i32 | RS2 | §3.6 |
| the column-crossing product | < 2^31 (unsigned) | u32 | RS3 | §3.6 |
cover; area | ≤ 2^8; ≤ 2^17 | i32 | RS3 | §3.6 |
the row density D_r | ≤ 2^22 − 256 | i32 | RS3 | §3.7 |
any partial sum into d, any partial sum of the scan, v | ≤ 512·D_r ≤ 2^31 − 2^17 | i32 | RS3, RS4 | §3.7 |
the run sum S | ≤ 2^29 | i32 / u32 | RS6 | §3.8 |
| a quantiser’s product | < 2^25 | i32 | RS5 | §3.8 |
compositing products; div255’s intermediate | ≤ 65,025; < 2^24 | i32 | RS7, RS8 | §3.9 |
| the straight-alpha numerator | ≤ 255·255 + 127 | i32 | RS9 | §2.9 |
3.13 What is exact, and what is merely deterministic
- Exact: the integers of this section, and so
RGBA8P; and the projection and the transcript (§5). - Deterministic, with a cost in quality (not a defect):
- Curves are polylines. The flattened polygon is the shape. The chord deviation is at most 16 subpixels per axis, about 0.088 pixel in distance.
- Over-coverage inside one fill, where two contours of one glyph overlap inside a partly covered pixel. The authoring tool removes overlaps inside a glyph where it can (§4).
- Join knots. Glyphs of one run that overlap over-cover the partly covered edge pixels of the overlap (§3.8). Cursive joins and marks touching their bases are such overlaps.
- The non-zero seam. Two contours of one fill that abut with opposite orientations cancel along the shared edge, so a pixel their union covers fully can read 0: two abutting rectangles reversed against each other give
α = 0at the seam. Sets orient outer contours and holes consistently (§4), and shapes are drawn as single contours. - The even-odd fringe. A contour duplicated under even-odd leaves the interior at 0, but its half-covered edge pixels quantise to 255: an opaque fringe one pixel wide around an invisible shape. The authoring tool removes duplicate contours, and a verifier flags a path with two equal contours.
- Seams across runs. Fills of different runs composite by source-over (§3.8).
- No hinting and no stem darkening are applied.
- Blending is in sRGB-encoded space.
- The mitigations live at authoring: overlap removal, consistent orientation, duplicate removal, one run per joining sequence, and heavier weights or optical-size instances for small text.
3.14 Strokes
There is no stroker at the sink. A stroke of an authored shape, such as a rule or a border, is expanded to a fill at authoring, and the fill is what the payload carries. The default stroke set ships expanded (§4). A normative integer stroker, needed for animated stroke parameters, is a successor string (§2.8).
3.15 The raster stage family RS1–RS9
The rasteriser’s stages are a stage family of their own, defined in this section. They cite integer-operators §2 for the notation and the rounding functions, and its §6 for the implementation rules: nothing wraps, no floating point, no relaxed SIMD, and the same freedoms. RS1–RS9 are not operators of wai-ops/1, and they add nothing to it.
| stage | name | what | width | refusal |
|---|---|---|---|---|
| RS1 | vec_flatten | §3.5’s closed form, and its 32-bit identity | i64; i32 by the identity | none |
| RS2 | vec_rows | §3.6’s row split, in closed form and by its 32-bit identity | i64; i32 by the identity | none |
| RS3 | vec_cells | the cell split and accumulation (§3.6–§3.7) | i32 (u32 for the column product) | none |
| RS4 | vec_scan | the row scan (§3.7) | i32 | none |
| RS5 | vec_alpha | the quantisers (§3.8) | i32 | none |
| RS6 | vec_union | S = Σ min(|v_k|, 2^17) and the run’s α (§3.8) | i32 / u32 | none |
| RS7 | vec_composite | paint × opacity × coverage, then source-over (§3.9) | i32 (every value < 2^24) | none |
| RS8 | vec_group | the group pop (§3.9) | i32 | none |
| RS9 | vec_unpremul | the straight-alpha view (§2.9) | i32 | none |
Every stage is total once the payload has loaded: the point cap, the range and the cost are load checks (§2.7), so no stage refuses. The driver’s scalar work (the clock, tracks, placement, the interval check and the cost) is i64 on CPU and Wasm, and is not a stage.
Widths on a 32-bit fabric. RS3–RS9 fit i32, and RS1–RS2 do so through their identities. Every product in an i32 stage is below 2^31 (§3.12), so the family needs no product formed from 16-bit halves.
4. Outline sets
This section lands in a later revision of this Draft: how a set is made from a font (authoring only), pinning and carriage, the keys of a set’s notes and the licence they carry, and the default stroke set (Annexes A and B). §2.1 defines the format a sink reads.
5. Text and accessibility
This section lands in a later revision of this Draft: what the authoring tool does to pre-shape text, the purpose vocabulary (§2.3 uses codes 0 and 13), clusters, the projection and the transcript with their exact JSON (text-equivalence, §1.3), exposure on each platform, the chain that checks that the words match the glyphs, and how captions stay under the viewer’s control.
6. Packages and floors
This section lands in a later revision of this Draft: graphics components and pieces in a package, slicing and its derivation record, and the text floor every graphics piece carries (packaging §7).
Annex A. WSF1, the stroke source format
Lands with §4.
Annex B. The default stroke set’s digest
Lands with §4.