open-standards/wai/tools/wai_vector_check.py
Transaction Science 4084a31bc6
All checks were successful
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 51s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m25s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m25s
standards workspaces build + test / aac-rs (pull_request) Successful in 2m28s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 56s
standards workspaces build + test / map-rs (pull_request) Successful in 54s
standards workspaces build + test / mesh-rs (pull_request) Successful in 53s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m28s
standards workspaces build + test / openpay (pull_request) Successful in 2m51s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 27s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 1m55s
wai-vector vector graphics / linux-aarch64 (pull_request) Successful in 1m45s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 7m26s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 6m32s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 19s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 1m32s
wai-vector vector graphics / macos-arm64 (pull_request) Successful in 1m33s
lockfiles / every Cargo.lock is current (pull_request) Successful in 37s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 3s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 3s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m8s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m59s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m38s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m10s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m11s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m19s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m10s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m22s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 4m5s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m8s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 13s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 8m43s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 9s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 27s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 49s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 2m2s
WAI section references / cited sections exist (pull_request) Successful in 10s
wai-vector vector graphics / linux-x86_64 (pull_request) Successful in 1m32s
wai-vector vector graphics / wasm32-wasip2 (wasmtime) (pull_request) Successful in 39s
wai-vector vector graphics / wasm32 under Node (pull_request) Successful in 21s
wai-vector vector graphics / wasm32 in three browser engines (pull_request) Successful in 2m20s
wai-vector vector graphics / fuzz, peak allocation, comparison count (pull_request) Successful in 16s
wai-vector vector graphics / independent checker (Python) (pull_request) Successful in 2m11s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 17m4s
graphics-vector 5.4: the worked example states each block's string and writing mode
The independent checker, written from the text alone, could not rebuild the
example's input from its list of blocks: the strings appeared only in the
printed output, and no block stated its writing mode. Each bullet now does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 02:46:34 -04:00

2918 lines
108 KiB
Python

#!/usr/bin/env python3
"""Independent conformance checker for `wai.graphics.vector`.
Written from the extension text alone: wai/extensions/graphics-vector.md, sections
1-3 and section 5 (the normative revision), with the notation of integer-operators
section 2 and the digests of SPEC section 7, which that text cites. It shares no code
with the reference crate, and was written without reading it.
Python 3.11 or later, standard library only. Run it with `python3 -I`.
What it computes, each from the section that defines it:
- WOS1 (section 2.1): the load checks in their order, the parse, and the canonical
rewrite from the parsed fields (counts derived, zero fields written as zero);
- WVG1 (sections 2.2-2.5, 2.7): the refusal order, steps 1-9, with the exact code
and step of the first failure; the parse; and the canonical rewrite;
- the load-time interval check, section 3.4(a), and the per-size cost, 3.4(b);
- the frame procedure R(P, S, z, r, g), section 3.10: the clock (3.1), tracks
(3.2), placement (3.3), flattening by the closed form (3.5), the row and cell
splits by the closed-form crossings, the floors of exact rationals in big
integers (3.6), accumulation and the scan (3.7), coverage (3.8) and compositing
(3.9), into RGBA8P (2.9); and RGBA8S;
- the digest sidecar (2.10): its canonical form and checks, the probe digests,
the clip digests, and the authoring tool's probe set;
- the text digests (2.11);
- the text layer (5.4): each block's active set from the effective windows of the
items that draw it, the projection at a frame of a listed rate and the transcript,
their JSON byte for byte, and the text record with its SHA-256; and the same
record a second way, from activity tick by tick and JSON with sorted keys;
- section 3.12's bounds, asserted on every value the procedure forms.
python3 -I wai_vector_check.py load PAYLOAD [--sets DIR]
python3 -I wai_vector_check.py render PAYLOAD WxH NUM/DEN G [--sets DIR] [--out F]
python3 -I wai_vector_check.py text PAYLOAD [NUM/DEN G] [--sets DIR] [--out F]
python3 -I wai_vector_check.py sidecar PAYLOAD SIDECAR [--sets DIR] [--verify]
python3 -I wai_vector_check.py selftest
python3 -I wai_vector_check.py corpus vector-conformance [--groups a,b] [--quiet]
`text` prints the SHA-256 of a payload's text record, and writes the record with
`--out`; with a rate and a frame it prints that frame's projection.
`corpus` recomputes every expected file under vector-conformance/ from its vector and
sets/ alone and requires the committed bytes, the text records among them, and
requires that it recomputed every file under expected/; rewrites every payload and
set to its own bytes; checks each refusal's step, and its canonical rule, where the
vector's name or description pins one; and recomputes every digest of sha256.json.
Exit status 1 on any failure.
"""
import bisect
import hashlib
import json
import os
import struct
import sys
from collections import namedtuple
# ------------------------------------------------------------------ refusals
class Refusal(Exception):
"""A refusal of section 2.7: its code, the step that found it, and where."""
def __init__(self, code, step, where=""):
super().__init__(f"{code} (step {step}) {where}")
self.code = code
self.step = step
self.where = where
class RequestError(Exception):
"""A render request error of section 2.7: size_unlisted, rate_unlisted, frame_range."""
def __init__(self, code):
super().__init__(code)
self.code = code
class SidecarCode(Exception):
"""sidecar_malformed or sidecar_mismatch (section 2.10)."""
def __init__(self, code, why=""):
super().__init__(f"{code}: {why}")
self.code = code
self.why = why
class BoundError(Exception):
"""A value past a bound section 3.12 (or the section it cites) states."""
# ------------------------------------------------------------------ section 3.12's bounds
#
# Each entry is (kind, limit): "lt" means |v| < limit, "le" means |v| <= limit.
# The checker asserts these on every value it forms; a tightened entry is how the
# falsification shows that a vector reaches a bound.
BOUNDS = {
# 3.2 track evaluation
"track.linear": ("lt", 1 << 56), # 2*delta*r + Delta
"track.table_r": ("lt", 1 << 47), # r*2^16
"track.table_step": ("lt", 1 << 28), # (E[k+1]-E[k])*phi
"track.value_step": ("lt", 1 << 40), # delta*e
# 3.3 placement, and 3.4(a)'s end products
"place.L_num": ("lt", 1 << 58),
"place.L": ("le", 1 << 30),
"place.point_sum": ("lt", 1 << 47), # L*p with its rounding term
"place.origin_num": ("lt", 1 << 58),
# 3.4(a): every device coordinate
"device.lo": ("ge", -(1 << 22)),
"device.hi": ("le_signed", (1 << 22) - 1),
# 3.4(a): every segment after the check
"segment.d": ("lt", 1 << 23),
# 3.5 flattening
"flatten.quad_n": ("le", 9),
"flatten.cubic_n": ("le", 10),
"flatten.quad_num": ("lt", 1 << 43),
"flatten.cubic_num": ("lt", 1 << 57),
# 3.6 row split: closed form, and the 32-bit form's first product
"rows.cross": ("lt", 1 << 46),
"rows.cross32": ("lt", 1 << 31),
# 3.6 cell split
"cells.product": ("lt", 1 << 31),
"cells.cover": ("le", 1 << 8),
"cells.area": ("le", 1 << 17),
# 3.7
"density": ("le", (1 << 22) - 256),
"accum": ("le", (1 << 31) - (1 << 17)),
# 3.8
"union.S": ("le", 1 << 29),
"alpha.product": ("lt", 1 << 25),
# 3.9
"composite.product": ("le", 65025),
"div255.intermediate": ("lt", 1 << 24),
# 2.9
"unpremul.num": ("le", 255 * 255 + 127),
# 3.4(b)
"cost.fill": ("lt", 1 << 28),
"cost.run": ("lt", 1 << 40),
"cost.sum": ("lt", 1 << 58),
}
# Section 3.7's per-fill density bound D_r <= 256*p is asserted with this slack.
DENSITY_SLACK = 0
# The largest |value| each bound saw, so a run can say which bounds a corpus reaches.
BOUND_PEAK = {}
def bound(name, v):
kind, lim = BOUNDS[name]
if kind == "lt":
a = abs(v)
ok = a < lim
elif kind == "le":
a = abs(v)
ok = a <= lim
elif kind == "ge":
a = v
ok = v >= lim
else: # le_signed
a = v
ok = v <= lim
if not ok:
raise BoundError(f"bound {name}: {v} against {kind} {lim}")
p = BOUND_PEAK.get(name)
if kind == "ge":
if p is None or a < p:
BOUND_PEAK[name] = a
elif p is None or a > p:
BOUND_PEAK[name] = a
return v
# ------------------------------------------------------------------ section 3.0 notation
def R_rat(n, d):
"""R_rat(n, d) = floor((2n + d) / (2d)), d > 0 (section 3.0)."""
assert d > 0
return (2 * n + d) // (2 * d)
def R_half_up(p, s):
"""integer-operators section 2: floor((p + 2^(s-1)) / 2^s), and p for s = 0."""
if s == 0:
return p
return (p + (1 << (s - 1))) >> s
def cdiv(a, b):
"""ceil(a / b) = -floor(-a / b), b > 0."""
assert b > 0
return -((-a) // b)
def div255(x):
"""Section 3.9: floor(257*(x + 128) / 2^16), x in [0, 65025]."""
if not 0 <= x <= 65025:
raise BoundError(f"div255 argument {x}")
t = 257 * (x + 128)
bound("div255.intermediate", t)
return t >> 16
def unpremul(px):
"""RGBA8S of one RGBA8P pixel (section 2.9)."""
r, g, b, a = px
if a == 0:
return (0, 0, 0, 0)
out = []
for c in (r, g, b):
bound("unpremul.num", 255 * c + a // 2)
out.append(R_rat(255 * c, a))
return (out[0], out[1], out[2], a)
def rgba8s(buf):
out = bytearray(len(buf))
for i in range(0, len(buf), 4):
out[i:i + 4] = bytes(unpremul(buf[i:i + 4]))
return bytes(out)
def sha256(b):
return hashlib.sha256(b).digest()
# ------------------------------------------------------------------ section 2.1: WOS1
WOS1_HDR = struct.Struct("<4sIHHIIIII")
WOS1_GLYPH = struct.Struct("<HHHHhhhh")
class Wos1Glyph:
__slots__ = ("gid", "n_contours", "n_points", "box", "ends", "points", "prims")
class Wos1:
__slots__ = ("upem2", "flags", "glyphs", "notes", "by_gid", "digest", "length", "bytes")
def _om(where):
return Refusal("outline_malformed", "6", where)
def walk_contour(tags):
"""Section 2.1's contour grammar over one contour's tags.
Returns the emitted primitives as tuples of point indices within the contour,
index m written as 0, or None when the contour is malformed."""
m = len(tags)
if m == 0 or tags[0] != 0:
return None
if m == 1:
return [] # "A contour of one point emits nothing."
def tag(j):
return tags[j if j < m else 0]
prims = []
i = 0
while i < m:
t1 = tag(i + 1)
if t1 == 0:
prims.append(("L", i, (i + 1) % m))
i += 1
elif t1 == 1:
if i + 2 > m or tag(i + 2) != 0:
return None
prims.append(("Q", i, i + 1, (i + 2) % m))
i += 2
elif t1 == 2:
if i + 3 > m or tag(i + 2) != 2 or tag(i + 3) != 0:
return None
prims.append(("C", i, i + 1, i + 2, (i + 3) % m))
i += 3
else:
return None
return prims
def exact_box(points):
if not points:
return (0, 0, 0, 0)
xs = [p[0] for p in points]
ys = [p[1] for p in points]
return (min(xs), min(ys), max(xs), max(ys))
def parse_wos1(b):
"""Section 2.1's load checks, in order; returns the parsed set."""
# 1. magic, format, zero, reserved bits of flags
if len(b) < 32:
raise _om("wos1 step 1: shorter than the header")
magic, fmt, upem2, flags, n_glyphs, points_total, contours_total, notes_len, zero = \
WOS1_HDR.unpack_from(b, 0)
if magic != b"WOS1":
raise _om("wos1 step 1: magic")
if fmt != 1:
raise _om("wos1 step 1: format")
if flags & 0xFFFE:
raise _om("wos1 step 1: reserved flags")
if zero != 0:
raise _om("wos1 step 1: zero")
# 2. the counts, in field order
if n_glyphs > 65536:
raise Refusal("too_large", "6", "wos1 step 2: n_glyphs")
if n_glyphs == 0:
raise _om("wos1 step 2: n_glyphs = 0")
if points_total > 1 << 22:
raise Refusal("too_large", "6", "wos1 step 2: points_total")
if contours_total > 1 << 22:
raise Refusal("too_large", "6", "wos1 step 2: contours_total")
if notes_len > 1 << 20:
raise Refusal("too_large", "6", "wos1 step 2: notes_len")
if notes_len == 0:
raise _om("wos1 step 2: notes_len = 0")
# 3. exact length
exact = 32 + 16 * n_glyphs + 2 * contours_total + 5 * points_total + notes_len
if exact != len(b):
raise _om(f"wos1 step 3: length {len(b)} against {exact}")
# 4. each glyph record
glyphs = []
off = 32
for k in range(n_glyphs):
gid, nc, npt, z, x0, y0, x1, y1 = WOS1_GLYPH.unpack_from(b, off)
off += 16
if npt > 16383:
raise Refusal("points_limit", "6", f"wos1 step 4: glyph {k} n_points")
if nc > npt:
raise _om(f"wos1 step 4: glyph {k} n_contours > n_points")
if npt >= 1 and nc == 0:
raise _om(f"wos1 step 4: glyph {k} n_contours = 0")
if z != 0:
raise _om(f"wos1 step 4: glyph {k} zero")
g = Wos1Glyph()
g.gid, g.n_contours, g.n_points, g.box = gid, nc, npt, (x0, y0, x1, y1)
glyphs.append(g)
# 5. gids strictly increase
for k in range(1, n_glyphs):
if glyphs[k].gid <= glyphs[k - 1].gid:
raise _om(f"wos1 step 5: gid of glyph {k}")
# 6. sums
if sum(g.n_points for g in glyphs) != points_total:
raise _om("wos1 step 6: points_total")
if sum(g.n_contours for g in glyphs) != contours_total:
raise _om("wos1 step 6: contours_total")
# 7. contour ends
ends_off = 32 + 16 * n_glyphs
for k, g in enumerate(glyphs):
g.ends = list(struct.unpack_from(f"<{g.n_contours}H", b, ends_off))
ends_off += 2 * g.n_contours
for j in range(1, len(g.ends)):
if g.ends[j] <= g.ends[j - 1]:
raise _om(f"wos1 step 7: glyph {k} contour ends")
if g.ends and g.ends[-1] != g.n_points - 1:
raise _om(f"wos1 step 7: glyph {k} last end")
# 8. tags
pts_off = ends_off
for g in glyphs:
pts = []
for _ in range(g.n_points):
pts.append(struct.unpack_from("<hhB", b, pts_off))
pts_off += 5
g.points = pts
for k, g in enumerate(glyphs):
for p in g.points:
if p[2] > 2:
raise _om(f"wos1 step 8: glyph {k} tag {p[2]}")
# 9. contour grammar
for k, g in enumerate(glyphs):
g.prims = contours_of(g.ends, g.points)
if g.prims is None:
raise _om(f"wos1 step 9: glyph {k} contour grammar")
# 10. exact box
for k, g in enumerate(glyphs):
if exact_box(g.points) != g.box:
raise _om(f"wos1 step 10: glyph {k} box")
# 11. notes
notes = b[pts_off:]
assert len(notes) == notes_len
try:
notes.decode("utf-8", errors="strict")
except UnicodeDecodeError:
raise _om("wos1 step 11: notes are not UTF-8")
s = Wos1()
s.upem2, s.flags, s.glyphs, s.notes = upem2, flags, glyphs, bytes(notes)
s.by_gid = {g.gid: g for g in glyphs}
s.digest = sha256(b)
s.length = len(b)
return s
def contours_of(ends, points):
"""Per contour: (start index, its primitives with indices into `points`), or None."""
out = []
start = 0
for e in ends:
tags = [points[j][2] for j in range(start, e + 1)]
prims = walk_contour(tags)
if prims is None:
return None
out.append((start, [(p[0],) + tuple(start + j for j in p[1:]) for p in prims]))
start = e + 1
return out
def write_wos1(s):
"""Section 2.1's layout from the parsed fields; counts derived, zero written as 0."""
out = bytearray()
pts_total = sum(len(g.points) for g in s.glyphs)
con_total = sum(len(g.ends) for g in s.glyphs)
out += WOS1_HDR.pack(b"WOS1", 1, s.upem2, s.flags, len(s.glyphs), pts_total, con_total,
len(s.notes), 0)
for g in s.glyphs:
out += WOS1_GLYPH.pack(g.gid, len(g.ends), len(g.points), 0, *exact_box(g.points))
for g in s.glyphs:
out += struct.pack(f"<{len(g.ends)}H", *g.ends)
for g in s.glyphs:
for p in g.points:
out += struct.pack("<hhB", *p)
out += s.notes
return bytes(out)
# ------------------------------------------------------------------ sections 2.2-2.4: WVG1
HDR = struct.Struct("<4sIIIIHHBBBBHHHHIIIIIIIIIQ")
HDR_FIELDS = ("magic", "format", "tb", "t0", "dur", "design_w", "design_h", "n_sizes",
"n_rates", "n_sets", "n_tables", "n_paints", "n_texts", "n_tracks", "n_paths",
"n_items", "n_keys", "n_glyphs", "n_clusters", "path_contours", "path_points",
"strings_len", "inline_len", "held_len", "zero")
assert HDR.size == 80
# Step 2's caps, in field order.
CAPS = (("n_sizes", 8), ("n_rates", 4), ("n_sets", 64), ("n_tables", 32), ("n_paints", 4096),
("n_paths", 4096), ("n_items", 65536), ("n_keys", 1 << 22), ("n_glyphs", 1 << 20),
("n_clusters", 1 << 20), ("path_contours", 1 << 20), ("path_points", 1 << 22),
("strings_len", 1 << 22), ("inline_len", 1 << 25), ("held_len", 1 << 26))
S_SIZE = struct.Struct("<HHI")
S_RATE = struct.Struct("<II")
S_SET = struct.Struct("<B3sI32s")
S_TABLE = struct.Struct("<17I")
S_PAINT = struct.Struct("<4B")
S_TRACK = struct.Struct("<BBHI")
S_KEY = struct.Struct("<IiB3s")
S_PATH = struct.Struct("<HHIhhhh")
S_PEND = struct.Struct("<H")
S_PPT = struct.Struct("<hhB")
S_TEXT = struct.Struct("<BBBBHHIII")
S_ITEM = struct.Struct("<BBHHHHHHHIII7H6s")
S_GLYPH = struct.Struct("<HHii")
S_CLUSTER = struct.Struct("<HHII")
for _s, _n in ((S_SIZE, 8), (S_RATE, 8), (S_SET, 40), (S_TABLE, 68), (S_PAINT, 4), (S_TRACK, 8),
(S_KEY, 12), (S_PATH, 16), (S_PEND, 2), (S_PPT, 5), (S_TEXT, 20), (S_ITEM, 48),
(S_GLYPH, 12), (S_CLUSTER, 12)):
assert _s.size == _n
Size = namedtuple("Size", "w h cost")
Rate = namedtuple("Rate", "num den")
SetRec = namedtuple("SetRec", "carriage zero length sha256")
Paint = namedtuple("Paint", "r g b a")
Key = namedtuple("Key", "t value interp zero")
Text = namedtuple("Text", "purpose dir lang_len mode group ruby ruby_start ruby_len str_len")
Item = namedtuple("Item", "kind rule zero0 ref paint text zero1 n_glyphs n_clusters t_on t_off "
"unit_scale slots zero2")
Glyph = namedtuple("Glyph", "gid zero x y")
Cluster = namedtuple("Cluster", "glyph_count zero byte_first byte_len")
SLOT_NAMES = ("m00", "m01", "m10", "m11", "tx", "ty", "op")
SLOT_DEFAULT = (65536, 0, 0, 65536, 0, 0, 255)
SLOT_KIND = (3, 3, 3, 3, 2, 2, 1)
ABSENT = 0xFFFF
T31 = 1 << 31
class TrackRec:
__slots__ = ("kind", "zero0", "zero1", "n_keys", "keys", "ts", "vs", "lo", "hi")
class PathRec:
__slots__ = ("n_contours", "n_points", "zero", "box", "ends", "points", "prims")
class Payload:
"""A loaded WVG1 and the sets it names."""
def exact_length(H):
return (80 + 8 * H["n_sizes"] + 8 * H["n_rates"] + 40 * H["n_sets"] + 68 * H["n_tables"]
+ 4 * H["n_paints"] + 8 * H["n_tracks"] + 12 * H["n_keys"] + 16 * H["n_paths"]
+ 2 * H["path_contours"] + 5 * H["path_points"] + 20 * H["n_texts"]
+ 48 * H["n_items"] + 12 * H["n_glyphs"] + 12 * H["n_clusters"]
+ H["strings_len"] + H["inline_len"])
def utf8_ok(b):
try:
b.decode("utf-8", errors="strict")
return True
except UnicodeDecodeError:
return False
def string_ok(b):
"""Section 2.5 rule 6: valid UTF-8, no C0 but U+000A, no U+007F, no C1."""
try:
s = b.decode("utf-8", errors="strict")
except UnicodeDecodeError:
return False
for ch in s:
o = ord(ch)
if (o < 0x20 and o != 0x0A) or o == 0x7F or 0x80 <= o <= 0x9F:
return False
return True
def tag_ok(b):
"""Section 2.5 rule 6: ASCII letters, digits and hyphens; no leading, trailing
or doubled hyphen."""
if not b:
return False
for c in b:
if not (0x30 <= c <= 0x39 or 0x41 <= c <= 0x5A or 0x61 <= c <= 0x7A or c == 0x2D):
return False
if b[0] == 0x2D or b[-1] == 0x2D or b"--" in b:
return False
return True
def cp_boundary(b, o):
"""An offset within b is a code-point boundary of the valid UTF-8 string b."""
if o == 0 or o == len(b):
return True
if o < 0 or o > len(b):
return False
return (b[o] & 0xC0) != 0x80
def gcd(a, b):
while b:
a, b = b, a % b
return a
def load(payload, store=None):
"""Section 2.7's load, steps 1-9. `store` maps a SHA-256 digest to the bytes of a
held set the sink holds (its resolution policy). Returns a Payload, or raises
Refusal with the first failure."""
store = store or {}
b = payload
# ---- step 1
if len(b) < 80:
raise Refusal("payload_malformed", "1", "shorter than the header")
hv = HDR.unpack_from(b, 0)
H = dict(zip(HDR_FIELDS, hv))
if H["magic"] != b"WVG1":
raise Refusal("payload_malformed", "1", "magic")
if H["format"] != 1:
raise Refusal("payload_malformed", "1", "format")
if H["zero"] != 0:
raise Refusal("payload_malformed", "1", "header zero")
# ---- step 2
for name, cap in CAPS:
if H[name] > cap:
raise Refusal("too_large", "2", name)
if H["inline_len"] + H["held_len"] > 1 << 26:
raise Refusal("too_large", "2", "inline_len + held_len")
exact = exact_length(H)
if exact > 1 << 26:
raise Refusal("too_large", "2", "exact length")
# ---- step 3
if len(b) != exact:
raise Refusal("payload_malformed", "3", f"length {len(b)} against {exact}")
# ---- step 4: the record pass
P = Payload()
P.bytes = bytes(b)
P.H = H
def fr(where):
return Refusal("field_range", "4", where)
def pm(where):
return Refusal("payload_malformed", "4", where)
tb, t0, dur, dw, dh = H["tb"], H["t0"], H["dur"], H["design_w"], H["design_h"]
if not 1 <= tb <= 1 << 20:
raise fr("tb")
if not 0 <= t0 <= T31 - 1:
raise fr("t0")
if not 1 <= dur <= T31 - t0:
raise fr("dur")
if not 16 <= dw <= 4096:
raise fr("design_w")
if not 16 <= dh <= 4096:
raise fr("design_h")
if H["n_sizes"] < 1:
raise fr("n_sizes")
if H["n_rates"] < 1:
raise fr("n_rates")
if H["n_items"] < 1:
raise fr("n_items")
off = 80
sizes = []
for k in range(H["n_sizes"]):
w, h, cost = S_SIZE.unpack_from(b, off)
off += 8
if not 16 <= w <= 4096 or not 8 * w >= dw or not w <= 4 * dw:
raise fr(f"SIZES[{k}].w")
if not 16 <= h <= 4096 or w * dh != h * dw:
raise fr(f"SIZES[{k}].h")
sizes.append(Size(w, h, cost))
rates = []
for k in range(H["n_rates"]):
num, den = S_RATE.unpack_from(b, off)
off += 8
if not 1 <= num <= 1 << 20:
raise fr(f"RATES[{k}].num")
if not 1 <= den <= 1 << 20:
raise fr(f"RATES[{k}].den")
if gcd(num, den) != 1 or num > 240 * den or (den * tb) % num != 0:
raise fr(f"RATES[{k}].den")
rates.append(Rate(num, den))
sets = []
for k in range(H["n_sets"]):
carriage, z, length, digest = S_SET.unpack_from(b, off)
off += 40
if carriage > 1:
raise fr(f"SETS[{k}].carriage")
if z != b"\0\0\0":
raise pm(f"SETS[{k}].zero")
if not 32 <= length <= 1 << 25:
raise fr(f"SETS[{k}].length")
sets.append(SetRec(carriage, z, length, digest))
tables = []
for k in range(H["n_tables"]):
E = S_TABLE.unpack_from(b, off)
off += 68
for i, e in enumerate(E):
if e > 65536 or (i == 0 and e != 0) or (i == 16 and e != 65536):
raise fr(f"TABLES[{k}].E[{i}]")
tables.append(tuple(E))
paints = []
for k in range(H["n_paints"]):
r, g, bb, a = S_PAINT.unpack_from(b, off)
off += 4
if r > a or g > a or bb > a:
raise fr(f"PAINTS[{k}].a")
paints.append(Paint(r, g, bb, a))
tracks = []
for k in range(H["n_tracks"]):
kind, z0, z1, nk = S_TRACK.unpack_from(b, off)
off += 8
if kind not in (1, 2, 3):
raise fr(f"TRACKS[{k}].kind")
if z0 != 0:
raise pm(f"TRACKS[{k}].zero(u8)")
if z1 != 0:
raise pm(f"TRACKS[{k}].zero(u16)")
if not 1 <= nk <= 4096:
raise fr(f"TRACKS[{k}].n_keys")
t = TrackRec()
t.kind, t.zero0, t.zero1, t.n_keys = kind, z0, z1, nk
tracks.append(t)
keys = []
for k in range(H["n_keys"]):
t, v, interp, z = S_KEY.unpack_from(b, off)
off += 12
if t >= T31:
raise fr(f"KEYS[{k}].t")
if interp > 1 + H["n_tables"]:
raise fr(f"KEYS[{k}].interp")
if z != b"\0\0\0":
raise pm(f"KEYS[{k}].zero")
keys.append(Key(t, v, interp, z))
paths = []
for k in range(H["n_paths"]):
nc, npt, z, x0, y0, x1, y1 = S_PATH.unpack_from(b, off)
off += 16
if nc < 1:
raise fr(f"PATHS[{k}].n_contours")
if npt > 16383:
raise Refusal("points_limit", "4", f"PATHS[{k}].n_points")
if npt < 1 or nc > npt:
raise fr(f"PATHS[{k}].n_points")
if z != 0:
raise pm(f"PATHS[{k}].zero")
p = PathRec()
p.n_contours, p.n_points, p.zero, p.box = nc, npt, z, (x0, y0, x1, y1)
paths.append(p)
pends = []
for k in range(H["path_contours"]):
pends.append(S_PEND.unpack_from(b, off)[0])
off += 2
ppts = []
for k in range(H["path_points"]):
x, y, tag = S_PPT.unpack_from(b, off)
off += 5
if tag > 2:
raise fr(f"PATH_POINTS[{k}].tag")
ppts.append((x, y, tag))
texts = []
for k in range(H["n_texts"]):
t = Text(*S_TEXT.unpack_from(b, off))
off += 20
if t.purpose == 0:
raise fr(f"TEXTS[{k}].purpose")
if t.dir > 1:
raise fr(f"TEXTS[{k}].dir")
if not 1 <= t.lang_len <= 63:
raise fr(f"TEXTS[{k}].lang_len")
if t.mode > 2:
raise fr(f"TEXTS[{k}].mode")
if t.ruby > H["n_texts"]:
raise fr(f"TEXTS[{k}].ruby")
if t.ruby == 0 and t.ruby_start != 0:
raise fr(f"TEXTS[{k}].ruby_start")
if (t.ruby == 0 and t.ruby_len != 0) or (t.ruby != 0 and t.ruby_len < 1):
raise fr(f"TEXTS[{k}].ruby_len")
if not 1 <= t.str_len <= 4096:
raise fr(f"TEXTS[{k}].str_len")
texts.append(t)
items = []
for k in range(H["n_items"]):
v = S_ITEM.unpack_from(b, off)
off += 48
it = Item(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10], v[11],
tuple(v[12:19]), v[19])
check_item_fields(it, k, H)
items.append(it)
glyphs = []
for k in range(H["n_glyphs"]):
gid, z, x, y = S_GLYPH.unpack_from(b, off)
off += 12
if z != 0:
raise pm(f"GLYPHS[{k}].zero")
if not -(1 << 23) <= x < 1 << 23:
raise fr(f"GLYPHS[{k}].x")
if not -(1 << 23) <= y < 1 << 23:
raise fr(f"GLYPHS[{k}].y")
glyphs.append(Glyph(gid, z, x, y))
clusters = []
for k in range(H["n_clusters"]):
gc, z, bf, bl = S_CLUSTER.unpack_from(b, off)
off += 12
if z != 0:
raise pm(f"CLUSTERS[{k}].zero")
clusters.append(Cluster(gc, z, bf, bl))
strings_off = off
strings = b[off:off + H["strings_len"]]
off += H["strings_len"]
inline = b[off:off + H["inline_len"]]
off += H["inline_len"]
assert off == len(b)
texts_off = strings_off - 12 * H["n_clusters"] - 12 * H["n_glyphs"] - 48 * H["n_items"] \
- 20 * H["n_texts"]
P.text_hash = sha256(b[texts_off:texts_off + 20 * H["n_texts"]] + strings)
# ---- step 5: cross-record structure
def pm5(sub, where):
return Refusal("payload_malformed", "5" + sub, where)
# (a) the sum rules
if sum(s.length for s in sets if s.carriage == 1) != H["inline_len"]:
raise pm5("a", "inline set lengths")
if sum(s.length for s in sets if s.carriage == 0) != H["held_len"]:
raise pm5("a", "held set lengths")
if sum(t.n_keys for t in tracks) != H["n_keys"]:
raise pm5("a", "track n_keys")
if sum(p.n_contours for p in paths) != H["path_contours"]:
raise pm5("a", "path n_contours")
if sum(p.n_points for p in paths) != H["path_points"]:
raise pm5("a", "path n_points")
if sum(t.lang_len + t.str_len for t in texts) != H["strings_len"]:
raise pm5("a", "text lengths")
runs = [it for it in items if it.kind == 3]
if sum(it.n_glyphs for it in runs) != H["n_glyphs"]:
raise pm5("a", "run n_glyphs")
if sum(it.n_clusters for it in runs) != H["n_clusters"]:
raise pm5("a", "run n_clusters")
# ownership, now that the sums hold
o = 0
for t in tracks:
t.keys = keys[o:o + t.n_keys]
o += t.n_keys
t.ts = [k.t for k in t.keys]
t.vs = [k.value for k in t.keys]
t.lo, t.hi = min(t.vs), max(t.vs)
oe = op_ = 0
for p in paths:
p.ends = pends[oe:oe + p.n_contours]
oe += p.n_contours
p.points = ppts[op_:op_ + p.n_points]
op_ += p.n_points
so = 0
text_lang, text_str = [], []
for t in texts:
text_lang.append(bytes(strings[so:so + t.lang_len]))
so += t.lang_len
text_str.append(bytes(strings[so:so + t.str_len]))
so += t.str_len
run_glyphs, run_clusters = {}, {}
og = oc = 0
for i, it in enumerate(items):
if it.kind == 3:
run_glyphs[i] = glyphs[og:og + it.n_glyphs]
og += it.n_glyphs
run_clusters[i] = clusters[oc:oc + it.n_clusters]
oc += it.n_clusters
# (b) ordering
for k in range(1, len(sizes)):
if sizes[k].w <= sizes[k - 1].w:
raise pm5("b", f"SIZES[{k}] order")
for k in range(1, len(rates)):
a, c = rates[k - 1], rates[k]
if not a.num * c.den < c.num * a.den:
raise pm5("b", f"RATES[{k}] order")
for ti, t in enumerate(tracks):
for j in range(1, len(t.keys)):
if t.keys[j].t <= t.keys[j - 1].t:
raise pm5("b", f"TRACKS[{ti}] key {j} order")
if t.keys[-1].interp != 0:
raise pm5("b", f"TRACKS[{ti}] last interp")
# (c) typing
ki = 0
for t in tracks:
for k in t.keys:
lo, hi = {1: (0, 255), 2: (-(1 << 22), 1 << 22), 3: (-(1 << 20), 1 << 20)}[t.kind]
if not lo <= k.value <= hi:
raise Refusal("field_range", "5c", f"KEYS[{ki}].value")
ki += 1
for i, it in enumerate(items):
if it.kind in (0, 2, 3):
for s, ti in enumerate(it.slots):
if ti != ABSENT and tracks[ti].kind != SLOT_KIND[s]:
raise Refusal("field_range", "5c", f"ITEMS[{i}].{SLOT_NAMES[s]}")
# (d) group balance and depth
depth = 0
open_ = []
match = {}
parents = [] # for each item, the group begins enclosing it, outermost first
for i, it in enumerate(items):
if it.kind == 0:
parents.append(tuple(open_))
depth += 1
if depth > 4:
raise pm5("d", f"ITEMS[{i}] depth 5")
open_.append(i)
elif it.kind == 1:
if depth == 0:
raise pm5("d", f"ITEMS[{i}] group end with no group open")
depth -= 1
match[open_.pop()] = i
parents.append(tuple(open_))
else:
parents.append(tuple(open_))
if depth != 0:
raise pm5("d", "a group is open after the last item")
# (e) paths: contour ends, grammar, box
for pi, p in enumerate(paths):
for j in range(1, len(p.ends)):
if p.ends[j] <= p.ends[j - 1]:
raise pm5("e", f"PATHS[{pi}] contour ends")
if p.ends[-1] != p.n_points - 1:
raise pm5("e", f"PATHS[{pi}] last end")
p.prims = contours_of(p.ends, p.points)
if p.prims is None:
raise pm5("e", f"PATHS[{pi}] contour grammar")
if exact_box(p.points) != p.box:
raise pm5("e", f"PATHS[{pi}] box")
# (f) STRINGS
for k in range(len(texts)):
if not tag_ok(text_lang[k]):
raise pm5("f", f"TEXTS[{k}] language tag")
if not string_ok(text_str[k]):
raise pm5("f", f"TEXTS[{k}] string")
# (g) the canonical rules of section 2.5
canonical_rules(items, sets, paints, tracks, paths, tables, texts, text_str, run_clusters)
# (h) a path's text naming a decorative block
for i, it in enumerate(items):
if it.kind == 2 and it.text != ABSENT and texts[it.text].purpose == 13:
raise pm5("h", f"ITEMS[{i}].text names a decorative block")
# ---- step 6: sets, one at a time in SET order
parsed_sets = []
io = 0
for k, s in enumerate(sets):
if s.carriage == 1:
data = bytes(inline[io:io + s.length])
io += s.length
else:
data = store.get(bytes(s.sha256))
if data is None:
raise Refusal("outline_not_held", "6", f"SETS[{k}]")
data = data[:s.length + 1]
if len(data) != s.length or sha256(data) != bytes(s.sha256):
raise Refusal("outline_mismatch", "6", f"SETS[{k}]")
try:
ws = parse_wos1(data)
except Refusal as r:
raise Refusal(r.code, "6", f"SETS[{k}] {r.where}")
ws.bytes = data
parsed_sets.append(ws)
# ---- step 7: glyph_missing
for i, it in enumerate(items):
if it.kind == 3:
ws = parsed_sets[it.ref]
for j, g in enumerate(run_glyphs[i]):
if g.gid not in ws.by_gid:
raise Refusal("glyph_missing", "7", f"ITEMS[{i}] glyph {j}")
P.sizes, P.rates, P.sets, P.tables, P.paints = sizes, rates, sets, tables, paints
P.tracks, P.keys, P.paths, P.texts, P.items = tracks, keys, paths, texts, items
P.glyphs, P.clusters, P.strings, P.inline = glyphs, clusters, bytes(strings), bytes(inline)
P.text_lang, P.text_str = text_lang, text_str
P.run_glyphs, P.run_clusters = run_glyphs, run_clusters
P.match, P.parents = match, parents
P.wos = parsed_sets
# ---- step 8: the interval check, at every listed size
P.device = {}
for z in sizes:
P.device[(z.w, z.h)] = interval_check(P, z.w, z.h)
# ---- step 9: the cost, in SIZE order
P.costs = []
for z in sizes:
c = size_cost(P, z.w, z.h)
P.costs.append(c)
if c > (1 << 32) - 1:
raise Refusal("too_large", "9", f"cost {c} at {z.w}x{z.h}")
if c != z.cost:
raise Refusal("payload_malformed", "9", f"cost {c} at {z.w}x{z.h}, stated {z.cost}")
return P
def check_item_fields(it, k, H):
"""Section 2.4's per-kind rules, in field offset order (section 2.7 step 4)."""
def fr(f):
return Refusal("field_range", "4", f"ITEMS[{k}].{f}")
def pm(f):
return Refusal("payload_malformed", "4", f"ITEMS[{k}].{f}")
kind = it.kind
if kind > 3:
raise fr("kind")
# rule (1)
if kind in (1, 3):
if it.rule != 0:
raise fr("rule")
elif it.rule > 1:
raise fr("rule")
# zero (2)
if it.zero0 != 0:
raise pm("zero@2")
# ref (4)
if kind == 0:
if it.ref != ABSENT and it.ref >= H["n_paths"]:
raise fr("ref")
if it.ref == ABSENT and it.rule != 0:
raise fr("ref (rule with no clip)")
elif kind == 1:
if it.ref != 0:
raise fr("ref")
elif kind == 2:
if it.ref >= H["n_paths"]:
raise fr("ref")
else:
if it.ref >= H["n_sets"]:
raise fr("ref")
# paint (6)
if kind == 0:
if it.paint != ABSENT:
raise fr("paint")
elif kind == 1:
if it.paint != 0:
raise fr("paint")
elif it.paint >= H["n_paints"]:
raise fr("paint")
# text (8)
if kind == 0:
if it.text != ABSENT:
raise fr("text")
elif kind == 1:
if it.text != 0:
raise fr("text")
elif kind == 2:
if it.text != ABSENT and it.text >= H["n_texts"]:
raise fr("text")
elif it.text >= H["n_texts"]:
raise fr("text")
# zero (10)
if it.zero1 != 0:
raise pm("zero@10")
# n_glyphs (12), n_clusters (14)
if kind == 3:
if not 1 <= it.n_glyphs <= 4096:
raise fr("n_glyphs")
if not 1 <= it.n_clusters <= 8192:
raise fr("n_clusters")
else:
if it.n_glyphs != 0:
raise fr("n_glyphs")
if it.n_clusters != 0:
raise fr("n_clusters")
# t_on (16), t_off (20)
if kind == 1:
if it.t_on != 0:
raise fr("t_on")
if it.t_off != 0:
raise fr("t_off")
else:
if it.t_on >= T31:
raise fr("t_on")
if it.t_off > T31 or not it.t_on < it.t_off:
raise fr("t_off")
# unit_scale (24)
us = it.unit_scale
if kind == 1:
if us != 0:
raise fr("unit_scale")
elif kind == 0:
if us > 1 << 24 or (it.ref == ABSENT and us != 0) or (it.ref != ABSENT and us < 1):
raise fr("unit_scale")
elif not 1 <= us <= 1 << 24:
raise fr("unit_scale")
# track slots (28..41)
for s, ti in enumerate(it.slots):
name = SLOT_NAMES[s]
if kind == 1:
if ti != 0:
raise fr(name)
elif kind == 0 and s < 4:
if ti != ABSENT:
raise fr(name)
elif ti != ABSENT and ti >= H["n_tracks"]:
raise fr(name)
# zero (42)
if it.zero2 != b"\0" * 6:
raise pm("zero@42")
def canonical_rules(items, sets, paints, tracks, paths, tables, texts, text_str, run_clusters):
"""Section 2.5 rules 2-5 (section 2.7 step 5(g)), in that order."""
def pm(where):
return Refusal("payload_malformed", "5g", where)
# rule 2: first-reference order
nxt = {"path": 0, "set": 0, "paint": 0, "track": 0}
size = {"path": len(paths), "set": len(sets), "paint": len(paints), "track": len(tracks)}
def ref(pool, x, where):
if x == nxt[pool]:
nxt[pool] += 1
elif x > nxt[pool]:
raise pm(f"rule 2: {pool} {x} first referenced before {pool} {nxt[pool]} ({where})")
for i, it in enumerate(items):
if it.kind == 1:
continue
if it.ref != ABSENT:
ref("set" if it.kind == 3 else "path", it.ref, f"ITEMS[{i}].ref")
if it.paint != ABSENT:
ref("paint", it.paint, f"ITEMS[{i}].paint")
for s, ti in enumerate(it.slots):
if ti != ABSENT:
ref("track", ti, f"ITEMS[{i}].{SLOT_NAMES[s]}")
for pool in ("path", "set", "paint", "track"):
if nxt[pool] != size[pool]:
raise pm(f"rule 2: {pool} {nxt[pool]} unreferenced")
tn = 0
for ti, t in enumerate(tracks):
for k in t.keys:
if k.interp >= 2:
j = k.interp - 2
if j == tn:
tn += 1
elif j > tn:
raise pm(f"rule 2: table {j} first referenced before table {tn}")
if tn != len(tables):
raise pm(f"rule 2: table {tn} unreferenced")
# rule 3: no duplicates, by hashing each pool
def nodup(pool, keys):
seen = set()
for k, v in enumerate(keys):
if v in seen:
raise pm(f"rule 3: duplicate {pool} {k}")
seen.add(v)
nodup("set", [bytes(s.sha256) for s in sets])
nodup("paint", [tuple(p) for p in paints])
nodup("track", [(t.kind, tuple((k.t, k.value, k.interp) for k in t.keys)) for t in tracks])
nodup("path", [(p.n_contours, p.n_points, p.box, tuple(p.ends), tuple(p.points))
for p in paths])
nodup("table", list(tables))
# rule 4: text blocks
named = set()
for it in items:
if it.kind == 3 or (it.kind == 2 and it.text != ABSENT):
named.add(it.text)
for k in range(len(texts)):
if k not in named:
raise pm(f"rule 4: block {k} is drawn by nothing")
mx = 0
for k, t in enumerate(texts):
if t.group != 0 and t.group > mx + 1:
raise pm(f"rule 4: block {k} group number skips")
mx = max(mx, t.group)
seen_groups = set()
for k, t in enumerate(texts):
if t.group != 0:
if t.group in seen_groups and texts[k - 1].group != t.group:
raise pm(f"rule 4: group {t.group} not contiguous at block {k}")
seen_groups.add(t.group)
for k, t in enumerate(texts):
if t.ruby != 0:
base = t.ruby - 1
if not base < k:
raise pm(f"rule 4: block {k} ruby names block {base}, not an earlier one")
s = text_str[base]
a, e = t.ruby_start, t.ruby_start + t.ruby_len
if e > len(s) or not cp_boundary(s, a) or not cp_boundary(s, e):
raise pm(f"rule 4: block {k} ruby range")
# rule 5: clusters of each run
for i, it in enumerate(items):
if it.kind != 3:
continue
cl = run_clusters[i]
if sum(c.glyph_count for c in cl) != it.n_glyphs:
raise pm(f"rule 5: ITEMS[{i}] cluster glyph counts")
s = text_str[it.text]
for j, c in enumerate(cl):
a, e = c.byte_first, c.byte_first + c.byte_len
if e > len(s) or not cp_boundary(s, a) or not cp_boundary(s, e):
raise pm(f"rule 5: ITEMS[{i}] cluster {j} range")
if c.glyph_count == 0 and c.byte_len < 1:
raise pm(f"rule 5: ITEMS[{i}] cluster {j} empty")
if c.byte_len == 0 and c.glyph_count < 1:
raise pm(f"rule 5: ITEMS[{i}] cluster {j} empty")
def write_wvg1(P):
"""Sections 2.2-2.4's layout from the parsed fields: every count derived from the
tables, every zero field written as 0."""
out = bytearray()
inline = b"".join(ws.bytes for s, ws in zip(P.sets, P.wos) if s.carriage == 1) \
if hasattr(P, "wos") else P.inline
held = sum(s.length for s in P.sets if s.carriage == 0)
strings = b"".join(l + s for l, s in zip(P.text_lang, P.text_str))
keys = [k for t in P.tracks for k in t.keys]
ends = [e for p in P.paths for e in p.ends]
pts = [q for p in P.paths for q in p.points]
runs = [i for i, it in enumerate(P.items) if it.kind == 3]
glyphs = [g for i in runs for g in P.run_glyphs[i]]
clusters = [c for i in runs for c in P.run_clusters[i]]
H = P.H
out += HDR.pack(b"WVG1", 1, H["tb"], H["t0"], H["dur"], H["design_w"], H["design_h"],
len(P.sizes), len(P.rates), len(P.sets), len(P.tables), len(P.paints),
len(P.texts), len(P.tracks), len(P.paths), len(P.items), len(keys),
len(glyphs), len(clusters), len(ends), len(pts), len(strings), len(inline),
held, 0)
for z in P.sizes:
out += S_SIZE.pack(z.w, z.h, z.cost)
for r in P.rates:
out += S_RATE.pack(r.num, r.den)
for s in P.sets:
out += S_SET.pack(s.carriage, b"\0\0\0", s.length, s.sha256)
for t in P.tables:
out += S_TABLE.pack(*t)
for p in P.paints:
out += S_PAINT.pack(*p)
for t in P.tracks:
out += S_TRACK.pack(t.kind, 0, 0, len(t.keys))
for k in keys:
out += S_KEY.pack(k.t, k.value, k.interp, b"\0\0\0")
for p in P.paths:
out += S_PATH.pack(len(p.ends), len(p.points), 0, *exact_box(p.points))
for e in ends:
out += S_PEND.pack(e)
for q in pts:
out += S_PPT.pack(*q)
for t, l, s in zip(P.texts, P.text_lang, P.text_str):
out += S_TEXT.pack(t.purpose, t.dir, len(l), t.mode, t.group, t.ruby, t.ruby_start,
t.ruby_len, len(s))
for i, it in enumerate(P.items):
ng = len(P.run_glyphs[i]) if it.kind == 3 else 0
nc = len(P.run_clusters[i]) if it.kind == 3 else 0
out += S_ITEM.pack(it.kind, it.rule, 0, it.ref, it.paint, it.text, 0, ng, nc, it.t_on,
it.t_off, it.unit_scale, *it.slots, b"\0" * 6)
for g in glyphs:
out += S_GLYPH.pack(g.gid, 0, g.x, g.y)
for c in clusters:
out += S_CLUSTER.pack(c.glyph_count, 0, c.byte_first, c.byte_len)
out += strings
out += inline
return bytes(out)
def block_identity(P, k):
"""Section 2.11: a block's identity."""
t = P.texts[k]
return sha256(b"wai:text-block" + b"\x01" + bytes([t.purpose, t.dir, t.mode, t.lang_len])
+ P.text_lang[k] + struct.pack("<I", t.str_len) + P.text_str[k])
# ------------------------------------------------------------------ section 3.4
def iv_scale(iv, k):
a, b = iv[0] * k, iv[1] * k
return (min(a, b), max(a, b))
def iv_add(a, b):
return (a[0] + b[0], a[1] + b[1])
def hull(I, J):
ps = (I[0] * J[0], I[0] * J[1], I[1] * J[0], I[1] * J[1])
return (min(ps), max(ps))
def slot_interval(P, it, s):
ti = it.slots[s]
if ti == ABSENT:
return (SLOT_DEFAULT[s], SLOT_DEFAULT[s])
t = P.tracks[ti]
return (t.lo, t.hi)
def instance_intervals(P, w, Mx, My, O, s, records):
"""Section 3.4(a) steps 3-4 for the records (g_x, g_y, box) of one item; returns
each record's device interval ((x_lo, x_hi), (y_lo, y_hi))."""
dw = P.H["design_w"]
den = dw << 16
def Liv(m):
lo_n, hi_n = m[0] * s * w, m[1] * s * w
bound("place.L_num", lo_n)
bound("place.L_num", hi_n)
L = (R_rat(lo_n, den), R_rat(hi_n, den))
bound("place.L", L[0])
bound("place.L", L[1])
return L
L00, L01 = Liv(Mx[0]), Liv(Mx[1])
L10, L11 = Liv(My[0]), Liv(My[1])
out = []
for gx, gy, box in records:
xmin, ymin, xmax, ymax = box
res = []
for Oa, m1, m2, L1, L2 in ((O[0], Mx[0], Mx[1], L00, L01), (O[1], My[0], My[1], L10, L11)):
numr = iv_add(iv_add(iv_scale(Oa, 1 << 16), iv_scale(m1, gx)), iv_scale(m2, gy))
for e in numr:
bound("place.origin_num", e * w)
org = (R_rat(numr[0] * w, den), R_rat(numr[1] * w, den))
pt = iv_add(hull(L1, (xmin, xmax)), hull(L2, (ymin, ymax)))
for e in pt:
bound("place.point_sum", e + (1 << 15))
pt = (R_half_up(pt[0], 16), R_half_up(pt[1], 16))
res.append(iv_add(org, pt))
out.append((res[0], res[1]))
return out
def interval_check(P, w, h):
"""Section 3.4(a) at one size. Returns {item index: [device intervals]}; raises
geometry_range."""
lim_lo, lim_hi = -(1 << 22), (1 << 22) - 1
dev = {}
ident = ((65536, 65536), (0, 0))
for i, it in enumerate(P.items):
if it.kind == 1 or (it.kind == 0 and it.ref == ABSENT):
continue
groups = P.parents[i]
if it.kind == 0:
chain = groups + (i,)
Mx, My = ident, ((0, 0), (65536, 65536))
else:
chain = groups
Mx = (slot_interval(P, it, 0), slot_interval(P, it, 1))
My = (slot_interval(P, it, 2), slot_interval(P, it, 3))
Ox, Oy = (0, 0), (0, 0)
if it.kind != 0:
Ox, Oy = slot_interval(P, it, 4), slot_interval(P, it, 5)
for gi in chain:
g = P.items[gi]
Ox = iv_add(Ox, slot_interval(P, g, 4))
Oy = iv_add(Oy, slot_interval(P, g, 5))
if it.kind == 3:
ws = P.wos[it.ref]
recs = [(g.x, g.y, ws.by_gid[g.gid].box) for g in P.run_glyphs[i]]
else:
recs = [(0, 0, P.paths[it.ref].box)]
ivs = instance_intervals(P, w, Mx, My, (Ox, Oy), it.unit_scale, recs)
for k, (xi, yi) in enumerate(ivs):
for e in xi + yi:
if not lim_lo <= e <= lim_hi:
raise Refusal("geometry_range", "8",
f"{w}x{h}: ITEMS[{i}] record {k} interval {xi}x{yi}")
dev[i] = ivs
return dev
def fill_box(xi, yi, w, h):
c_lo = max(0, xi[0] // 256)
c_hi = min(w - 1, xi[1] // 256)
r_lo = max(0, yi[0] // 256)
r_hi = min(h - 1, yi[1] // 256)
bw = max(0, c_hi - c_lo + 1)
bh = max(0, r_hi - r_lo + 1)
return c_lo, c_hi, r_lo, r_hi, bw, bh
def fill_cost(p, bw, bh):
return bound("cost.fill", p * (1024 + bw + bh) + bw * bh)
def item_costs(P, w, h):
dev = P.device[(w, h)]
costs = {}
for i, it in enumerate(P.items):
if it.kind == 1:
continue
if it.kind == 0:
c = w * h
if it.ref != ABSENT:
xi, yi = dev[i][0]
_, _, _, _, bw, bh = fill_box(xi, yi, w, h)
c += fill_cost(P.paths[it.ref].n_points, bw, bh)
elif it.kind == 2:
xi, yi = dev[i][0]
_, _, _, _, bw, bh = fill_box(xi, yi, w, h)
c = fill_cost(P.paths[it.ref].n_points, bw, bh)
else:
ws = P.wos[it.ref]
c = 0
cl = ch = rl = rh = None
for g, (xi, yi) in zip(P.run_glyphs[i], dev[i]):
c_lo, c_hi, r_lo, r_hi, bw, bh = fill_box(xi, yi, w, h)
c += fill_cost(ws.by_gid[g.gid].n_points, bw, bh)
if bw > 0 and bh > 0:
cl = c_lo if cl is None else min(cl, c_lo)
ch = c_hi if ch is None else max(ch, c_hi)
rl = r_lo if rl is None else min(rl, r_lo)
rh = r_hi if rh is None else max(rh, r_hi)
if cl is not None:
c += (ch - cl + 1) * (rh - rl + 1)
bound("cost.run", c)
costs[i] = c
return costs
def effective_window(P, i):
it = P.items[i]
a, b = it.t_on, it.t_off
for gi in P.parents[i]:
g = P.items[gi]
a, b = max(a, g.t_on), min(b, g.t_off)
t0, dur = P.H["t0"], P.H["dur"]
a, b = max(a, t0), min(b, t0 + dur)
return a, b
def size_cost(P, w, h):
"""Section 3.4(b) step 5: w*h + the largest sum over any tick."""
costs = item_costs(P, w, h)
ev = []
for i, c in costs.items():
a, b = effective_window(P, i)
if a < b:
ev.append((a, 1, c))
ev.append((b, 0, -c)) # ends (0) before starts (1) at equal ticks
ev.sort(key=lambda e: (e[0], e[1]))
cur = best = 0
for _, _, d in ev:
cur += d
bound("cost.sum", cur)
best = max(best, cur)
total = w * h + best
bound("cost.sum", total)
return total
# ------------------------------------------------------------------ section 3.1, 3.2
def ticks_per_frame(P, num, den):
return den * P.H["tb"] // num
def frame_range(P, num, den):
Pf = ticks_per_frame(P, num, den)
t0, dur = P.H["t0"], P.H["dur"]
return cdiv(t0, Pf), (t0 + dur - 1) // Pf
def eval_track(P, ti, tau):
t = P.tracks[ti]
ts, vs = t.ts, t.vs
n = len(ts)
if tau <= ts[0]:
return vs[0]
if tau >= ts[-1]:
return vs[-1]
i = bisect.bisect_right(ts, tau) - 1
D = ts[i + 1] - ts[i]
r = tau - ts[i]
d = vs[i + 1] - vs[i]
interp = t.keys[i].interp
if interp == 0:
return vs[i]
if interp == 1:
bound("track.linear", 2 * d * r + D)
return vs[i] + R_rat(d * r, D)
E = P.tables[interp - 2]
bound("track.table_r", r << 16)
u = (r << 16) // D
assert 0 <= u <= 65535
k = u // 4096
phi = u % 4096
step = (E[k + 1] - E[k]) * phi
bound("track.table_step", step)
e = E[k] + step // 4096
assert 0 <= e <= 65536
bound("track.value_step", d * e)
return vs[i] + R_half_up(d * e, 16)
def slot_value(P, it, s, tau):
ti = it.slots[s]
if ti == ABSENT:
return SLOT_DEFAULT[s]
return eval_track(P, ti, tau)
# ------------------------------------------------------------------ section 3.3, 3.5
def place(P, w, M, O, s, g, points):
"""Section 3.3: the device points of one glyph record (or path, or clip)."""
dw = P.H["design_w"]
den = dw << 16
L = []
for m in M:
n = m * s * w
bound("place.L_num", n)
L.append(bound("place.L", R_rat(n, den)))
L00, L01, L10, L11 = L
m00, m01, m10, m11 = M
nx = ((O[0] << 16) + m00 * g[0] + m01 * g[1]) * w
ny = ((O[1] << 16) + m10 * g[0] + m11 * g[1]) * w
bound("place.origin_num", nx)
bound("place.origin_num", ny)
tx, ty = R_rat(nx, den), R_rat(ny, den)
out = []
for px, py, _tag in points:
sx = L00 * px + L01 * py
sy = L10 * px + L11 * py
bound("place.point_sum", sx + (1 << 15))
bound("place.point_sum", sy + (1 << 15))
dx = tx + R_half_up(sx, 16)
dy = ty + R_half_up(sy, 16)
bound("device.lo", dx)
bound("device.hi", dx)
bound("device.lo", dy)
bound("device.hi", dy)
out.append((dx, dy))
return out
def flatten_quad(P0, P1, P2):
Ax, Ay = P0[0] - 2 * P1[0] + P2[0], P0[1] - 2 * P1[1] + P2[1]
M = max(abs(Ax), abs(Ay))
n = 0
while M > 64 * (4 ** n):
n += 1
bound("flatten.quad_n", n)
Bx, By = P1[0] - P0[0], P1[1] - P0[1]
N = 1 << n
sh = 2 * n
out = []
for k in range(1, N + 1):
nx = (1 << (n + 1)) * k * Bx + k * k * Ax
ny = (1 << (n + 1)) * k * By + k * k * Ay
bound("flatten.quad_num", nx)
bound("flatten.quad_num", ny)
out.append((P0[0] + (nx >> sh), P0[1] + (ny >> sh)))
assert out[-1] == tuple(P2)
return out
def flatten_cubic(P0, P1, P2, P3):
M = 0
for a, b_, c in ((P0, P1, P2), (P1, P2, P3)):
for ax in (0, 1):
M = max(M, abs(a[ax] - 2 * b_[ax] + c[ax]))
n = 0
while 3 * M > 64 * (4 ** n):
n += 1
bound("flatten.cubic_n", n)
B = [P1[a] - P0[a] for a in (0, 1)]
C = [P0[a] - 2 * P1[a] + P2[a] for a in (0, 1)]
D = [P3[a] - 3 * P2[a] + 3 * P1[a] - P0[a] for a in (0, 1)]
N = 1 << n
sh = 3 * n
out = []
for k in range(1, N + 1):
v = []
for a in (0, 1):
num = 3 * (4 ** n) * k * B[a] + 3 * (2 ** n) * k * k * C[a] + k * k * k * D[a]
bound("flatten.cubic_num", num)
v.append(P0[a] + (num >> sh))
out.append((v[0], v[1]))
assert out[-1] == tuple(P3)
return out
def polylines(prims, dev):
"""Section 3.5: each contour's polyline from its primitives and device points."""
out = []
for start, plist in prims:
pl = [dev[start]]
for p in plist:
if p[0] == "L":
pl.append(dev[p[2]])
elif p[0] == "Q":
pl.extend(flatten_quad(dev[p[1]], dev[p[2]], dev[p[3]]))
else:
pl.extend(flatten_cubic(dev[p[1]], dev[p[2]], dev[p[3]], dev[p[4]]))
out.append(pl)
return out
# ------------------------------------------------------------------ sections 3.6, 3.7
def fill_values(pls, w, h, p=None):
"""Sections 3.6-3.7 for one fill: {row: [(c_start, c_end, v)]}, the runs of
constant nonzero v on the canvas, with the left fold. With `p`, the stored point
count of the fill's outline, it also asserts section 3.7's D_r <= 256*p.
Crossings are the closed forms, floors of exact rationals in big integers. Column
boundaries left of x = 0 and right of x = 256*w are not split at: every sub-piece
left of the canvas folds 512*cover into column 0 and every sub-piece right of it
adds nothing, so their sum is the same (section 3.7; section 3.11's freedom)."""
d = {}
D = {}
for pl in pls:
for j in range(len(pl) - 1):
x0, y0 = pl[j]
x1, y1 = pl[j + 1]
bound("segment.d", x1 - x0)
bound("segment.d", y1 - y0)
if y0 == y1:
continue
if y0 < y1:
xt, yt, xb, yb = x0, y0, x1, y1
else:
xt, yt, xb, yb = x1, y1, x0, y0
dx, dy = xb - xt, yb - yt
first = 256 * (yt // 256 + 1)
bound("rows.cross32", (first - yt) * abs(dx))
r0 = max(yt // 256, 0)
r1 = min((yb - 1) // 256, h - 1)
for r in range(r0, r1 + 1):
ytop = max(yt, 256 * r)
ybot = min(yb, 256 * r + 256)
D[r] = D.get(r, 0) + (ybot - ytop)
bound("rows.cross", (ytop - yt) * dx)
bound("rows.cross", (ybot - yt) * dx)
Xt = xt + ((ytop - yt) * dx) // dy
Xb = xt + ((ybot - yt) * dx) // dy
if y0 < y1:
cell_split(Xt, ytop - 256 * r, Xb, ybot - 256 * r, d.setdefault(r, {}), w)
else:
cell_split(Xb, ybot - 256 * r, Xt, ytop - 256 * r, d.setdefault(r, {}), w)
out = {}
for r, drow in d.items():
bound("density", D[r])
if p is not None:
# section 3.7, "the point cap makes the condition hold": D_r <= 256*p.
# DENSITY_SLACK is 0; the falsification raises it to tighten the bound.
if D[r] > 256 * p - DENSITY_SLACK:
raise BoundError(f"D_{r} = {D[r]} past 256*p = {256 * p} - {DENSITY_SLACK}")
gap = 256 * p - D[r]
if gap < BOUND_PEAK.get("density.per_point.gap", 1 << 62):
BOUND_PEAK["density.per_point.gap"] = gap
lim = 512 * D[r]
cols = sorted(drow)
v = 0
spans = []
for k, c in enumerate(cols):
if abs(drow[c]) > lim:
raise BoundError(f"d[{r}][{c}] = {drow[c]} past 512*D_r = {lim}")
v += drow[c]
if abs(v) > lim:
raise BoundError(f"v[{r}][{c}] = {v} past 512*D_r = {lim}")
bound("accum", v)
nxt = cols[k + 1] if k + 1 < len(cols) else w
if v != 0:
spans.append((c, nxt, v))
if spans:
out[r] = spans
return out
def cell_split(xa, ya, xb, yb, drow, w):
"""Section 3.6's cell split of one row piece and section 3.7's accumulation."""
xs, ys = [xa], [ya]
if xa != xb:
if xa < xb:
cs = range(max(xa // 256 + 1, 0), min((xb - 1) // 256, w) + 1)
else:
cs = range(min((xa - 1) // 256, w), max(xb // 256 + 1, 0) - 1, -1)
num_y = yb - ya
den = xb - xa
for c in cs:
xc = 256 * c
bound("cells.product", abs(xc - xa) * abs(num_y))
n = (xc - xa) * num_y
if den > 0:
yc = ya + n // den
else:
yc = ya + (-n) // (-den)
xs.append(xc)
ys.append(yc)
xs.append(xb)
ys.append(yb)
for j in range(len(xs) - 1):
cover = ys[j + 1] - ys[j]
bound("cells.cover", cover)
if cover == 0:
continue
c = min(xs[j], xs[j + 1]) // 256
if c < 0:
drow[0] = drow.get(0, 0) + 512 * cover
bound("accum", drow[0])
elif c < w:
fa = xs[j] - 256 * c
fb = xs[j + 1] - 256 * c
assert 0 <= fa <= 256 and 0 <= fb <= 256
area = cover * (fa + fb)
bound("cells.area", area)
drow[c] = drow.get(c, 0) + 512 * cover - area
bound("accum", drow[c])
if c + 1 < w:
drow[c + 1] = drow.get(c + 1, 0) + area
bound("accum", drow[c + 1])
# ------------------------------------------------------------------ section 3.8
def alpha_nonzero(v):
m = min(abs(v), 1 << 17)
bound("alpha.product", 255 * m)
return R_half_up(255 * m, 17)
def alpha_evenodd(v):
m = v % (1 << 18)
m2 = min(m, (1 << 18) - m)
bound("alpha.product", 255 * m2)
return R_half_up(255 * m2, 17)
def path_alpha(vals, rule, w):
"""{pixel index: alpha} for a path or clip fill."""
q = alpha_evenodd if rule == 1 else alpha_nonzero
out = {}
for r, spans in vals.items():
base = r * w
for c0, c1, v in spans:
a = q(v)
if a:
for c in range(c0, c1):
out[base + c] = a
return out
def run_alpha(S):
out = {}
for px, s in S.items():
bound("union.S", s)
m = min(s, 1 << 17)
bound("alpha.product", 255 * m)
a = R_half_up(255 * m, 17)
if a:
out[px] = a
return out
# ------------------------------------------------------------------ section 3.9, 3.10
def composite_fill(T, alpha, paint, o):
pp = []
for ch in paint:
bound("composite.product", ch * o)
pp.append(div255(ch * o))
for px, a in alpha.items():
s = []
for ch in pp:
bound("composite.product", ch * a)
s.append(div255(ch * a))
sA = s[3]
b = 4 * px
for ch in range(4):
bound("composite.product", T[b + ch] * (255 - sA))
T[b + ch] = s[ch] + div255(T[b + ch] * (255 - sA))
assert T[b + ch] <= 255
def composite_group(parent, G, mask, og):
n = len(G) // 4
for px in range(n):
b = 4 * px
if G[b + 3] == 0:
continue # R, G, B <= A = 0, so s = 0 and the parent is unchanged
m = 255 if mask is None else mask.get(px, 0)
bound("composite.product", m * og)
k = div255(m * og)
s = []
for ch in range(4):
bound("composite.product", G[b + ch] * k)
s.append(div255(G[b + ch] * k))
sA = s[3]
for ch in range(4):
parent[b + ch] = s[ch] + div255(parent[b + ch] * (255 - sA))
def check_request(P, w, h, num, den, g):
if not any(z.w == w and z.h == h for z in P.sizes):
raise RequestError("size_unlisted")
if not any(r.num == num and r.den == den for r in P.rates):
raise RequestError("rate_unlisted")
lo, hi = frame_range(P, num, den)
if not (isinstance(g, int) and g >= 0 and lo <= g <= hi):
raise RequestError("frame_range")
def render(P, w, h, num, den, g):
"""R(P, S, z, r, g), section 3.10: the frame's RGBA8P."""
check_request(P, w, h, num, den, g)
tau = g * ticks_per_frame(P, num, den)
stack = [bytearray(4 * w * h)]
gstack = [] # (item index, tx, ty, op) of each open, active group
items = P.items
i = 0
while i < len(items):
it = items[i]
if it.kind == 0:
if it.t_on <= tau < it.t_off:
gstack.append((i, slot_value(P, it, 4, tau), slot_value(P, it, 5, tau),
slot_value(P, it, 6, tau)))
stack.append(bytearray(4 * w * h))
else:
i = P.match[i] + 1
continue
elif it.kind == 1:
gi, _, _, og = gstack[-1]
grp = items[gi]
mask = None
if grp.ref != ABSENT:
O = (sum(e[1] for e in gstack), sum(e[2] for e in gstack))
path = P.paths[grp.ref]
dev = place(P, w, (65536, 0, 0, 65536), O, grp.unit_scale, (0, 0), path.points)
mask = path_alpha(fill_values(polylines(path.prims, dev), w, h, path.n_points),
grp.rule, w)
gstack.pop()
G = stack.pop()
composite_group(stack[-1], G, mask, og)
elif it.t_on <= tau < it.t_off:
M = tuple(slot_value(P, it, s, tau) for s in range(4))
O = (slot_value(P, it, 4, tau) + sum(e[1] for e in gstack),
slot_value(P, it, 5, tau) + sum(e[2] for e in gstack))
o = slot_value(P, it, 6, tau)
if it.kind == 2:
path = P.paths[it.ref]
dev = place(P, w, M, O, it.unit_scale, (0, 0), path.points)
alpha = path_alpha(fill_values(polylines(path.prims, dev), w, h, path.n_points),
it.rule, w)
else:
ws = P.wos[it.ref]
S = {}
for gr in P.run_glyphs[i]:
gl = ws.by_gid[gr.gid]
if not gl.points:
continue
dev = place(P, w, M, O, it.unit_scale, (gr.x, gr.y), gl.points)
vals = fill_values(polylines(gl.prims, dev), w, h, gl.n_points)
for r, spans in vals.items():
base = r * w
for c0, c1, v in spans:
ck = min(abs(v), 1 << 17)
for c in range(c0, c1):
S[base + c] = S.get(base + c, 0) + ck
alpha = run_alpha(S)
composite_fill(stack[-1], alpha, P.paints[it.paint], o)
i += 1
assert len(stack) == 1
root = stack[0]
for j in range(0, len(root), 4):
assert root[j] <= root[j + 3] and root[j + 1] <= root[j + 3] and root[j + 2] <= root[j + 3]
return bytes(root)
# ------------------------------------------------------------------ section 2.10
SIDECAR_SCHEMA = "wai.graphics.digests/1"
I31 = (1 << 31) - 1
def write_sidecar(sc):
def ints(xs):
return ",".join(str(x) for x in xs)
clips = ",".join(f'[{ints(c[:4])},"{c[4]}"]' for c in sc["clips"])
probes = ",".join(f'[{ints(p[:5])},"{p[5]}"]' for p in sc["probes"])
return (f'{{"clips":[{clips}],"instr":"{sc["instr"]}","probes":[{probes}],'
f'"schema":"{SIDECAR_SCHEMA}"}}').encode("ascii")
def _hex64(s):
return isinstance(s, str) and len(s) == 64 and all(c in "0123456789abcdef" for c in s)
def _int31(v):
return type(v) is int and 0 <= v <= I31
def parse_sidecar(b, P):
"""Section 2.10's sidecar checks 1-2 against a loaded payload; returns the parsed
sidecar, or raises SidecarCode."""
def bad(why):
return SidecarCode("sidecar_malformed", why)
if len(b) > 1 << 17:
raise bad("over 2^17 bytes")
def no_const(x):
raise bad(f"constant {x}")
def no_float(x):
raise bad(f"fraction or exponent {x}")
def pairs(kv):
d = {}
for k, v in kv:
if k in d:
raise bad(f"repeated member {k}")
d[k] = v
return d
try:
text = b.decode("utf-8", errors="strict")
sc = json.loads(text, parse_float=no_float, parse_constant=no_const,
object_pairs_hook=pairs)
except SidecarCode:
raise
except (UnicodeDecodeError, ValueError) as e:
raise bad(f"not JSON: {e}")
if not isinstance(sc, dict) or set(sc) != {"clips", "instr", "probes", "schema"}:
raise bad("members")
if sc["schema"] != SIDECAR_SCHEMA:
raise bad("schema")
if not _hex64(sc["instr"]):
raise bad("instr")
clips, probes = sc["clips"], sc["probes"]
if not isinstance(clips, list) or not 0 <= len(clips) <= 32:
raise bad("clip count")
if not isinstance(probes, list) or not 1 <= len(probes) <= 1024:
raise bad("probe count")
sizes = {(z.w, z.h) for z in P.sizes}
rates = {(r.num, r.den) for r in P.rates}
prev = None
for c in clips:
if not (isinstance(c, list) and len(c) == 5 and all(_int31(x) for x in c[:4])
and _hex64(c[4])):
raise bad(f"clip entry {c}")
if (c[0], c[1]) not in sizes or (c[2], c[3]) not in rates:
raise bad(f"clip size or rate {c[:4]}")
k = tuple(c[:4])
if prev is not None and not prev < k:
raise bad("clips out of order or repeated")
prev = k
prev = None
for p in probes:
if not (isinstance(p, list) and len(p) == 6 and all(_int31(x) for x in p[:5])
and _hex64(p[5])):
raise bad(f"probe entry {p}")
if (p[0], p[1]) not in sizes or (p[2], p[3]) not in rates:
raise bad(f"probe size or rate {p[:4]}")
lo, hi = frame_range(P, p[2], p[3])
if not lo <= p[4] <= hi:
raise bad(f"probe g {p[4]} out of range")
k = tuple(p[:5])
if prev is not None and not prev < k:
raise bad("probes out of order or repeated")
prev = k
if write_sidecar(sc) != bytes(b):
raise bad("not the canonical form")
if sc["instr"] != hashlib.sha256(P.bytes).hexdigest():
raise SidecarCode("sidecar_mismatch", "instr")
return sc
def frame_digest(P, w, h, num, den, g, cache=None):
key = (w, h, num, den, g)
if cache is not None and key in cache:
return cache[key]
d = sha256(render(P, w, h, num, den, g))
if cache is not None:
cache[key] = d
return d
def clip_digest(P, w, h, num, den, cache=None):
lo, hi = frame_range(P, num, den)
return sha256(b"".join(frame_digest(P, w, h, num, den, g, cache) for g in range(lo, hi + 1)))
def check_probes(P, sc, cache=None):
"""The sink rule: every probe frame rendered as published, compared by SHA-256.
Returns None, or the first mismatching probe (layer_mismatch)."""
for p in sc["probes"]:
if frame_digest(P, *p[:5], cache).hex() != p[5]:
return p
return None
def probe_set(P, pairs):
"""Section 2.10's authoring-tool probe set over the given (w, h, num, den) pairs."""
levels = [[], [], []]
t0, dur = P.H["t0"], P.H["dur"]
for (w, h, num, den) in pairs:
Pf = ticks_per_frame(P, num, den)
lo, hi = frame_range(P, num, den)
if lo <= hi:
# Only when the document has a frame at this rate: with none, the first
# exceeds the last and neither is in range (the text fix of this PR).
levels[0] += [(w, h, num, den, lo), (w, h, num, den, hi)]
for i, it in enumerate(P.items):
a, b = effective_window(P, i)
if a >= b:
continue
if cdiv(a, Pf) <= cdiv(b, Pf) - 1:
levels[1] += [(w, h, num, den, cdiv(a, Pf)), (w, h, num, den, cdiv(b, Pf) - 1)]
for t in P.tracks:
for k in t.keys:
g = cdiv(k.t, Pf)
if lo <= g <= hi:
levels[2].append((w, h, num, den, g))
held = set()
for lv in levels:
for c in sorted(lv):
if len(held) >= 1024:
break
held.add(c)
return sorted(held)
# ------------------------------------------------------------------ section 5.4
#
# The text layer: each block's active set, the projection at a frame, the transcript,
# their JSON, and the text record. Section 5.4 defines all of it over the loaded
# payload; nothing here reads a set, a glyph or a pixel.
DECORATIVE = 13 # section 2.3: text used as ornament, never exposed
def text_fault(P, what):
"""A statement of section 5.4 that a computed value breaks. It is recorded, not
raised, so the record is still written and compared with the corpus."""
if getattr(P, "text_faults", None) is None:
P.text_faults = []
P.text_faults.append(what)
def drawing_items(P, k):
"""Section 5.4, "the items that draw a block": the run items whose `text` is the
block's index, and the path items whose `text` is the block's index (section 2.4).
A group begin carries no text, and its clip draws no block."""
return [i for i, it in enumerate(P.items) if it.kind in (2, 3) and it.text == k]
def active_set(P, k):
"""Section 5.4, "a block's active set": the union of the effective windows
(section 3.4(b) step 4) of the items that draw block k, as half-open tick
intervals [on, off): ascending by `on`, with intervals that overlap or touch
merged; empty when the block is never active inside the document's window."""
windows = []
for i in drawing_items(P, k):
a, b = effective_window(P, i)
if a < b: # "It may be empty": an empty window adds nothing to the union
windows.append((a, b))
windows.sort() # ascending order of `on`
out = []
for a, b in windows:
if out and a <= out[-1][1]: # overlaps the last interval, or touches it: merge
if b > out[-1][1]:
out[-1][1] = b
else:
out.append([a, b])
t0, end = P.H["t0"], P.H["t0"] + P.H["dur"]
for j, (a, b) in enumerate(out):
# "Every interval has t0 <= on < off <= t0 + dur", and "no two intervals of
# the list share a tick or an end".
if not t0 <= a < b <= end:
text_fault(P, f"block {k}: interval [{a}, {b}) outside [t0, t0 + dur) or empty")
if j > 0 and not out[j - 1][1] < a:
text_fault(P, f"block {k}: intervals {out[j - 1]} and {[a, b]} share a tick or an "
"end, or are out of order")
return out
def active_sets(P):
"""Every block's active set, computed once for a payload."""
if getattr(P, "active", None) is None:
P.active = [active_set(P, k) for k in range(len(P.texts))]
return P.active
def holds(active, tau):
"""Section 5.4: an active set holds a tick when some interval has on <= tau < off."""
return any(a <= tau < b for a, b in active)
def projection_blocks(P, num, den, g):
"""Section 5.4, "the projection at frame g of listed rate r": the indices, in block
order, of every block whose purpose is not 13 and whose active set holds the
frame's tick tau = g*P_r (section 3.1). A request at an unlisted rate is
rate_unlisted and one at a frame outside the document at that rate frame_range,
the request errors of section 2.7, decided by its divisions."""
if not any(r.num == num and r.den == den for r in P.rates):
raise RequestError("rate_unlisted")
lo, hi = frame_range(P, num, den)
if not (isinstance(g, int) and g >= 0 and lo <= g <= hi):
raise RequestError("frame_range")
tau = g * ticks_per_frame(P, num, den)
act = active_sets(P)
return [k for k, t in enumerate(P.texts) if t.purpose != DECORATIVE and holds(act[k], tau)]
def transcript_blocks(P):
"""Section 5.4, "the transcript": the indices, in block order, of every block whose
purpose is not 13. A block that is never active is in it."""
return [k for k, t in enumerate(P.texts) if t.purpose != DECORATIVE]
def json_string(b):
"""Section 5.4, "the encoding": a quotation mark, the bytes carried, a quotation
mark, with exactly three replacements among those bytes: 0x22 as \\", 0x5C as \\\\
and 0x0A as \\n. Nothing else is escaped."""
out = bytearray(b'"')
for c in b:
if c == 0x22:
out += b'\\"'
elif c == 0x5C:
out += b"\\\\"
elif c == 0x0A:
out += b"\\n"
else:
out.append(c)
out += b'"'
return bytes(out)
def json_uint(v):
"""Section 5.4, "the encoding": decimal, no sign, no leading zero; zero is 0."""
assert type(v) is int and v >= 0
return b"%d" % v
def node_members(P, k):
"""Section 5.4's table: a node object's members for block k, in the stated order
(the ascending byte order of their keys): dir, group, index, lang, mode, purpose,
ruby (only when the block's `ruby` is not 0) and text."""
t = P.texts[k]
if t.purpose == 13: # "1 ..= 255 and never 13"
text_fault(P, f"block {k}: a node of purpose 13")
m = [b'"dir":' + json_uint(t.dir),
b'"group":' + json_uint(t.group),
b'"index":' + json_uint(k),
b'"lang":' + json_string(P.text_lang[k]),
b'"mode":' + json_uint(t.mode),
b'"purpose":' + json_uint(t.purpose)]
if t.ruby != 0:
# {"base":B,"len":L,"start":S}: B = ruby - 1, S = ruby_start, L = ruby_len;
# "written whether or not block B is itself in the list".
m.append(b'"ruby":{"base":' + json_uint(t.ruby - 1) + b',"len":' + json_uint(t.ruby_len)
+ b',"start":' + json_uint(t.ruby_start) + b"}")
m.append(b'"text":' + json_string(P.text_str[k]))
return m
def node_json(P, k):
return b"{" + b",".join(node_members(P, k)) + b"}"
def block_json(P, k):
"""Section 5.4: a block object has the member `active` first, then the members of a
node object. `active` is an array of two-element arrays [on,off], in ticks."""
act = b"[" + b",".join(b"[" + json_uint(a) + b"," + json_uint(b) + b"]"
for a, b in active_sets(P)[k]) + b"]"
return b"{" + b",".join([b'"active":' + act] + node_members(P, k)) + b"}"
def projection_json(P, num, den, g):
"""Section 5.4: a projection is one JSON array of node objects, in block order."""
return b"[" + b",".join(node_json(P, k) for k in projection_blocks(P, num, den, g)) + b"]"
def transcript_json(P):
"""Section 5.4: a transcript is one JSON array of block objects, in block order."""
return b"[" + b",".join(block_json(P, k) for k in transcript_blocks(P)) + b"]"
def text_record_lines(P):
"""Section 5.4, "the text record", as its lines without their line ends: the
transcript; then, for each listed rate in RATE order and each frame g of the
document at that rate in ascending order, {"den":D,"g":G,"num":N,"projection":J}.
A rate at which the document has no frame adds no line."""
yield transcript_json(P)
for r in P.rates:
lo, hi = frame_range(P, r.num, r.den)
for g in range(lo, hi + 1):
yield (b'{"den":' + json_uint(r.den) + b',"g":' + json_uint(g) + b',"num":'
+ json_uint(r.num) + b',"projection":' + projection_json(P, r.num, r.den, g)
+ b"}")
def text_record(P):
"""Section 5.4: a payload's text record, each line ended by one U+000A."""
return b"".join(line + b"\n" for line in text_record_lines(P))
def blocks_drawn_at(P, tau):
"""The blocks an active item draws at tick tau, by the walk of section 3.10 over
section 2.4's activity rule: an item is active when t_on <= tau < t_off, and an
inactive group begin skips everything up to its matching end. No tick outside the
document's window [t0, t0 + dur) draws anything (section 5.4)."""
out = set()
if not P.H["t0"] <= tau < P.H["t0"] + P.H["dur"]:
return out
i = 0
while i < len(P.items):
it = P.items[i]
if it.kind == 0 and not it.t_on <= tau < it.t_off:
i = P.match[i] + 1
continue
if it.kind in (2, 3) and it.t_on <= tau < it.t_off and it.text != ABSENT:
out.add(it.text)
i += 1
return out
def text_record_recheck(P, record):
"""A second derivation of the text record, which shares neither the interval
arithmetic nor the writer of the first. Returns a list of failures.
- Active sets, by ticks: between two consecutive ticks at which some window opens
or closes nothing changes, so a block's active set is the maximal stretches of
such ticks at which section 3.10's walk finds an active item that draws it.
- Frames, by section 3.1's definition: the g with t0 <= g*P < t0 + dur.
- The JSON: section 5.4 states that each object's member order is the ascending
byte order of its keys, and that after its three replacements no other control
character occurs (section 2.5 rule 6). So each line is the value written with
sorted keys, no whitespace, and characters outside ASCII as their UTF-8 bytes."""
t0, end = P.H["t0"], P.H["t0"] + P.H["dur"]
edges = {t0, end}
for it in P.items:
if it.kind != 1:
edges.update(t for t in (it.t_on, it.t_off) if t0 < t < end)
edges = sorted(edges)
act = [[] for _ in P.texts]
for p, q in zip(edges, edges[1:]):
for k in blocks_drawn_at(P, p):
if act[k] and act[k][-1][1] == p:
act[k][-1][1] = q
else:
act[k].append([p, q])
def dump(v):
return json.dumps(v, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def node(k):
t = P.texts[k]
d = {"dir": t.dir, "group": t.group, "index": k, "lang": P.text_lang[k].decode("utf-8"),
"mode": t.mode, "purpose": t.purpose, "text": P.text_str[k].decode("utf-8")}
if t.ruby:
d["ruby"] = {"base": t.ruby - 1, "len": t.ruby_len, "start": t.ruby_start}
return d
shown = [k for k, t in enumerate(P.texts) if t.purpose != DECORATIVE]
lines = [dump([dict(node(k), active=act[k]) for k in shown])]
for r in P.rates:
Pf = ticks_per_frame(P, r.num, r.den)
g = cdiv(t0, Pf)
while g * Pf < end:
here = blocks_drawn_at(P, g * Pf)
lines.append(dump({"den": r.den, "g": g, "num": r.num,
"projection": [node(k) for k in shown if k in here]}))
g += 1
if "".join(line + "\n" for line in lines).encode("utf-8") != record:
return ["the text record differs from its second derivation (activity by ticks, "
"frames by section 3.1, JSON by sorted keys)"]
return []
# ------------------------------------------------------------------ set store
def load_store(dirpath):
store = {}
if dirpath and os.path.isdir(dirpath):
for name in sorted(os.listdir(dirpath)):
if name.endswith(".wos1"):
with open(os.path.join(dirpath, name), "rb") as f:
data = f.read()
store[sha256(data)] = data
return store
# ------------------------------------------------------------------ self-test from the text
def spec_worked_example():
"""Section 5.4's worked example as the extension text prints it: the two lines of
its fenced block (the transcript, and the text record's line for frame 30), read
from the text beside this tool. None when the text is not there."""
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, "extensions",
"graphics-vector.md")
try:
with open(path, "rb") as f:
text = f.read()
except OSError:
return None
at = text.find(b"**A worked example.**")
start = text.find(b"```text\n", at)
stop = text.find(b"\n```", start)
if at < 0 or start < 0 or stop < 0:
return None
lines = text[start + len(b"```text\n"):stop].split(b"\n")
return lines if len(lines) == 2 else None
def text_only_payload(tb, t0, dur, rates, blocks, runs):
"""A payload's text layer alone, as section 5.4 reads it. `blocks` are
(purpose, dir, mode, group, ruby, ruby_start, ruby_len, tag, string), and `runs`
are (block index, t_on, t_off) with no enclosing group."""
P = Payload()
P.H = {"tb": tb, "t0": t0, "dur": dur}
P.rates = [Rate(*r) for r in rates]
P.texts = [Text(b[0], b[1], len(b[7]), b[2], b[3], b[4], b[5], b[6], len(b[8])) for b in blocks]
P.text_lang = [b[7] for b in blocks]
P.text_str = [b[8] for b in blocks]
P.items = [Item(3, 0, 0, 0, 0, k, 0, 1, 1, on, off, 65536, (ABSENT,) * 7, b"\0" * 6)
for k, on, off in runs]
P.parents = [() for _ in runs]
P.match = {}
return P
def selftest_text(eq, notes):
"""Section 5.4's statements, recomputed."""
# "The encoding": exactly three replacements, and a solidus takes none.
eq("5.4 string escapes", json_string(b'a"b\\c\nd/<&'), b'"a\\"b\\\\c\\nd/<&"')
eq("5.4 zero", json_uint(0), b"0")
# "A block's active set": [a, b) and [b, c) become [a, c); windows one tick apart
# stay two; the list ascends whatever the items' order; a window is cut to the
# document's, and a block never active inside it has an empty active set.
lab = (7, 0, 0, 0, 0, 0, 0, b"en", b"x")
P = text_only_payload(1000, 100, 900, [(10, 1)], [lab, lab, lab, lab],
[(0, 300, 400), (0, 200, 300), (1, 520, 600), (1, 500, 519),
(2, 0, 150), (2, 950, 5000), (2, 140, 160), (3, 1000, 2000)])
eq("5.4 active set, touching", active_set(P, 0), [[200, 400]])
eq("5.4 active set, one tick apart", active_set(P, 1), [[500, 519], [520, 600]])
eq("5.4 active set, cut and merged", active_set(P, 2), [[100, 160], [950, 1000]])
eq("5.4 active set, never active", active_set(P, 3), [])
eq("5.4 never active, in the transcript", transcript_blocks(P), [0, 1, 2, 3])
# "on <= tau < off": frame 2 sits at tick 200, block 0's first; frame 4 at its end.
eq("5.4 projection at a first tick", projection_blocks(P, 10, 1, 2), [0])
eq("5.4 projection at an end", projection_blocks(P, 10, 1, 4), [])
for bad, code in (((25, 1, 2), "rate_unlisted"), ((10, 1, 0), "frame_range"),
((10, 1, 10), "frame_range")):
try:
projection_blocks(P, *bad)
eq(f"5.4 request {bad}", "answered", code)
except RequestError as e:
eq(f"5.4 request {bad}", e.code, code)
eq("5.4 second derivation", text_record_recheck(P, text_record(P)), [])
eq("5.4 stated invariants", getattr(P, "text_faults", None) or [], [])
# "A worked example": tb = 120000, t0 = 0, dur = 360000, 25 fps, so P = 4800; three
# blocks in group 1, the third a ruby annotation of the first 8 bytes of block 1.
# Its list of blocks states each block's string and writing mode (0). The strings
# are read from the two lines it prints, which hold the same ones; every other byte
# of those lines is recomputed from the stated fields.
ex = spec_worked_example()
if ex is None:
notes.append("section 5.4's worked example was not recomputed: the extension text "
"is not beside this tool")
return
s = [e["text"].encode("utf-8") for e in json.loads(ex[0].decode("utf-8"))]
eq("5.4 worked example, blocks", len(s), 3)
if len(s) != 3:
return
P = text_only_payload(120000, 0, 360000, [(25, 1)],
[(3, 1, 0, 1, 0, 0, 0, b"ar", s[0]), (3, 0, 0, 1, 0, 0, 0, b"en", s[1]),
(7, 0, 0, 1, 2, 0, 8, b"en", s[2])],
[(0, 24000, 264000), (1, 24000, 264000), (2, 24000, 120000)])
eq("5.4 worked example, P", ticks_per_frame(P, 25, 1), 4800)
eq("5.4 worked example, frames", frame_range(P, 25, 1), (0, 74))
lines = list(text_record_lines(P))
eq("5.4 worked example, transcript", lines[0], ex[0])
eq("5.4 worked example, frame 30", lines[1 + 30], ex[1])
eq("5.4 worked example, second derivation", text_record_recheck(P, text_record(P)), [])
eq("5.4 worked example, stated invariants", getattr(P, "text_faults", None) or [], [])
def selftest():
"""Worked examples and claims the text states, recomputed."""
fails = []
notes = []
def eq(what, got, want):
if got != want:
fails.append(f"{what}: {got} != {want}")
selftest_text(eq, notes)
# 3.8: the triangle (0,0)-(512,3)-(0,3) gives |v| = 1280 and 512 in its two pixels.
vals = fill_values([[(0, 0), (512, 3), (0, 3), (0, 0)]], 4, 4)
got = sorted(abs(v) for spans in vals.values() for c0, c1, v in spans for _ in range(c0, c1))
eq("3.8 triangle", got, [512, 1280])
# 3.8: a full pixel gives 255, half a pixel 128.
eq("3.8 full pixel", alpha_nonzero(1 << 17), 255)
eq("3.8 half pixel", alpha_nonzero(1 << 16), 128)
# 3.9: div255 is the half-up rounding of x/255 over [0, 65025].
for x in range(0, 65026):
if div255(x) != (2 * x + 255) // 510:
fails.append(f"3.9 div255({x})")
break
# 3.9: div255(255*x) = x.
for x in range(256):
eq(f"3.9 div255(255*{x})", div255(255 * x), x)
# 3.0: R_rat's quotient-remainder form.
for n in range(-600, 601, 7):
for d in (1, 2, 3, 255, 256):
q, r = n // d, n - (n // d) * d
eq(f"3.0 R_rat({n},{d})", R_rat(n, d), q + (1 if 2 * r >= d else 0))
# 2.1: the WOS1 length at the caps.
eq("2.1 WOS1 caps", 32 + 16 * 65536 + 2 * (1 << 22) + 5 * (1 << 22) + (1 << 20), 31457312)
# 2.9: a 1920x1080 frame.
eq("2.9 frame bytes", 4 * 1920 * 1080, 8294400)
# 3.5: V_(2^n) is the end point (asserted inside), and a straight quadratic is n = 0.
eq("3.5 straight quad", flatten_quad((0, 0), (256, 0), (512, 0)), [(512, 0)])
# 3.6: X(y) is the same from either end (the row split of a reversed segment).
a = fill_values([[(10, 7), (900, 700), (10, 700), (10, 7)]], 8, 4)
b = fill_values([[(10, 7), (10, 700), (900, 700), (10, 7)]], 8, 4)
eq("3.11 P1 reversal", {r: [(c0, c1, -v) for c0, c1, v in s] for r, s in a.items()}, b)
for n in notes:
print("NOTE", n)
if fails:
for f in fails:
print("FAIL", f)
return 1
print("selftest: every worked example and stated claim recomputes")
return 0
# ------------------------------------------------------------------ the corpus driver
#
# Written after the checker above was committed, from the corpus README's layout: it
# maps each vector file to the computations above and writes the expected file's JSON.
# It takes no expected value as an input, with one exception it names: the `v` values
# at which the `alpha` vector probes the quantisers are listed only in its expected
# file, so they are read from there and the values at them recomputed.
# The groups the corpus README names as rendering: their expected files list frames.
RENDER_GROUPS = ("clock", "composite", "fills", "motion", "runs", "slicing", "text")
RGBA8S_GROUPS = ("composite",)
# The group whose expected files state `text_record`, each with its text record beside
# it as <name>.a11y.jsonl. Its frames are not rendered.
TEXT_GROUPS = ("a11y",)
TEXT_RECORD_SUFFIX = ".a11y.jsonl"
def canon(obj):
return (json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
+ "\n").encode("utf-8")
def frames_of(P, group, only=None):
out = []
for z in P.sizes:
for r in P.rates:
if only is not None and (z.w, z.h, r.num, r.den) not in only:
continue
lo, hi = frame_range(P, r.num, r.den)
fr, s8 = [], []
digests = []
for g in range(lo, hi + 1):
buf = render(P, z.w, z.h, r.num, r.den, g)
d = sha256(buf)
digests.append(d)
fr.append([g, d.hex()])
if group in RGBA8S_GROUPS:
s8.append([g, sha256(rgba8s(buf)).hex()])
e = {"clip": sha256(b"".join(digests)).hex(), "den": r.den, "frames": fr, "h": z.h,
"num": r.num, "w": z.w}
if group in RGBA8S_GROUPS:
e["rgba8s"] = s8
out.append(e)
return out
def payload_result(data, store, group):
"""The expected-file object for one WVG1, and whether it rewrites to its own bytes
(None when refused), and the refusal (or None)."""
try:
P = load(data, store)
except Refusal as r:
return {"code": r.code}, None, r, None
d = {"blocks": [block_identity(P, k).hex() for k in range(len(P.texts))],
"code": "accepted", "instr": sha256(data).hex(),
"rates": [[r.num, r.den, ticks_per_frame(P, r.num, r.den), *frame_range(P, r.num, r.den)]
for r in P.rates],
"sizes": [[z.w, z.h, c] for z, c in zip(P.sizes, P.costs)],
"text_hash": P.text_hash.hex()}
if group in RENDER_GROUPS:
d["frames"] = frames_of(P, group)
if group in TEXT_GROUPS:
P.record = text_record(P) # section 5.4; the file beside the expected file
d["text_record"] = sha256(P.record).hex()
return d, write_wvg1(P) == data, None, P
def wos1_result(data):
try:
s = parse_wos1(data)
except Refusal as r:
return {"code": r.code}, None, r
return ({"code": "accepted", "glyphs": len(s.glyphs),
"points": sum(len(g.points) for g in s.glyphs), "sha256": sha256(data).hex()},
write_wos1(s) == data, None)
def arith_result(v, expected_path):
op = v["op"]
if op == "div255":
table = bytes(div255(x) for x in range(65026))
first = []
for x in range(65026):
if table[x] == len(first):
first.append(x)
return {"count": 65026, "first_x_of_each_value": first, "op": op,
"sha256": sha256(table).hex()}
if op == "alpha":
lo, hi = -(1 << 18), 1 << 18
nz = bytes(alpha_nonzero(x) for x in range(lo, hi + 1))
eo = bytes(alpha_evenodd(x) for x in range(lo, hi + 1))
with open(expected_path, "rb") as f:
vs = [p[0] for p in json.load(f)["probes"]] # the probe inputs (see above)
return {"domain": [lo, hi], "evenodd_sha256": sha256(eo).hex(),
"nonzero_sha256": sha256(nz).hex(), "op": op,
"probes": [[x, alpha_nonzero(x), alpha_evenodd(x)] for x in vs]}
if op == "unpremul":
table = bytearray()
for a in range(256):
for c in range(a + 1):
table.append(unpremul((c, c, c, a))[0])
return {"count": len(table), "op": op, "sha256": sha256(bytes(table)).hex()}
if op == "half_up":
return {"op": op, "values": [R_half_up(p, s) for p, s in v["cases"]]}
if op == "r_rat":
return {"op": op, "values": [R_rat(n, d) for n, d in v["cases"]]}
if op == "interp":
P = Payload()
P.tables = [tuple(t) for t in v["tables"]]
P.tracks = []
for t in v["tracks"]:
tr = TrackRec()
tr.kind = t["kind"]
tr.keys = [Key(k[0], k[1], k[2], b"\0\0\0") for k in t["keys"]]
tr.ts = [k.t for k in tr.keys]
tr.vs = [k.value for k in tr.keys]
P.tracks.append(tr)
return {"op": op, "values": [[eval_track(P, ti, tau) for tau in v["ticks"]]
for ti in range(len(P.tracks))]}
raise ValueError(f"unknown op {op}")
def flatten_result(v):
out = []
for c in v["curves"]:
pts = [tuple(p) for p in c[1:]]
verts = flatten_quad(*pts) if c[0] == "q" else flatten_cubic(*pts)
out.append({"n": len(verts).bit_length() - 1, "vertices": [list(p) for p in verts]})
return {"curves": out, "op": v.get("op", "flatten")}
def pinned_step(name, desc):
"""The step a refusal vector's own name or description pins, or None."""
import re
m = re.match(r"s(\d)([a-h]?)-", name)
if m:
return m.group(1) + m.group(2)
m = re.search(r"\((?:step|check) (\d+[a-h]?) before", desc)
if m:
return m.group(1)
m = re.match(r"(?:step|check) (\d+[a-h]?):", desc)
if m:
return m.group(1)
m = re.match(r"step (\d+)\(([a-h])\):", desc)
if m:
return m.group(1) + m.group(2)
if pinned_rule(desc) is not None:
return "5g" # section 2.7: rules 2-5 of section 2.5 are checked at step 5(g)
return None
def pinned_rule(desc):
"""The canonical rule of section 2.5 a vector's description pins ("rule 4: ..."),
or None."""
import re
m = re.match(r"rule (\d):", desc)
return m.group(1) if m else None
def json_diff(a, b, path=""):
"""Field paths where two JSON values differ."""
if isinstance(a, dict) and isinstance(b, dict):
out = []
for k in sorted(set(a) | set(b)):
if k not in a or k not in b:
out.append(f"{path}.{k} present in one only")
else:
out += json_diff(a[k], b[k], f"{path}.{k}")
return out
if isinstance(a, list) and isinstance(b, list):
if len(a) != len(b):
return [f"{path} length {len(a)} != {len(b)}"]
out = []
for i, (x, y) in enumerate(zip(a, b)):
out += json_diff(x, y, f"{path}[{i}]")
return out
return [] if a == b else [f"{path}: checker {a!r}, expected {b!r}"]
def text_record_diff(mine, want):
"""Where a recomputed text record first differs from the committed one."""
if want is None:
return ["no text record beside the expected file"]
a, b = mine.split(b"\n"), want.split(b"\n")
out = []
if len(a) != len(b):
out.append(f"{len(a) - 1} lines, expected {len(b) - 1}")
for n, (x, y) in enumerate(zip(a, b)):
if x != y:
at = next((i for i, (p, q) in enumerate(zip(x, y)) if p != q), min(len(x), len(y)))
lo = max(0, at - 60)
out.append(f"line {n + 1}, byte {at}: checker ...{x[lo:at + 60]!r}")
out.append(f"line {n + 1}, byte {at}: expected ...{y[lo:at + 60]!r}")
break
return out or ["byte form differs"]
def sidecar_exercise(P, frames):
"""The digest sidecar (section 2.10) the authoring tool writes for an accepted
payload, built from its rendered frames: the probe set over every listed pair and
a clip per pair. It must parse and rewrite to its own bytes, every probe must
match, and three corruptions must give sidecar_malformed, sidecar_mismatch and
layer_mismatch. Returns a list of failures."""
cache = {}
for e in frames:
for g, d in e["frames"]:
cache[(e["w"], e["h"], e["num"], e["den"], g)] = bytes.fromhex(d)
pairs = [(z.w, z.h, r.num, r.den) for z in P.sizes for r in P.rates]
probes = probe_set(P, pairs)
if not probes:
return [] # no frame at any pair: no sidecar can list a probe
sc = {"clips": sorted([e["w"], e["h"], e["num"], e["den"], e["clip"]] for e in frames),
"instr": hashlib.sha256(P.bytes).hexdigest(),
"probes": [list(p) + [cache[p].hex()] for p in probes]}
b = write_sidecar(sc)
fails = []
try:
got = parse_sidecar(b, P)
if write_sidecar(got) != b:
fails.append("sidecar does not rewrite to its own bytes")
if check_probes(P, got, cache) is not None:
fails.append("a probe of the sidecar mismatches")
for c in got["clips"]:
if clip_digest(P, *c[:4], cache).hex() != c[4]:
fails.append(f"clip {c[:4]} mismatches in verify mode")
except SidecarCode as e:
fails.append(f"the tool's own sidecar refused: {e}")
for bad, want in ((b.replace(b'":', b'": ', 1), "sidecar_malformed"),
(b.replace(sc["instr"].encode(), b"0" * 64), "sidecar_mismatch"),
(b[:-1] + b" ", "sidecar_malformed")):
try:
parse_sidecar(bad, P)
fails.append(f"a corrupted sidecar was accepted, {want} expected")
except SidecarCode as e:
if e.code != want:
fails.append(f"corrupted sidecar gave {e.code}, {want} expected")
flipped = dict(sc, probes=[list(sc["probes"][0][:5]) + ["0" * 64]] + sc["probes"][1:])
if "0" * 64 != sc["probes"][0][5]:
if check_probes(P, parse_sidecar(write_sidecar(flipped), P), cache) is None:
fails.append("a wrong probe digest was not a layer_mismatch")
return fails
def corpus(root, only_groups=None, verbose=True):
import time
store = load_store(os.path.join(root, "sets"))
agree = total = 0
rewrite_ok = rewrite_total = 0
step_ok = step_total = 0
disagreements = []
step_disagreements = []
rendered = {}
sidecars = 0
records = 0
compared = set() # the files under expected/ this run recomputed
example = spec_worked_example()
vroot = os.path.join(root, "vectors")
for group in sorted(os.listdir(vroot)):
if only_groups and group not in only_groups:
continue
gdir = os.path.join(vroot, group)
with open(os.path.join(gdir, "index.json"), encoding="utf-8") as f:
index = json.load(f)
for name in sorted(index):
t = time.time()
exp_path = os.path.join(root, "expected", group, name + ".json")
with open(exp_path, "rb") as f:
exp_bytes = f.read()
compared.add((group, name + ".json"))
refusal = None
rw = None
P = None
try:
if os.path.exists(os.path.join(gdir, name + ".wvg1")):
with open(os.path.join(gdir, name + ".wvg1"), "rb") as f:
data = f.read()
got, rw, refusal, P = payload_result(data, store, group)
if P is not None and group in RENDER_GROUPS:
rendered[(group, name)] = got.get("frames")
sidecars += 1
for fl in sidecar_exercise(P, got["frames"]):
disagreements.append((f"{group}/{name} sidecar", [fl]))
elif os.path.exists(os.path.join(gdir, name + ".wos1")):
with open(os.path.join(gdir, name + ".wos1"), "rb") as f:
data = f.read()
got, rw, refusal = wos1_result(data)
else:
with open(os.path.join(gdir, name + ".json"), encoding="utf-8") as f:
v = json.load(f)
got = flatten_result(v) if group == "flatten" else arith_result(v, exp_path)
mine = canon(got)
except BoundError as e:
mine = None
got = {"bound": str(e)}
except AssertionError as e:
import traceback
at = traceback.extract_tb(e.__traceback__)[-1]
mine = None
got = {"assertion": f"line {at.lineno}: {at.line}"}
total += 1
ok = mine == exp_bytes
if ok:
agree += 1
else:
try:
exp = json.loads(exp_bytes)
diff = json_diff(got, exp)
except ValueError:
diff = ["expected file is not JSON"]
disagreements.append((f"{group}/{name}", diff[:6] or ["byte form differs"]))
if P is not None and group in TEXT_GROUPS:
# The text record (section 5.4) is an expected file of its own, the
# file beside the one that states its SHA-256.
records += 1
total += 1
rec_name = name + TEXT_RECORD_SUFFIX
rec_path = os.path.join(root, "expected", group, rec_name)
want_rec = None
if os.path.exists(rec_path):
with open(rec_path, "rb") as f:
want_rec = f.read()
compared.add((group, rec_name))
if P.record == want_rec:
agree += 1
else:
disagreements.append((f"{group}/{rec_name}",
text_record_diff(P.record, want_rec)))
for fl in text_record_recheck(P, P.record):
disagreements.append((f"{group}/{rec_name}", [fl]))
for fl in getattr(P, "text_faults", None) or []:
disagreements.append((f"{group}/{rec_name}", [f"section 5.4: {fl}"]))
if name == "worked-example" and example is not None:
# The vector its index calls the worked example of section 5.4:
# the text prints its transcript and its line for frame 30.
lines = P.record.split(b"\n")
if lines[0] != example[0] or example[1] not in lines[1:]:
disagreements.append((f"{group}/{rec_name}", [
"the record is not the worked example section 5.4 prints"]))
if rw is not None:
rewrite_total += 1
rewrite_ok += bool(rw)
if not rw:
disagreements.append((f"{group}/{name}", ["does not rewrite to its own bytes"]))
if refusal is not None:
want = pinned_step(name, index[name])
mystep = refusal.step
if group == "refusals" and name.startswith("wos1"):
mystep = refusal.where.split(":")[0].replace("wos1 step ", "")
rule = pinned_rule(index[name])
if rule is not None:
# A canonical rule of section 2.5: the checker names the rule it
# refused under at the start of `where`.
want += f" rule {rule}"
mystep += " " + refusal.where.split(":")[0]
if want is not None:
step_total += 1
if want == mystep:
step_ok += 1
else:
step_disagreements.append((f"{group}/{name}", mystep, want,
refusal.where))
if verbose:
tag = "ok " if ok else "DIFF"
extra = f" [{refusal.code} step {refusal.step}: {refusal.where}]" if refusal else ""
print(f"{tag} {group}/{name} ({time.time() - t:.1f}s){extra}", flush=True)
# the sets the payloads hold, each rewritten
for name in sorted(os.listdir(os.path.join(root, "sets"))):
with open(os.path.join(root, "sets", name), "rb") as f:
data = f.read()
got, rw, refusal = wos1_result(data)
if rw is not None:
rewrite_total += 1
rewrite_ok += bool(rw)
if verbose:
print(f"set {name}: {got['code']}" + (f", rewrites: {rw}" if rw is not None else
f" ({refusal.where})"))
# sha256.json: every digest recomputed
sha_ok = sha_total = 0
sha_path = os.path.join(root, "sha256.json")
if os.path.exists(sha_path) and not only_groups:
with open(sha_path, encoding="utf-8") as f:
sj = json.load(f)
for key, ent in sj.items():
sha_total += 1
if key == "arith/div255":
good = ent["sha256"] == sha256(bytes(div255(x) for x in range(65026))).hex()
else:
vec, wh, rate = key.split("@")
group, name = vec.split("/")
w, h = (int(x) for x in wh.split("x"))
num, den = (int(x) for x in rate.split("/"))
fr = rendered.get((group, name)) or []
e = [x for x in fr if (x["w"], x["h"], x["num"], x["den"]) == (w, h, num, den)]
good = bool(e) and ent["frames_sha256"] == [d for _, d in e[0]["frames"]] \
and ent["clip_sha256"] == e[0]["clip"]
sha_ok += good
if not good:
disagreements.append((f"sha256.json {key}", ["digest differs"]))
# Every file under expected/, in the groups this run covers, must be one it
# recomputed: a file no index names and no rule above reaches is a disagreement.
eroot = os.path.join(root, "expected")
present = 0
for group in sorted(os.listdir(eroot)):
if only_groups and group not in only_groups:
continue
for fname in sorted(os.listdir(os.path.join(eroot, group))):
present += 1
if (group, fname) not in compared:
disagreements.append((f"{group}/{fname}",
["a file under expected/ that this run did not recompute"]))
print()
print(f"expected files recomputed byte for byte: {agree} of {total}")
print(f"files under expected/ in the groups covered: {present}, compared: {len(compared)}")
print(f"text records recomputed, and recomputed a second way: {records}")
print(f"payloads and sets rewritten to their own bytes: {rewrite_ok} of {rewrite_total}")
print(f"refusal steps the vectors pin, reported at that step: {step_ok} of {step_total}")
if sha_total:
print(f"sha256.json entries recomputed: {sha_ok} of {sha_total}")
print(f"digest sidecars built, parsed, rewritten and corrupted: {sidecars}")
for v, diff in disagreements:
print(f"DISAGREE {v}")
for d in diff:
print(f" {d}")
for v, mine, want, where in step_disagreements:
print(f"STEP {v}: checker step {mine} ({where}), vector pins step {want}")
peaks = ", ".join(f"{k}={v}" for k, v in sorted(BOUND_PEAK.items()))
print(f"bound peaks: {peaks}")
return 0 if not disagreements and not step_disagreements and agree == total else 1
# ------------------------------------------------------------------ command line
def _opt(args, name, default=None):
if name in args:
i = args.index(name)
v = args[i + 1]
del args[i:i + 2]
return v
return default
def main(argv):
args = list(argv)
if not args:
print(__doc__)
return 2
cmd = args.pop(0)
if cmd == "selftest":
return selftest()
if cmd == "corpus":
quiet = "--quiet" in args
if quiet:
args.remove("--quiet")
groups = _opt(args, "--groups")
rc = selftest()
rc2 = corpus(args[0], groups.split(",") if groups else None, not quiet)
return rc or rc2
sets_dir = _opt(args, "--sets")
out = _opt(args, "--out")
verify = "--verify" in args
if verify:
args.remove("--verify")
store = load_store(sets_dir)
if cmd == "load":
data = open(args[0], "rb").read()
try:
P = load(data, store)
except Refusal as r:
print(json.dumps({"refused": r.code, "step": r.step, "where": r.where}))
return 0
same = write_wvg1(P) == data
print(json.dumps({"accepted": True, "costs": P.costs, "rewrite_identical": same,
"text_hash": P.text_hash.hex()}))
return 0 if same else 1
if cmd == "render":
data = open(args[0], "rb").read()
w, h = (int(x) for x in args[1].split("x"))
num, den = (int(x) for x in args[2].split("/"))
g = int(args[3])
P = load(data, store)
buf = render(P, w, h, num, den, g)
if out:
open(out, "wb").write(buf)
print(sha256(buf).hex())
return 0
if cmd == "text":
data = open(args[0], "rb").read()
P = load(data, store)
if len(args) >= 3:
num, den = (int(x) for x in args[1].split("/"))
try:
line = projection_json(P, num, den, int(args[2]))
except RequestError as e:
print(json.dumps({"request": e.code}))
return 1
sys.stdout.buffer.write(line + b"\n")
return 0
rec = text_record(P)
if out:
open(out, "wb").write(rec)
print(sha256(rec).hex())
return 0
if cmd == "sidecar":
data = open(args[0], "rb").read()
sc_bytes = open(args[1], "rb").read()
P = load(data, store)
try:
sc = parse_sidecar(sc_bytes, P)
except SidecarCode as e:
print(json.dumps({"sidecar": e.code, "why": e.why}))
return 0
bad = check_probes(P, sc)
if bad is not None:
print(json.dumps({"render": "layer_mismatch", "probe": bad[:5]}))
return 1
if verify:
for c in sc["clips"]:
if clip_digest(P, *c[:4]).hex() != c[4]:
print(json.dumps({"verify": "clip mismatch", "clip": c[:4]}))
return 1
print(json.dumps({"sidecar": "ok", "probes": len(sc["probes"])}))
return 0
print(f"unknown command {cmd}")
return 2
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))