wai-vector: the independent checker, written from the extension text alone, and a text fix (D7-4) #219
3 changed files with 2484 additions and 7 deletions
29
.github/workflows/wai-vector.yml
vendored
29
.github/workflows/wai-vector.yml
vendored
|
|
@ -16,7 +16,12 @@ name: wai-vector vector graphics
|
|||
# every expected file and every authoring form;
|
||||
# - the fuzz leg: the corpus-seeded fuzz of the WOS1 and WVG1 parsers at depth, each
|
||||
# accepted payload of small cost rendered, the peak-allocation test and the
|
||||
# duplicate-detection comparison count.
|
||||
# duplicate-detection comparison count;
|
||||
# - the independent checker, tools/wai_vector_check.py: Python standard library only,
|
||||
# written from the extension text alone and sharing no code with wai-vector, which
|
||||
# recomputes every expected file byte for byte (every frame by the closed-form
|
||||
# crossings in big integers), rewrites every payload and set to its own bytes, and
|
||||
# recomputes every digest of sha256.json.
|
||||
#
|
||||
# There is no Windows leg: the Windows runner has been offline since 2026-08-28.
|
||||
|
||||
|
|
@ -215,3 +220,25 @@ jobs:
|
|||
- name: Every workflow declares a concurrency group
|
||||
shell: bash
|
||||
run: bash scripts/check-workflow-concurrency.sh
|
||||
|
||||
checker:
|
||||
name: independent checker (Python)
|
||||
runs-on: linux
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
# Standard library only, so it needs nothing installed. Every frame of every
|
||||
# rendering vector is rendered, the clock vector's 3840 x 2160 frames included.
|
||||
- name: wai_vector_check.py recomputes every expected file of the corpus
|
||||
working-directory: wai
|
||||
shell: bash
|
||||
run: python3 -I tools/wai_vector_check.py corpus vector-conformance --quiet
|
||||
|
||||
- name: Every workflow declares a concurrency group
|
||||
shell: bash
|
||||
run: bash scripts/check-workflow-concurrency.sh
|
||||
|
|
|
|||
|
|
@ -164,8 +164,8 @@ Anything else is malformed. The walk never needs an index past `m`. A contour of
|
|||
**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**
|
||||
1. The magic, `format`, the `zero` field and the reserved bits of `flags`: `outline_malformed`.
|
||||
2. The header's counts `n_glyphs`, `points_total`, `contours_total` and `notes_len`, in field order: a count above its cap is `too_large`; `n_glyphs = 0` or `notes_len = 0` is `outline_malformed`.
|
||||
1. Fewer than the header's 32 bytes; the magic, `format`, the `zero` field and the reserved bits of `flags`: `outline_malformed`.
|
||||
2. The header's counts `n_glyphs`, `points_total`, `contours_total` and `notes_len`, one at a time in field order, each against its whole range before the next: a count above its cap is `too_large`, and `n_glyphs = 0` or `notes_len = 0` is `outline_malformed`. So `n_glyphs = 0` with `points_total` past its cap is `outline_malformed`.
|
||||
3. The exact length differs from the number of bytes in the set: `outline_malformed`.
|
||||
4. Each glyph record, in order: `n_points ≤ 16,383` (`points_limit`); then `n_contours ≤ n_points`, `n_contours ≥ 1` when `n_points ≥ 1`, and the `zero` field (`outline_malformed`).
|
||||
5. The `gid`s strictly increase.
|
||||
|
|
@ -229,7 +229,7 @@ Each table holds the number of records its header count states: `n_sizes` `SIZE`
|
|||
| `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`, 1 ..= `n_points`. `n_points: u16`, 1 ..= **16,383**. `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` | 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` |
|
||||
|
|
@ -248,7 +248,7 @@ Items are listed in **paint order**: the first is drawn first, at the bottom. An
|
|||
| 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 | 0 | the path's rule | 0 (glyphs are non-zero) |
|
||||
| 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`) |
|
||||
|
|
@ -326,7 +326,7 @@ A sink refuses a payload at load, before the first frame, and reports the first
|
|||
|
||||
**Load, once per payload**
|
||||
|
||||
1. `payload_malformed`: the magic, `format`, or the header's `zero` field.
|
||||
1. `payload_malformed`: a payload shorter than the header's 80 bytes; the magic, `format`, or the header's `zero` field.
|
||||
2. `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`); then `inline_len + held_len > 2^26`; then the computed exact length above 2^26.
|
||||
3. `payload_malformed`: the payload's length differs from the computed exact length.
|
||||
4. **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.
|
||||
|
|
@ -425,10 +425,11 @@ On either code the sink ignores the sidecar, keeps presenting the overlay, and r
|
|||
|
||||
**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:
|
||||
1. the document's first frame `⌈t0/P⌉` and last frame `⌊(t0 + dur − 1)/P⌋`;
|
||||
1. 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;
|
||||
2. 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`;
|
||||
3. 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 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. `probes` holds at least one entry, so the tool writes no sidecar for it.
|
||||
|
||||
A probe set is detection, not proof.
|
||||
|
||||
|
|
|
|||
2449
wai/tools/wai_vector_check.py
Normal file
2449
wai/tools/wai_vector_check.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue