wai-vector: the default stroke set and the test repertoire, as WSF1 sources expanded by Annex A, and the text vectors (D7-5) #224
61 changed files with 3351 additions and 31 deletions
20
.github/workflows/wai-vector.yml
vendored
20
.github/workflows/wai-vector.yml
vendored
|
|
@ -8,12 +8,16 @@ name: wai-vector vector graphics
|
|||
# - Linux x86_64, Linux arm64 and macOS arm64, natively, debug and release: the unit
|
||||
# tests, the refusal-order, canonical, clock, track, geometry, cost, held-set and
|
||||
# raster tests, the corpus tests (every frame of the rendering groups recomputed
|
||||
# to its SHA-256), and `wai-vector vectors verify`, which also regenerates the
|
||||
# whole corpus and converts every payload's authoring form back to its bytes;
|
||||
# to its SHA-256), the in-house sets' tests (the default stroke set's SHA-256 is
|
||||
# Annex B's, Annex A's table is the expansion's, and both sets re-derive from their
|
||||
# WSF1 sources), and `wai-vector vectors verify`, which also regenerates the
|
||||
# whole corpus and converts every payload's authoring form back to its bytes; and
|
||||
# the sink's build, without the feature `author`, so with no stroker;
|
||||
# - WASI (wasm32-wasip2) under wasmtime, a 32-bit target: the same tests and the same
|
||||
# `vectors verify`;
|
||||
# - a browser's engine (wasm32-unknown-unknown) under Node: the recomputation of
|
||||
# every expected file and every authoring form;
|
||||
# every expected file and every authoring form, in a sink's build, and the default
|
||||
# stroke set's SHA-256 against Annex B;
|
||||
# - the fuzz leg: the corpus-seeded fuzz of the WOS1 and WVG1 parsers at depth, each
|
||||
# accepted payload of small cost rendered, the peak-allocation test and the
|
||||
# duplicate-detection comparison count;
|
||||
|
|
@ -45,6 +49,8 @@ on:
|
|||
- 'wai/tools/wai_vector_*.py'
|
||||
# The text the crate and the corpus are held to.
|
||||
- 'wai/extensions/graphics-vector.md'
|
||||
# The licence text the in-house sets carry in their notes.
|
||||
- 'LICENSE'
|
||||
- '.github/workflows/wai-vector.yml'
|
||||
push:
|
||||
branches:
|
||||
|
|
@ -55,6 +61,7 @@ on:
|
|||
- 'wai/vector-conformance/**'
|
||||
- 'wai/tools/wai_vector_*.py'
|
||||
- 'wai/extensions/graphics-vector.md'
|
||||
- 'LICENSE'
|
||||
- '.github/workflows/wai-vector.yml'
|
||||
|
||||
jobs:
|
||||
|
|
@ -101,6 +108,13 @@ jobs:
|
|||
shell: bash
|
||||
run: cargo run --locked --release --features std --bin wai-vector -- vectors verify ..
|
||||
|
||||
# A sink has no stroker (graphics-vector.md §3.14): the library builds without
|
||||
# the authoring code.
|
||||
- name: The sink's build, without the feature author
|
||||
working-directory: wai/wai-vector
|
||||
shell: bash
|
||||
run: cargo build --locked --no-default-features --lib
|
||||
|
||||
wasip2:
|
||||
name: wasm32-wasip2 (wasmtime)
|
||||
runs-on: linux
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
> Status: Draft. This extension defines `wai.graphics.vector`: titles, credits and other authored graphics carried as integer drawing instructions, which the sink rasterises into an overlay layer. This revision defines:
|
||||
> - the model, the conformance criteria and what they cover (§1);
|
||||
> - the payload format `WVG1`, the outline-set format `WOS1`, the digest sidecar, and the order in which a sink refuses a payload (§2);
|
||||
> - the arithmetic: the clock, track evaluation, placement, the load-time interval check and cost, flattening, coverage and compositing, written as the raster stage family RS1–RS9 (§3).
|
||||
> - the arithmetic: the clock, track evaluation, placement, the load-time interval check and cost, flattening, coverage and compositing, written as the raster stage family RS1–RS9 (§3);
|
||||
> - outline sets: pinning and carriage, the notes and the licence they carry, the default stroke set and the test repertoire (§4), the stroke source format `WSF1` and its integer expansion (Annex A), and the default stroke set's digest (Annex B).
|
||||
>
|
||||
> Sections 4 to 6 and Annexes A and B are headings in this revision. Their text lands in later revisions of this Draft: outline sets and the default stroke set (§4, Annexes A and B), text and accessibility (§5), and packages and floors (§6). Where §1–§3 depend on them, they say what they depend on.
|
||||
> Sections 5 and 6 are headings in this revision. Their text lands in later revisions of this Draft: text and accessibility (§5), and packages and floors (§6). Where §1–§4 depend on them, they say what they depend on.
|
||||
>
|
||||
> **What a reader can build from this text.** A sink that loads a `WVG1` payload and the `WOS1` sets it names, refuses exactly the payloads §2.7 refuses with the code §2.7 gives, and renders each listed size, rate and frame to the bytes §3 defines; and a verifier that checks rendered frames against a digest sidecar (§2.10).
|
||||
>
|
||||
|
|
@ -811,7 +812,63 @@ Every stage is total once the payload has loaded: the point cap, the range and t
|
|||
|
||||
## 4. Outline sets
|
||||
|
||||
*This section lands in a later revision of this Draft:* how a set is made from a font (authoring only), pinning and carriage, the keys of a set's notes and the licence they carry, and the default stroke set (Annexes A and B). §2.1 defines the format a sink reads.
|
||||
### 4.1 The sink reads `WOS1` only
|
||||
|
||||
A sink never parses a font file and never runs a font program. It reads `WOS1` (§2.1): fixed-width records and bounded counts, no recursion (composite glyphs are flattened before a set is written), no bytecode, subroutines, compression, bitmaps or variations, an exact box per glyph, and named refusals.
|
||||
|
||||
An authoring tool makes a set, from a font or from a stroke source (Annex A). How it does so is not sink conformance: a sink conforms by matching the set's digest, and a set is what its bytes are.
|
||||
|
||||
### 4.2 Pinning and carriage
|
||||
|
||||
- **Pinned** means named by SHA-256 over the whole `WOS1`, notes included, in a `SET` record, which also states the set's exact length, whether the set is held or inline (§2.3).
|
||||
- **Inline:** the set's bytes are in `INLINE`. The sink hashes them and refuses the payload on a mismatch (§2.7 step 6). The payload is then self-contained.
|
||||
- **Held:** the sink obtains the bytes by digest under its resolution policy: for example, only the sets bundled with it, a cache, or a fetch by digest. It reads at most `length + 1` bytes, refuses a length mismatch (`outline_mismatch`), and hashes the bytes before it parses them, whatever their source (§2.7 step 6).
|
||||
- **Bundled:** the default stroke set (§4.4) ships with every reference sink and is always held.
|
||||
- **Never substitute** (§1.6). A set the sink cannot obtain refuses the payload (`outline_not_held`), and the sink presents its floor.
|
||||
- **Revocation** of a held set is a later revision's (§2.7 step 6 reserves its names).
|
||||
|
||||
### 4.3 The notes: provenance and licence
|
||||
|
||||
The notes of a set made by the authoring tool are UTF-8 `key=value` lines, each ending in U+000A, then a blank line, then the full licence text of the set's source. A key that does not apply to a source is omitted.
|
||||
|
||||
| key | content |
|
||||
|---|---|
|
||||
| `parent_sha256` | the SHA-256 of the source: the font file, or the stroke source (Annex A), in lowercase hexadecimal |
|
||||
| `converter` | the tool's name and version, or for a stroke source the expansion's (Annex A) |
|
||||
| `licence` | the source's licence, as an SPDX identifier, for example `Apache-2.0` |
|
||||
| `licence_sha256` | the SHA-256 of the licence text that follows the blank line |
|
||||
| `copyright` | the source's copyright lines, verbatim; repeatable |
|
||||
| `rfn` | each name the source's licence reserves, or `none` |
|
||||
| `rules` | for a set converted from a font: the canonicalisation, that is the ×2 grid, whether overlaps were removed (`off`, or the SHA-256 of the pinned module that removed them), and any down-shift |
|
||||
| `instance` | for a variable font: the axis tags and values the set was instanced at |
|
||||
| `source` | for a stroke source: where its drawing comes from |
|
||||
| `glyphs` | the source glyph IDs the set keeps, as ranges, and what they mean |
|
||||
| `metrics` | `ascender,descender,cap_height,x_height` in outline units, read by the authoring tool's checks and never by a sink |
|
||||
|
||||
- **The licence travels inside the digest.** A set served by digest from any origin carries its licence and notices in the pinned bytes, and no copy can drop them without changing the digest.
|
||||
- A set has no family-name field, so it never presents a reserved name as its own.
|
||||
- **What checks what.** A sink checks only that the notes are valid UTF-8 (§2.1). A verifier checks that `licence_sha256` is the SHA-256 of the text after the blank line, and reports `licence_mismatch` when it is not.
|
||||
|
||||
### 4.4 The default stroke set
|
||||
|
||||
- **A stroke source, expanded once.** The default stroke set is drawn in this repository as original centre lines, in the stroke source format `WSF1` (Annex A). No outline is traced from any font. Annex A's integer expansion turns it, once, into an ordinary polygon-only `WOS1`, which is pinned and bundled. The sink has no stroker (§3.14).
|
||||
- **Licence:** Apache-2.0. The set's notes carry the licence text and say where the drawing comes from.
|
||||
- **Coverage:** printable ASCII, U+0020–U+007E. Each glyph ID is the code point the glyph draws. A set with further characters is a new set with its own digest, never a change to this one.
|
||||
- **Carriage:** an encoder names it with a held `SET` record stating Annex B's digest and length. A reference sink resolves it from the bytes bundled with it.
|
||||
- **Layout** is the authoring tool's. It places the glyphs from the advances in the source (Annex A); a sink reads none.
|
||||
- Where two stroke paths of one glyph overlap inside a partly covered pixel, the fill over-covers (§3.13).
|
||||
|
||||
### 4.5 The test repertoire
|
||||
|
||||
A second stroke source, drawn in this repository under Apache-2.0, is for the conformance corpus only. It is never bundled, and never a default for authors. It holds only what the corpus's test words need:
|
||||
- **Arabic:** the isolated, initial, medial and final forms of the letters of the test words, the lam-alef ligature as one glyph, and two marks;
|
||||
- **Hebrew:** the letters of the test words, among them one final form;
|
||||
- **Devanagari:** the consonants of the test words, each with its headline; one conjunct as a single glyph; the vowel sign i, which displays before the consonant it follows in logical order; and one mark above;
|
||||
- one monochrome glyph for U+1F600, for a visible character that a colour glyph would otherwise draw.
|
||||
|
||||
Its shaping is a small table in the corpus generator: Arabic forms chosen by Unicode joining type, the ligature, the reordered vowel sign, and authored mark positions. Each joining form's connector, and each Devanagari headline, runs to the edge of its glyph's advance, so the pen's round cap passes the edge and overlaps the neighbour, as joins in production fonts overlap; inside one run those overlaps over-cover their partly covered edge pixels (§3.8, §3.13).
|
||||
|
||||
With it, the corpus pins right-to-left visual order, contextual forms, a conjunct, a reordered vowel sign, marks, clusters and mixed direction on every fabric, with no third-party font. It does not test a shaper on a production font.
|
||||
|
||||
## 5. Text and accessibility
|
||||
|
||||
|
|
@ -823,8 +880,110 @@ Every stage is total once the payload has loaded: the point cap, the range and t
|
|||
|
||||
## Annex A. `WSF1`, the stroke source format
|
||||
|
||||
*Lands with §4.*
|
||||
A `WSF1` is an authoring-side source: a sink never reads one. It holds, for each glyph, stroke paths: polylines of integer centre-line points in outline units, drawn with one round pen of radius `ρ`, with round caps and round joins. The expansion below turns it into a `WOS1` (§2.1) with integer arithmetic alone. §2.0's common rules apply: little-endian fixed-width integers, no offsets, the exact length computed from the header, and caps checked before anything is allocated.
|
||||
|
||||
### A.1 Layout
|
||||
|
||||
Header (32 bytes) · glyph table (8 × `n_glyphs`) · stroke ends (2 × `strokes_total`) · points (4 × `points_total`) · notes (`notes_len`). The exact length is `32 + 8·n_glyphs + 2·strokes_total + 4·points_total + notes_len`.
|
||||
|
||||
**Header (32 bytes)**
|
||||
|
||||
| off | size | field | rule |
|
||||
|---|---|---|---|
|
||||
| 0 | 4 | magic | the ASCII bytes `WSF1` |
|
||||
| 4 | 4 | `format` | `u32` = 1 |
|
||||
| 8 | 2 | `upem2` | `u16`: twice the source's units per em, copied to the set's `upem2` |
|
||||
| 10 | 2 | `radius` | `u16`: the pen radius `ρ` in outline units, 1 ..= 4096 |
|
||||
| 12 | 4 | `n_glyphs` | `u32`, 1 ..= 65,536 |
|
||||
| 16 | 4 | `strokes_total` | `u32`, ≤ 2^14 |
|
||||
| 20 | 4 | `points_total` | `u32`, ≤ 2^16 |
|
||||
| 24 | 4 | `notes_len` | `u32`, 1 ..= 2^19 |
|
||||
| 28 | 4 | `zero` | |
|
||||
|
||||
**Glyph record (8 bytes):** `gid: u16`, strictly increasing across records. `n_strokes: u16`, ≤ 64, ≤ `n_points`, and at least 1 when `n_points ≥ 1`. `n_points: u16`, ≤ 256. `advance: u16`: the glyph's advance in outline units, which an authoring tool's layout reads and the set does not carry.
|
||||
|
||||
**Stroke ends.** For each glyph in record order, `n_strokes` values, `u16`: the index, within the glyph, of each stroke path's last point. They strictly increase, and the last equals `n_points − 1`.
|
||||
|
||||
**Points.** For each glyph in record order, `n_points` points of 4 bytes, `x: i16` and `y: i16`: centre-line points in outline units, y down, the origin at the glyph's pen position on the baseline.
|
||||
|
||||
**Notes.** UTF-8, in §4.3's form; the expansion copies them into the set's notes.
|
||||
|
||||
**Checks, in order.** An expansion refuses a source at the first failure:
|
||||
1. fewer than 32 bytes; the magic, `format` or the `zero` field;
|
||||
2. `radius`, `n_glyphs`, `strokes_total`, `points_total` and `notes_len`, in field order, each against its range;
|
||||
3. the exact length;
|
||||
4. each glyph record: `n_strokes` and `n_points` against their caps, then `n_strokes` against `n_points`;
|
||||
5. the `gid`s strictly increase;
|
||||
6. the records' `n_strokes` sum to `strokes_total`, and their `n_points` to `points_total`;
|
||||
7. each glyph's stroke ends;
|
||||
8. every point has `−2^15 + ρ ≤ x, y ≤ 2^15 − 1 − ρ`;
|
||||
9. within each stroke path, consecutive points differ, and no two consecutive segments point in exactly opposite directions (their cross product is 0 and their dot product negative);
|
||||
10. the notes are valid UTF-8 (RFC 3629).
|
||||
|
||||
**Why the caps fit `WOS1`.** A stroke path of `m ≥ 2` points expands to at most `36·m − 4` vertices and one of one point to 64, so a glyph expands to at most `36·256 + 64·64 = 13,312 ≤ 16,383` points (§2.1), and a source to at most `36·2^16 + 64·2^14 < 2^22` points and `2^14` contours. Every vertex lies within `ρ` of its centre-line point on each axis, so by check 8 it fits `i16`.
|
||||
|
||||
### A.2 The table of unit vectors
|
||||
|
||||
`U` is 64 unit vectors in Q15: `U[k] = (round(2^15·cos(2πk/64)), round(2^15·sin(2πk/64)))` for `k = 0 … 63`, each rounded once to the nearest integer. With y down, `k` increases from +x toward +y. **These bytes are normative:** for each `k` in order, the `cos` component, then the `sin` component, each an `i32` little-endian, 512 bytes in all:
|
||||
|
||||
```text
|
||||
0080000000000000627f00008c0c00008a7d0000f91800007d7a000028250000
|
||||
42760000fc300000e3700000573c00006e6a00001d470000f262000034510000
|
||||
825a0000825a000034510000f26200001d4700006e6a0000573c0000e3700000
|
||||
fc30000042760000282500007d7a0000f91800008a7d00008c0c0000627f0000
|
||||
000000000080000074f3ffff627f000007e7ffff8a7d0000d8daffff7d7a0000
|
||||
04cfffff42760000a9c3ffffe3700000e3b8ffff6e6a0000ccaefffff2620000
|
||||
7ea5ffff825a00000e9dffff345100009295ffff1d4700001d8fffff573c0000
|
||||
be89fffffc3000008385ffff282500007682fffff91800009e80ffff8c0c0000
|
||||
0080ffff000000009e80ffff74f3ffff7682ffff07e7ffff8385ffffd8daffff
|
||||
be89ffff04cfffff1d8fffffa9c3ffff9295ffffe3b8ffff0e9dffffccaeffff
|
||||
7ea5ffff7ea5ffffccaeffff0e9dffffe3b8ffff9295ffffa9c3ffff1d8fffff
|
||||
04cfffffbe89ffffd8daffff8385ffff07e7ffff7682ffff74f3ffff9e80ffff
|
||||
000000000080ffff8c0c00009e80fffff91800007682ffff282500008385ffff
|
||||
fc300000be89ffff573c00001d8fffff1d4700009295ffff345100000e9dffff
|
||||
825a00007ea5fffff2620000ccaeffff6e6a0000e3b8ffffe3700000a9c3ffff
|
||||
4276000004cfffff7d7a0000d8daffff8a7d000007e7ffff627f000074f3ffff
|
||||
```
|
||||
|
||||
Their SHA-256 is `11c05a1323c8b6dde89a5627d29acecb9163b87dfd169054236fb1d1def66437`. No trigonometry is evaluated anywhere else: the expansion reads only these integers.
|
||||
|
||||
### A.3 The expansion (normative)
|
||||
|
||||
The notation is §3.0's (`R_rat`, `R_half_up`, `isqrt`), with `cross(a, b) = a_x·b_y − a_y·b_x`. Every value is an exact integer, and every one fits `i64`.
|
||||
|
||||
**The set.** One `WOS1` glyph record for each source glyph, in order, with the same `gid`. One contour for each stroke path, in order, every point on-curve (tag 0). `format` 1; `upem2` copied; `flags` 0, because overlaps are not removed; each glyph's box exact (§2.1). The notes are the line `parent_sha256=` followed by the source's SHA-256 in lowercase hexadecimal, the line `converter=wsf1-expansion/1`, each ending in U+000A, then the source's notes, byte for byte.
|
||||
|
||||
**A segment's offset.** For a segment `d = (dx, dy)`, `len = isqrt(dx² + dy²)` and `n(d) = (R_rat(−dy·ρ, len), R_rat(dx·ρ, len))`. Check 9 makes `len ≥ 1`.
|
||||
|
||||
**Arc vertices.** The vertex of direction `k` at a point `p` is `p + (R_half_up(ρ·U[k]_x, 15), R_half_up(ρ·U[k]_y, 15))`. An arc at `p`, from an offset `a`, takes the directions `u = U[k]` that lie strictly between its ends in the **negative sense** (the sense of decreasing `k`):
|
||||
- a **cap**, from `a` to `−a`: every `u` with `cross(a, u) < 0`;
|
||||
- a **join**, from `a` to `b`: none unless `cross(a, b) < 0`; otherwise every `u` with `cross(a, u) < 0` and `cross(u, b) < 0`.
|
||||
|
||||
The chosen directions lie in one open half-plane, and are taken in order from `a`: `u` before `v` when `cross(u, v) < 0`.
|
||||
|
||||
**The contour of a stroke path** `p_0 … p_{m−1}`:
|
||||
- **`m = 1`, a dot:** the vertices of every direction at `p_0`, for `k = 0, 63, 62, …, 1`.
|
||||
- **`m ≥ 2`:** let `d_i = p_{i+1} − p_i` and `n_i = n(d_i)` for `i = 0 … m − 2`, and at each interior point `c_i = cross(d_{i−1}, d_i)`. The vertices are, in order:
|
||||
1. `p_0 + n_0`;
|
||||
2. for `i = 1 … m − 2`: `p_i + n_{i−1}`; if `c_i < 0`, the join from `n_{i−1}` to `n_i` at `p_i`; then `p_i + n_i`;
|
||||
3. `p_{m−1} + n_{m−2}`, then the cap from `n_{m−2}` at `p_{m−1}`;
|
||||
4. `p_{m−1} − n_{m−2}`;
|
||||
5. for `i = m − 2` down to 1: `p_i − n_i`; if `c_i > 0`, the join from `−n_i` to `−n_{i−1}` at `p_i`; then `p_i − n_{i−1}`;
|
||||
6. `p_0 − n_0`, then the cap from `−n_0` at `p_0`.
|
||||
|
||||
So the left offsets run forward, the end cap turns, the right offsets run back, and the start cap closes the contour. The outer side of each join takes the arc; the inner side takes its two offset points only, and the small loop they make lies inside the stroke's body, where the non-zero rule covers it.
|
||||
- **Then** a vertex equal to the one before it is dropped, and while the contour has more than one vertex and its last equals its first, the last is dropped.
|
||||
|
||||
The same source therefore gives the same set, byte for byte, on every machine.
|
||||
|
||||
## Annex B. The default stroke set's digest
|
||||
|
||||
*Lands with §4.*
|
||||
| | |
|
||||
|---|---|
|
||||
| the set (`WOS1`) | SHA-256 `05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9`, length 105,523 bytes |
|
||||
| its source (`WSF1`) | SHA-256 `4eec759d394d021aec8ce99b41e4b25fecb36372c77459ef62df182b9eb8c783`, length 19,181 bytes |
|
||||
| coverage | U+0020–U+007E; each glyph ID is the code point it draws |
|
||||
| pen | radius 80 outline units; `upem2` 2000 |
|
||||
| licence | Apache-2.0, its text in the notes |
|
||||
|
||||
Expanding the source by Annex A gives exactly the set's bytes. The conformance corpus holds both, as `sources/stroke-ascii.wsf1` and `sets/stroke-ascii.wos1`, and every fabric leg checks the set's digest against this annex.
|
||||
|
|
|
|||
|
|
@ -2040,7 +2040,8 @@ def selftest():
|
|||
# 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.
|
||||
|
||||
RENDER_GROUPS = ("clock", "composite", "fills", "motion", "runs", "slicing")
|
||||
# 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",)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,15 +5,12 @@ Vectors for `wai.graphics.vector`
|
|||
outline set and the `WVG1` payload with the order in which a sink refuses them (§2),
|
||||
the arithmetic a sink loads with: the clock, track evaluation, the load-time interval
|
||||
check and the per-frame cost (§3.1–§3.4); and the raster stage family RS1–RS9 and the
|
||||
frame procedure (§3.5–§3.10), as the SHA-256 of every frame's `RGBA8P`. The
|
||||
capability is a Draft and is not registered; this corpus precedes its registration.
|
||||
frame procedure (§3.5–§3.10), as the SHA-256 of every frame's `RGBA8P`; and text runs
|
||||
drawn from the in-house outline sets (§4): the default stroke set and the test
|
||||
repertoire. The capability is a Draft and is not registered; this corpus precedes its
|
||||
registration.
|
||||
|
||||
**Provenance.** Generated here by `wai-vector vectors gen` from formulas: every
|
||||
payload, outline set and arithmetic input is integers and text written in this
|
||||
repository, with no media and no font. The outline sets under `sets/` and inside the
|
||||
payloads are drawn from formulas in `wai-vector/src/vectors/sets.rs`, and each set's
|
||||
notes state that it was generated here and is licensed Apache-2.0. All text in the
|
||||
blocks is written here.
|
||||
**Provenance.** Generated here by `wai-vector vectors gen` from formulas and from centre lines drawn in this repository: every payload, outline set, stroke source and arithmetic input is integers and text written here, with no media and no third-party font. The synthetic outline sets are drawn from formulas in `wai-vector/src/vectors/sets.rs` and `raster.rs`. The two in-house sets are drawn here as integer centre lines, the ASCII stroke set in `wai-vector/src/vectors/stroke.rs` and the Arabic, Hebrew and Devanagari test repertoire in `wai-vector/src/vectors/repertoire.rs`; the generator writes them as `WSF1` sources under `sources/` and expands each by Annex A into `sets/stroke-ascii.wos1` and `sets/test-repertoire.wos1`. No outline is traced from any font. Every set's notes state where it comes from and that it is licensed Apache-2.0, and the two in-house sets' notes carry the licence text. All text in the blocks is written here.
|
||||
|
||||
**Equivalence.** A sink conforms on this corpus when, for every payload, it reports the
|
||||
expected file's `code`: the first refusal §2.7 gives, or `accepted`; and for every
|
||||
|
|
@ -27,6 +24,10 @@ parser conforms when it reports §2.1's first code for every `.wos1` vector.
|
|||
|
||||
- `sets/<name>.wos1` — the outline sets the payloads hold as held sets. A held `SET`
|
||||
record finds its set here by SHA-256; a digest none of these files has is not held.
|
||||
Among them: `stroke-ascii.wos1`, the default stroke set, whose SHA-256 is Annex B's;
|
||||
and `test-repertoire.wos1`, the test repertoire (§4.5).
|
||||
- `sources/<name>.wsf1` — the `WSF1` stroke sources of those two sets (Annex A).
|
||||
Expanding a source by Annex A gives its set byte for byte.
|
||||
- `vectors/<group>/<name>.wvg1` — a payload, with its JSON authoring form (§2.12) beside
|
||||
it as `<name>.json`. The form has one member per field, zero fields included, and
|
||||
converts back to the same bytes (`wai-vector pack`).
|
||||
|
|
@ -41,7 +42,8 @@ parser conforms when it reports §2.1's first code for every `.wos1` vector.
|
|||
identity, in reading order), `sizes` (`[w, h, cost]`) and `rates`
|
||||
(`[num, den, P, first frame, last frame]`; the range is empty when the first
|
||||
exceeds the last);
|
||||
- in the groups that render, `frames`: for each listed size and rate, every frame's
|
||||
- in the groups that render (`fills`, `runs`, `composite`, `motion`, `clock`,
|
||||
`slicing` and `text`), `frames`: for each listed size and rate, every frame's
|
||||
`[g, SHA-256 of RGBA8P]` and the clip digest, the SHA-256 of the frames' digests
|
||||
concatenated in order of `g` (SPEC §7, *Digests*); in `composite`, also each
|
||||
frame's `RGBA8S` digest (`rgba8s`);
|
||||
|
|
@ -86,16 +88,26 @@ parser conforms when it reports §2.1's first code for every `.wos1` vector.
|
|||
§3.13.
|
||||
- `runs` — glyphs that abut mid-pixel (no seam within a run, a seam across runs),
|
||||
overlap, carry marks; a run of 4,096 glyphs; one glyph at all 256 subpixel phases.
|
||||
The run set is drawn here (`vectors/raster.rs`) and carried inline.
|
||||
The run set is drawn here (`vectors/raster.rs`) and carried inline. And an Arabic
|
||||
joining sequence from the test repertoire, in one run (its joins over-cover, the
|
||||
knot of §3.13) and split across two (the runs composite by source-over).
|
||||
- `composite` — opacity, isolated groups four deep, clips under both rules, the group
|
||||
pop, and pixels on which the two candidate fill orders differ (§3.9).
|
||||
- `motion` — a credit roll at an integer speed (frame `k` is frame 0 moved, P2) and at
|
||||
a fractional one, a lower third that slides, holds and fades as a group, and each
|
||||
easing mode.
|
||||
easing mode; and a lower third and a short credit roll set in the stroke set.
|
||||
- `clock` — one document at 1280 × 720, 1920 × 1080 and 3840 × 2160, and at 25,
|
||||
30000/1001 and 60 fps.
|
||||
- `slicing` — a document and three pieces cut from it; each piece's frames equal the
|
||||
whole's at the same `g` (P7).
|
||||
- `text` — text runs from the held stroke set and test repertoire, each at two listed
|
||||
sizes: printable ASCII; Arabic right to left, with contextual forms, the lam-alef
|
||||
ligature and two marks; Hebrew, with a final form; Devanagari, with the vowel sign i
|
||||
drawn before its consonant, a mark above and a conjunct; an Arabic block holding a
|
||||
Latin word and digits, its runs from both sets; one block over three runs and lines,
|
||||
with a cluster of no glyphs and an inserted hyphen; a ruby annotation; a vertical-rl
|
||||
block; and U+1F600 drawn by one monochrome glyph. The blocks' projection lands with
|
||||
§5; these vectors pin runs, clusters, blocks and frames.
|
||||
|
||||
## Regenerating and verifying
|
||||
|
||||
|
|
|
|||
1
wai/vector-conformance/expected/motion/stroke-slice.json
Normal file
1
wai/vector-conformance/expected/motion/stroke-slice.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["8166a9197be92a87f3b474fd6cfec0925a355fd19b6e1a5d56dc136d27607c20","a7802bc583a32d0e59dcfbf04b385f3dda345d510a8e91f0103fed7faf3450e6","22607a28bcee502181efb54a8454a8f4b78e3631141273156b79814bcfb1b835","1f7ca8fa84ab49efeaa3cc1076703b5c4377c520cc61d6f2ab65e540e5bbece3","42c60ae8daa74f0e9be2b0ec1c569cc76c44eea3bf78a060f4c2037191a0812a"],"code":"accepted","frames":[{"clip":"efca9b207efa9d9d487552bb9ed6c1e4718fdfba22c56b075988268a76716a8b","den":1,"frames":[[0,"51f9c33fb9f7c65fc675cf9a8f286a95e39d11eed58a9bd52b91da323ac583ea"],[1,"24af64a8b20b655727d5e62c2964474e58ad993a696354d190b8f1a45ca863cd"],[2,"db89480066e6c1cd54ba1048e45c964f476a46aca8dadd2af9a10267c7ec0c09"],[3,"f0e46cbd094e20023dd845ca7416dc4954381319a6587fab862bb5cdc76daa1c"],[4,"e8f05e1feecd24a6365ec7c25152dde7809f1bf1dd51edd63b14d19457af5c4e"],[5,"c8cf3704099bd7d955e781205b9c7e050c9e98ca80549c54713b9ec2fe0e00d7"],[6,"65bfcac71d5184201c42c2e7351ee3cda8c35da9d43864302829bf13462c9103"],[7,"6d244b03beceff5c11e2bdfe0e0ab9c46abfd0af7ecf85eeaf4a02676867a662"],[8,"94f0bf7aeaf7082ebfdb8285ade3d7c6310a93eb799d2cb4081a7bb34c32d659"],[9,"1c5cb976f91ff2b16bedf7e6c11b47cdcf04fc68b60dca09ce4881892a11fd7e"],[10,"82facce1e6ace3c5f2dd3950d0ecf17c26dd5897f849a4a512a73b7a68d15be5"],[11,"4eb8e66960ff2b8dd5bc5a8eafa29a04ce55ce234c3abe45dfd0b6ca4b10b6f6"],[12,"2308d9f1cc973be6e38c90d8a40d400e6098e18ee0e5235857a3c9b64a7ede3a"],[13,"ea1b0c888c5857d4fa136cc8fcdfc05c4b3d0f40e87d39dbae7852d5850abbcb"],[14,"b54e2d5ba0feca12e0fef1330bc1b8799e06bf7e69442405fc0e371084f83b56"],[15,"f0aec5846b8a7ffa9675e569793bf6c66b9dd72b64b6d9487318710f412d761b"],[16,"802a585234f5bf690b0163cb1744c29a5aba2768e5a7b794516603f4359f2ac2"],[17,"2724930371428790b3c67d870e066bbcaf1d4d3237bbbaf0e1e24fec1d342728"],[18,"973db68e7ec16691846999513f76acb5b0b60264cd09c9c3b4f061ed84857747"],[19,"a52d6e5d92df1d2d6363b2f4fd5a679f08f3f6d91dcde2b2472925a818b13a03"],[20,"5cff1ffd3f5afd9f8e2638ffe81d73c3c730e324841720f8312c1e8d2a07ee8f"],[21,"ff529fbffabfe7f369a64552493306c647871c0e92f69502e2105cec9e1820ac"],[22,"0d9d7100639681c499921c3350d94e3c8b430f34134e572da3d944dbe7ff53c4"],[23,"42f1d26e6ba83fbaeaa2c529b0803884676f6d90fa8925c33bdbf7a6412b4724"],[24,"fe17cf005b9b92a9a87615b917cd26ea9445974ad0ac2ea6cad1f85ce415d8d2"],[25,"41bca0991e394f37dbea57547dae054f215f52679e11156f5ba7979d36db8dc3"],[26,"7e48b6b4aa98b1d2c864982a9a02c52e601941feed5d86b5868c3d1a1f96dc18"],[27,"4d4c3bd23afeb979a1cb0034c75bb5bdbbb54b261cd7ea4f3ac39c4c515784d6"],[28,"eddeb7d5d5cc73c4959e678607ca9c41087cf37b3e9915a5eb7a4333b625698d"],[29,"3b7b02f68a0e884802455183e5b7da724d2a8bf7aad600d9a56e1f1d4ff15ed9"],[30,"3e1fca6d53f6ddb3593f15c80f0d9497e5946fec1cb4a0237284f38eb5e6129d"],[31,"d3d830844430aea59a3bc0f2ba0c78eebff23d1176bd97d5c68f11e994dab90a"],[32,"0ce0e9b42a968d189717f0fa70784941d8ea6a80f117ddc1ecd02bd6abdb6e9a"],[33,"ca66ee4fb115569f49639b0e283fc1e8d83aa7d69f15e145a635a0ad42a00ab3"],[34,"68abea21cbcea92374a5d4b535f19e606245b56d263dbf2029c7188b2afba706"],[35,"b9704483f218a364462a753c6a9fa86eeeb31907f9360c6d7b86f571a8d4372d"],[36,"e736441c2e198921e930da342190caac604e89fe8bd78a2fa29e6d841712044b"],[37,"fddfc9d19e1407a3020e81bfa95d6ff44eec9bc5f2558902ccbec7a2be71dbc9"],[38,"678c26a298fac8829d90179f7efdc6e0ad4599f5dbfb780bf16e94887eea669b"],[39,"de36b5bd62944fcc7d0b405d902a13e4b4b97c29774522cea546a8e510addde3"],[40,"297c16b9eb660a49ed5d0f1ae49079842db1f4b7931935688d1472e2e4f3e763"],[41,"4bd50eb0ab2fe83261de4c761ea03c6ebc5baabd7342b41a3da1d19ec82cdf8f"],[42,"9be53e089b51bf5d3ad39942c820a7457e6838f735b5eade03cdb21b19166160"],[43,"7170ce78e75c7b712e4fbf6584ff4f7afb5182580ffe02bb643047020a09456e"],[44,"160a4bc34b110689eae4bf90b9506d5ec4fbbb93e57018825dd3b80f6e169c16"],[45,"6fcaac14edaef1282473e0fd61f4a142794e0a6eb47bcc94eb5c53834a43edae"],[46,"6cbd88fb80d87b14e384b2ca5dba058bba8e305ad848100abb50c3784f3771e6"],[47,"f8da02c0c14dd4fadd511f9ff14d198fb3214c3cf1b8613a5e96b5c8f00390aa"],[48,"75b7c82c389fa10aff769a103fef2dc963edb6583ac7d40ddb532c5b1f73f81a"],[49,"16898aeb46ad07fb8974a4bff2c476de5df3ad85c90617bcdfa838b6685117c4"]],"h":180,"num":25,"w":320}],"instr":"42d82a584a14fb454f497155849ab6cd482fb5f53e572e8d527953808263edde","rates":[[25,1,4800,0,49]],"sizes":[[320,180,16863033]],"text_hash":"573b489ff1943d4ead50cd8ca74011a52ea4a4546e835ea8c0baf0f9a9ee7fca"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["a757954c6cca3876765c556e0b6548164c4421f220af1a52a8e1070882cf6e5e"],"code":"accepted","frames":[{"clip":"4bce3a67a81481a01b29f1d930cb08a41260dcc5fd8df07a9a2e6deec5bcf55b","den":1,"frames":[[0,"6b605b7066f21ddcfc5545748a575c1158de85a39bb1d31bfb094d73bbdc20ea"]],"h":48,"num":25,"w":96}],"instr":"892dc5c57c58c53e45a20a973dda9e66ebaa4a2b34c9e0947d0eb0ab264f47d5","rates":[[25,1,4800,0,0]],"sizes":[[96,48,718113]],"text_hash":"d9b1b13fa7eb857adc66777ca431c6c5fc4e2f73481b0256faddde4dea1bb119"}
|
||||
1
wai/vector-conformance/expected/runs/arabic-joins.json
Normal file
1
wai/vector-conformance/expected/runs/arabic-joins.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["a757954c6cca3876765c556e0b6548164c4421f220af1a52a8e1070882cf6e5e"],"code":"accepted","frames":[{"clip":"10583262cf57dc99cf295a5ee9211922618353fa41268d5732ab3b063a7599a4","den":1,"frames":[[0,"828d00fc87f087778556d6ed5de3fa8a663aca6ab8455171f6fde0b32a64aff9"]],"h":48,"num":25,"w":96}],"instr":"c7dc3d22c6760632f8c97c6e0832a03337a39becfca8f306fa2292ed4f1c9826","rates":[[25,1,4800,0,0]],"sizes":[[96,48,718535]],"text_hash":"d9b1b13fa7eb857adc66777ca431c6c5fc4e2f73481b0256faddde4dea1bb119"}
|
||||
1
wai/vector-conformance/expected/text/arabic.json
Normal file
1
wai/vector-conformance/expected/text/arabic.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["a1ac5064cd79c3326dede6e01d1ec6c694ab870bc43fd6373945023777fa900d"],"code":"accepted","frames":[{"clip":"b6faa8405f77ad6006d55a270b243e27c43be96c5af6bb3e79d8cf92c37da458","den":1,"frames":[[0,"e33d9768bca8769dee48af7fce73e8bab3c95644b2d18c797c3d8ccec098dca9"]],"h":64,"num":25,"w":160},{"clip":"9a3aa42017dcc2bd42ac3c689c9e1202c5e0deedc08412eebd22544f4b416a06","den":1,"frames":[[0,"e031862eb79ebb800f479f9a616554729c5fc03e9ab8c27b219f79c454f35bd8"]],"h":128,"num":25,"w":320}],"instr":"e2bec5bcae46fe9117c9c0a954340d05bb052dc09f0fe6db0ba7d8f9c3c00dce","rates":[[25,1,4800,0,0]],"sizes":[[160,64,1822464],[320,128,1923747]],"text_hash":"8c94168b3435c74649178864574423da358f59e6a84134622e57ebb7651e20a0"}
|
||||
1
wai/vector-conformance/expected/text/devanagari.json
Normal file
1
wai/vector-conformance/expected/text/devanagari.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["4f6ea9772cb64faeca1a8200e629d0640490bda90e68f6e25ebf8903eb14bb94"],"code":"accepted","frames":[{"clip":"05c2577e300c87d05547cec85b2bee68e9729e75d918e340e06cabcc4c3002f2","den":1,"frames":[[0,"de7b8a4d77e1ed7f6b1e653d4166ef817f41f2de229122f6014b61a16bf813bb"]],"h":48,"num":25,"w":128},{"clip":"0191405f20ddcfa808bc147688f0d8d40c59acb05dccf4d49eced0e54fd7e9ba","den":1,"frames":[[0,"64a8e694fb523303cb780cbe91bcfb9aa8c92403719a0d9fcddfaf86a9d12f10"]],"h":96,"num":25,"w":256}],"instr":"ee7d172201a0df5f21bdebc0716cee11d376da5890f7b4c073170535a9be62d1","rates":[[25,1,4800,0,0]],"sizes":[[128,48,2409572],[256,96,2538475]],"text_hash":"8bf4aff8247d493288c89751a2b6bc133346d6416568df89eaeb7b8c3e384974"}
|
||||
1
wai/vector-conformance/expected/text/emoji.json
Normal file
1
wai/vector-conformance/expected/text/emoji.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["21b751ed648ba10c6594afca8701e14dcc8f19880662f3a571f4da32ae581677"],"code":"accepted","frames":[{"clip":"f8f901be288a067da15e32b5374eb4b6961268e7ec44827fc0e1efcf2bb835e0","den":1,"frames":[[0,"db02e147c6aabbb4fb2a37b88f1f0e04c92274929d7899bf39d813701056bb33"]],"h":40,"num":25,"w":80},{"clip":"1a6a95ea77ed01e8ab5a17e8bbc05646d3d30a8cf6935eba8165d5dad1c810aa","den":1,"frames":[[0,"7971ccd3260c96e3e1ad4ad97956af04a6c84b9b16fa660b3aa0a4ea2f3b4a21"]],"h":80,"num":25,"w":160}],"instr":"409258a329f96665f3fb6f4ac25246e44e0bd3627d875365626c8cb90e6b0667","rates":[[25,1,4800,0,0]],"sizes":[[80,40,1001617],[160,80,1046609]],"text_hash":"1838dafc24fd5742f240119096c27e94a7db2933b0ac5b16dfeaee45deb9dbfa"}
|
||||
1
wai/vector-conformance/expected/text/hebrew.json
Normal file
1
wai/vector-conformance/expected/text/hebrew.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["722c8daf25a3241f335fb4286ecdce0ab9363527bdd554d42ac0c07203f52066"],"code":"accepted","frames":[{"clip":"190c3cdd7a0106ee114bcf8af5c25a9246a7e672a580a42c279b33965c2bedf4","den":1,"frames":[[0,"7f1c9b3e8ec66c3c0e3caffbe5bf67a0c07d3ad12cbcc60533fbbcbbb9b875da"]],"h":48,"num":25,"w":128},{"clip":"09df67a0972e81f725138d0cf1080f894f9521139440dfa3e4e28a685322ff9a","den":1,"frames":[[0,"10c53c9b1f51c49ee1571c08cebade11e9111d36b331920725b3eaabe8e3b00a"]],"h":96,"num":25,"w":256}],"instr":"35819682ffa104f8255ec1d94704a904ee9020596eaf4bf4d41ad15ede1fe150","rates":[[25,1,4800,0,0]],"sizes":[[128,48,533026],[256,96,575728]],"text_hash":"4b411db5af2ae10ef53ec133e8f606736bf52de5ea1e8c0ee82905262e9e324d"}
|
||||
1
wai/vector-conformance/expected/text/latin-ascii.json
Normal file
1
wai/vector-conformance/expected/text/latin-ascii.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["0a9f4b5d043f8433fa1bdcf9a7c364130caf6264fb47e0b3aa47cb9ad51709e4"],"code":"accepted","frames":[{"clip":"ce815393b148dae5dd44d50b731049d29c6895440f460c95a3a0f5f3f4a0cb3b","den":1,"frames":[[0,"dbcf4bc1b464c3acc88a0e7df30a03ce82d84e477e6fdeb9d8c790caa54ad570"]],"h":96,"num":25,"w":320},{"clip":"577bb19242156d0e2fe41bc6fb89f211cb875d71a7cd4f0d8f8dc87484421a28","den":1,"frames":[[0,"ba471588b8d826ca431dcfd36e7478e907ada1c4a57d14dad7a78233f2277e76"]],"h":192,"num":25,"w":640}],"instr":"f02dc158b0eb14ee9a3875132896c7c1cc5942ac8b71503e86d648b463f3e9ec","rates":[[25,1,4800,0,0]],"sizes":[[320,96,19235383],[640,192,19733375]],"text_hash":"e71f985f2c86cbaa9da7aa055e0a2f0ffba40b742875d87434bc8b24a7eb0d05"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["723651bdcd9ffacf349cc16afdba5c23242e275538a9a2d561ee291363d9d4fb"],"code":"accepted","frames":[{"clip":"93b1cee5a681d86ba49324b72f931a8f291e7b29e76126ff17c72d1ea8dfb38e","den":1,"frames":[[0,"5ed5b87c38b975b0714f4decf7214c264482f3914345370154fd457f12bede90"]],"h":48,"num":25,"w":224},{"clip":"a3a9cb32b42b5b14e19e4615a6c6fd1aaf817faffb8d1b6c20c0aa0d5b7f6989","den":1,"frames":[[0,"108753723c365bd3c9a997c5cb6e749be31ebb43640a201667fff0a831207442"]],"h":96,"num":25,"w":448}],"instr":"9585340e1aacba74a30fa8625042cbaa7e6cc3683549aa8fae7a01732bc46bfe","rates":[[25,1,4800,0,0]],"sizes":[[224,48,2301369],[448,96,2437421]],"text_hash":"179f28de48d5faea78fd52a432293517adfc39880257fa60fcdb0df966bfea9a"}
|
||||
1
wai/vector-conformance/expected/text/ruby.json
Normal file
1
wai/vector-conformance/expected/text/ruby.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["3fa00b9bbe6f4ce44ff3efa35063be19619e6a9d50491980625dab76495506ef","41597c7d8ebc421391be0391527a59f1ec4b450bcada03db3c699d1551611256"],"code":"accepted","frames":[{"clip":"85268003852f5e1e1be5a327762419452a60c7a5855ed26739520b4b90895dd9","den":1,"frames":[[0,"6d12cec6949e39ef8931d2e5c27370f54ca0965c828b1e97c04c52df7cf97441"]],"h":56,"num":25,"w":80},{"clip":"8ee93c3784dd8b6334eb26e0a815aad5caf5f3887bb74c47eaf302a84bdc5b1e","den":1,"frames":[[0,"d15d75d75ca2ed764bd30e988803fcf31ee9dbd0bde330412d303939e98e8efa"]],"h":112,"num":25,"w":160}],"instr":"e596f338add71735af28eb432cecf6aaa0abdf37f6a7a020fa24ae4303ec83ad","rates":[[25,1,4800,0,0]],"sizes":[[80,56,2260523],[160,112,2339549]],"text_hash":"0d1bb801ca26584e5c64aa7be45785b77f9892f1b40d015d87f5d5c48444235d"}
|
||||
1
wai/vector-conformance/expected/text/split-lines.json
Normal file
1
wai/vector-conformance/expected/text/split-lines.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["16343f1f036bfa43f6aad1e946f8823155ea0dfd1fabcc25ac18f5987f700345"],"code":"accepted","frames":[{"clip":"10df3daaa6212dc347420f29f4316c9c5058a7ab4b32c6507cd4694461f4de4d","den":1,"frames":[[0,"73d7dd6ead9dc7c14e7a9324c5a7150fb68590a5cd52919dd3651aa6dbe5d470"]],"h":96,"num":25,"w":208},{"clip":"8d364cff370d46b17c2b3fd9d101aaa130272c271ab3e2dd205b8d9eaac98c2a","den":1,"frames":[[0,"f31e767207c61d1b5c3839667d684d7894d63d61dc8a0dc49246e55381fc3e65"]],"h":192,"num":25,"w":416}],"instr":"2782f335e8aac2308c789402ebb19f4025c125ebce9ff460ab26e5c90d3ca8d0","rates":[[25,1,4800,0,0]],"sizes":[[208,96,8058342],[416,192,8346462]],"text_hash":"5765fb1528390e801985580a1ea1cce579549efd67dabc6a0b26ef2c0256ffd9"}
|
||||
1
wai/vector-conformance/expected/text/vertical-rl.json
Normal file
1
wai/vector-conformance/expected/text/vertical-rl.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"blocks":["47d74c9ccb8456e3f1171904dda15a84bf74897c9bcc12d9f4fef166730e8c1a"],"code":"accepted","frames":[{"clip":"1d559c2c4ffafa1e45cf5bd5651df602340cc8290607c49a82d7ffe929b23ed4","den":1,"frames":[[0,"d9b43774221ccc5c9d677d6a6d125fd772e95487cfdcae1a308b648fb4c6bd41"]],"h":96,"num":25,"w":48},{"clip":"2bb95d63e1008192572d721190163e09b92ed883d0b9633901c9dde608b1274d","den":1,"frames":[[0,"00e04f0f25e422f25d236f7c46b56b348e9ace34ae048675a8b96d50ecf1cfcf"]],"h":192,"num":25,"w":96}],"instr":"f6577d8e21273c47799a339501620e5484de4770abf1a981513ca2754f05e459","rates":[[25,1,4800,0,0]],"sizes":[[48,96,420615],[96,192,460590]],"text_hash":"c6bd18445aadbee0afa6756365837f765cf2e2da7d9ef2a9eb2caff4d5c76774"}
|
||||
BIN
wai/vector-conformance/sets/stroke-ascii.wos1
Normal file
BIN
wai/vector-conformance/sets/stroke-ascii.wos1
Normal file
Binary file not shown.
BIN
wai/vector-conformance/sets/test-repertoire.wos1
Normal file
BIN
wai/vector-conformance/sets/test-repertoire.wos1
Normal file
Binary file not shown.
|
|
@ -64,5 +64,26 @@
|
|||
"slicing/piece-1@160x90@25/1":{"buffer":"RGBA8P, 160 x 90, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 10..=19 at 25/1 fps, in order","frames_sha256":["cff9d4a7f232431f658ede7ba41e6e7d7a962e95d33196157319e772d79c6a58","a06b510d6d22c4a092d79f7c86dfa672ad3330e0c266ed2ce7d83a4226cc706b","fe873cb1801a22a26a96a228f29d0ec2145d73ea75c6f9a1834c8c4e1ef97162","1fbcfd349eb22d8f2711d1a2e9982c1a4603cbdd612f2c8731f7c6b38d13491b","a1ab5ef6bafc069cfa4c3e09614973bd84cb8791630747446186a83833f0f975","597c55503577383c39f9a112c0551870d41a5513019f5819d7bd3ce3d9b41cc9","36c9c48db79eb966ee655673e6ffbc147e10401e0da23b58b26f60fcb581e615","660a1b0ea9ee6158541308d1273e21633f5ae80ec913e3b790a432ee03013935","08f1656af9bf118bac16beb1adaf8ba0a14f97a1b1be128502b69391713da1d0","99ff547ab12fa297fa462a6560113930993561bb0a3ef0c45de3beb151eb0815"],"clip_sha256":"c117bf5a0e203402a102b82f9e34355021f784052f2708842149ce7c214459af"},
|
||||
"slicing/piece-1@160x90@50/1":{"buffer":"RGBA8P, 160 x 90, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 19..=39 at 50/1 fps, in order","frames_sha256":["92665c212f38919a3f0adb9dd0e442c0d023dcce08e2e1f9c512625507a87496","cff9d4a7f232431f658ede7ba41e6e7d7a962e95d33196157319e772d79c6a58","34c9f854f3f1e36ad7b88e5d236a077cf501e760ba1b13925c32917a8fa5b57d","a06b510d6d22c4a092d79f7c86dfa672ad3330e0c266ed2ce7d83a4226cc706b","d8cd9cc140ffb17e5b9b4394582fe96e82a6f0945e721f71610b51407375cef5","fe873cb1801a22a26a96a228f29d0ec2145d73ea75c6f9a1834c8c4e1ef97162","e693e08a7e3340afd8cca435544f8c64f071762294d8f0abd2119d34985085ba","1fbcfd349eb22d8f2711d1a2e9982c1a4603cbdd612f2c8731f7c6b38d13491b","cf0538db2a0814a82b4e6f4df750e79ef427f7e657ca2d63b7079703fb591239","a1ab5ef6bafc069cfa4c3e09614973bd84cb8791630747446186a83833f0f975","4077ad2675c6945f5e4939016ada436ac76af80c0f4ef49a6cf7aa78cea9459b","597c55503577383c39f9a112c0551870d41a5513019f5819d7bd3ce3d9b41cc9","5753d08d1331f360c8514a8f234a7f8190a0d829ccd19047b87dee326eb7bd4a","36c9c48db79eb966ee655673e6ffbc147e10401e0da23b58b26f60fcb581e615","13c4ad9a85773343544bb8799e4bcadec0c416140af4e9f9b90422b2655847ac","660a1b0ea9ee6158541308d1273e21633f5ae80ec913e3b790a432ee03013935","8f469aa36ca2441536043db4cd8bde002c780faa36754e752c6d147979959cc2","08f1656af9bf118bac16beb1adaf8ba0a14f97a1b1be128502b69391713da1d0","226655d98dbfcfeab2fe31585eb5cf246c0b39e7d34a13ae4104260a04638c33","99ff547ab12fa297fa462a6560113930993561bb0a3ef0c45de3beb151eb0815","4a9774446434d3519797705048eb14fb8ac5a376e743546aedcc2bde68a59f78"],"clip_sha256":"267c1ddeb2083d89d312636f424a09fbb5e22a1f056858698abb3d28c4cebc64"},
|
||||
"slicing/piece-2@160x90@25/1":{"buffer":"RGBA8P, 160 x 90, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 20..=29 at 25/1 fps, in order","frames_sha256":["dc936081635c336b1d98af10b3e464920bbc3f2b69b598ca17d66c792247a9a6","0f168fdbb3542b9253caa39db08269bd04b88133239b3a117e7a294adcd95d31","c7aba33e68242a7d2e6e0c9e9df5c36c010c53ce8a61e0160e4e32348bcc12cc","a46035af5b9be6142e8f812bd7e6e3f3654542d94480b88ed6fcea1acd4233c5","c3732c4a2ee679edf6166f4044f11819045ca07e0f939bb593f69c1131a93ab8","f4b9f19ea2d4b2c2ee43a5a70f22db3aaa15201039705c27ecc42d7d5678262e","4b00dd134d8c7813add8877d05bd5fda02f418d743a11729e51396a44fd0b7d0","a0788a54e75c196b6c077eaa21b849c97c72a185ce480a723d65bd5e9351d6ec","166bd0ff3c60697ee4c7b1ecee95b2ec720013abc792aeab5cbc08e576f4d5c9","ce02be1012d531d1f886eae303cb966f87d53a5d5eaa10ee606999c54b491346"],"clip_sha256":"a864326ae16ebe027bffd2b038b03dca61d23b0cd8cb768662cbb528b7bb501e"},
|
||||
"slicing/piece-2@160x90@50/1":{"buffer":"RGBA8P, 160 x 90, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 40..=59 at 50/1 fps, in order","frames_sha256":["dc936081635c336b1d98af10b3e464920bbc3f2b69b598ca17d66c792247a9a6","f5ce782f09c9fcfba17ca0a1136cf666dbf1ae0fd393fe4f13d6fb1dace470a1","0f168fdbb3542b9253caa39db08269bd04b88133239b3a117e7a294adcd95d31","3889e80648c5c8441041b5150c113211065445c10de2006b6154ef2d42b9ef8e","c7aba33e68242a7d2e6e0c9e9df5c36c010c53ce8a61e0160e4e32348bcc12cc","dbcd989b79b33f889e7863feb738852428c5beb0dafa7949ccce5081a334a342","a46035af5b9be6142e8f812bd7e6e3f3654542d94480b88ed6fcea1acd4233c5","cfc7b9cf3820367628247fe6b3af03362b9e7a76ffe24acac0b9cd78eb477b99","c3732c4a2ee679edf6166f4044f11819045ca07e0f939bb593f69c1131a93ab8","78751cd438b6e16c1fa1cd24fb38c9a3405a132478961decf44777c4bedeb9ad","f4b9f19ea2d4b2c2ee43a5a70f22db3aaa15201039705c27ecc42d7d5678262e","e510f57da223c3aaa84b01fb0eb734d26631d1eb24ae633e942694e28d41caba","4b00dd134d8c7813add8877d05bd5fda02f418d743a11729e51396a44fd0b7d0","23601909304027af52a58b31c8ea17ac585bb79474fabb8af3269b598efadcd1","a0788a54e75c196b6c077eaa21b849c97c72a185ce480a723d65bd5e9351d6ec","b86a51bb3707dcb5c9f5c46c1c6873279b0232b2c246cac794e89546169ad246","166bd0ff3c60697ee4c7b1ecee95b2ec720013abc792aeab5cbc08e576f4d5c9","03b45d692c0ab04e55ca7a240380ceafe6e923336920458112973054b5f2e0bf","ce02be1012d531d1f886eae303cb966f87d53a5d5eaa10ee606999c54b491346","42c5a35c3cda08f0a44c87f43164a0819f66e9148308b397ad0ba4ba8a4b348e"],"clip_sha256":"d43f7d4a8c5088834130ad0cee2e09b28029a99e7e42ad84987ca05d2d0361da"}
|
||||
"slicing/piece-2@160x90@50/1":{"buffer":"RGBA8P, 160 x 90, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 40..=59 at 50/1 fps, in order","frames_sha256":["dc936081635c336b1d98af10b3e464920bbc3f2b69b598ca17d66c792247a9a6","f5ce782f09c9fcfba17ca0a1136cf666dbf1ae0fd393fe4f13d6fb1dace470a1","0f168fdbb3542b9253caa39db08269bd04b88133239b3a117e7a294adcd95d31","3889e80648c5c8441041b5150c113211065445c10de2006b6154ef2d42b9ef8e","c7aba33e68242a7d2e6e0c9e9df5c36c010c53ce8a61e0160e4e32348bcc12cc","dbcd989b79b33f889e7863feb738852428c5beb0dafa7949ccce5081a334a342","a46035af5b9be6142e8f812bd7e6e3f3654542d94480b88ed6fcea1acd4233c5","cfc7b9cf3820367628247fe6b3af03362b9e7a76ffe24acac0b9cd78eb477b99","c3732c4a2ee679edf6166f4044f11819045ca07e0f939bb593f69c1131a93ab8","78751cd438b6e16c1fa1cd24fb38c9a3405a132478961decf44777c4bedeb9ad","f4b9f19ea2d4b2c2ee43a5a70f22db3aaa15201039705c27ecc42d7d5678262e","e510f57da223c3aaa84b01fb0eb734d26631d1eb24ae633e942694e28d41caba","4b00dd134d8c7813add8877d05bd5fda02f418d743a11729e51396a44fd0b7d0","23601909304027af52a58b31c8ea17ac585bb79474fabb8af3269b598efadcd1","a0788a54e75c196b6c077eaa21b849c97c72a185ce480a723d65bd5e9351d6ec","b86a51bb3707dcb5c9f5c46c1c6873279b0232b2c246cac794e89546169ad246","166bd0ff3c60697ee4c7b1ecee95b2ec720013abc792aeab5cbc08e576f4d5c9","03b45d692c0ab04e55ca7a240380ceafe6e923336920458112973054b5f2e0bf","ce02be1012d531d1f886eae303cb966f87d53a5d5eaa10ee606999c54b491346","42c5a35c3cda08f0a44c87f43164a0819f66e9148308b397ad0ba4ba8a4b348e"],"clip_sha256":"d43f7d4a8c5088834130ad0cee2e09b28029a99e7e42ad84987ca05d2d0361da"},
|
||||
"text/latin-ascii@320x96@25/1":{"buffer":"RGBA8P, 320 x 96, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["dbcf4bc1b464c3acc88a0e7df30a03ce82d84e477e6fdeb9d8c790caa54ad570"],"clip_sha256":"ce815393b148dae5dd44d50b731049d29c6895440f460c95a3a0f5f3f4a0cb3b"},
|
||||
"text/latin-ascii@640x192@25/1":{"buffer":"RGBA8P, 640 x 192, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["ba471588b8d826ca431dcfd36e7478e907ada1c4a57d14dad7a78233f2277e76"],"clip_sha256":"577bb19242156d0e2fe41bc6fb89f211cb875d71a7cd4f0d8f8dc87484421a28"},
|
||||
"text/arabic@160x64@25/1":{"buffer":"RGBA8P, 160 x 64, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["e33d9768bca8769dee48af7fce73e8bab3c95644b2d18c797c3d8ccec098dca9"],"clip_sha256":"b6faa8405f77ad6006d55a270b243e27c43be96c5af6bb3e79d8cf92c37da458"},
|
||||
"text/arabic@320x128@25/1":{"buffer":"RGBA8P, 320 x 128, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["e031862eb79ebb800f479f9a616554729c5fc03e9ab8c27b219f79c454f35bd8"],"clip_sha256":"9a3aa42017dcc2bd42ac3c689c9e1202c5e0deedc08412eebd22544f4b416a06"},
|
||||
"text/hebrew@128x48@25/1":{"buffer":"RGBA8P, 128 x 48, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["7f1c9b3e8ec66c3c0e3caffbe5bf67a0c07d3ad12cbcc60533fbbcbbb9b875da"],"clip_sha256":"190c3cdd7a0106ee114bcf8af5c25a9246a7e672a580a42c279b33965c2bedf4"},
|
||||
"text/hebrew@256x96@25/1":{"buffer":"RGBA8P, 256 x 96, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["10c53c9b1f51c49ee1571c08cebade11e9111d36b331920725b3eaabe8e3b00a"],"clip_sha256":"09df67a0972e81f725138d0cf1080f894f9521139440dfa3e4e28a685322ff9a"},
|
||||
"text/devanagari@128x48@25/1":{"buffer":"RGBA8P, 128 x 48, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["de7b8a4d77e1ed7f6b1e653d4166ef817f41f2de229122f6014b61a16bf813bb"],"clip_sha256":"05c2577e300c87d05547cec85b2bee68e9729e75d918e340e06cabcc4c3002f2"},
|
||||
"text/devanagari@256x96@25/1":{"buffer":"RGBA8P, 256 x 96, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["64a8e694fb523303cb780cbe91bcfb9aa8c92403719a0d9fcddfaf86a9d12f10"],"clip_sha256":"0191405f20ddcfa808bc147688f0d8d40c59acb05dccf4d49eced0e54fd7e9ba"},
|
||||
"text/mixed-direction@224x48@25/1":{"buffer":"RGBA8P, 224 x 48, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["5ed5b87c38b975b0714f4decf7214c264482f3914345370154fd457f12bede90"],"clip_sha256":"93b1cee5a681d86ba49324b72f931a8f291e7b29e76126ff17c72d1ea8dfb38e"},
|
||||
"text/mixed-direction@448x96@25/1":{"buffer":"RGBA8P, 448 x 96, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["108753723c365bd3c9a997c5cb6e749be31ebb43640a201667fff0a831207442"],"clip_sha256":"a3a9cb32b42b5b14e19e4615a6c6fd1aaf817faffb8d1b6c20c0aa0d5b7f6989"},
|
||||
"text/split-lines@208x96@25/1":{"buffer":"RGBA8P, 208 x 96, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["73d7dd6ead9dc7c14e7a9324c5a7150fb68590a5cd52919dd3651aa6dbe5d470"],"clip_sha256":"10df3daaa6212dc347420f29f4316c9c5058a7ab4b32c6507cd4694461f4de4d"},
|
||||
"text/split-lines@416x192@25/1":{"buffer":"RGBA8P, 416 x 192, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["f31e767207c61d1b5c3839667d684d7894d63d61dc8a0dc49246e55381fc3e65"],"clip_sha256":"8d364cff370d46b17c2b3fd9d101aaa130272c271ab3e2dd205b8d9eaac98c2a"},
|
||||
"text/ruby@80x56@25/1":{"buffer":"RGBA8P, 80 x 56, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["6d12cec6949e39ef8931d2e5c27370f54ca0965c828b1e97c04c52df7cf97441"],"clip_sha256":"85268003852f5e1e1be5a327762419452a60c7a5855ed26739520b4b90895dd9"},
|
||||
"text/ruby@160x112@25/1":{"buffer":"RGBA8P, 160 x 112, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["d15d75d75ca2ed764bd30e988803fcf31ee9dbd0bde330412d303939e98e8efa"],"clip_sha256":"8ee93c3784dd8b6334eb26e0a815aad5caf5f3887bb74c47eaf302a84bdc5b1e"},
|
||||
"text/vertical-rl@48x96@25/1":{"buffer":"RGBA8P, 48 x 96, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["d9b43774221ccc5c9d677d6a6d125fd772e95487cfdcae1a308b648fb4c6bd41"],"clip_sha256":"1d559c2c4ffafa1e45cf5bd5651df602340cc8290607c49a82d7ffe929b23ed4"},
|
||||
"text/vertical-rl@96x192@25/1":{"buffer":"RGBA8P, 96 x 192, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["00e04f0f25e422f25d236f7c46b56b348e9ace34ae048675a8b96d50ecf1cfcf"],"clip_sha256":"2bb95d63e1008192572d721190163e09b92ed883d0b9633901c9dde608b1274d"},
|
||||
"text/emoji@80x40@25/1":{"buffer":"RGBA8P, 80 x 40, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["db02e147c6aabbb4fb2a37b88f1f0e04c92274929d7899bf39d813701056bb33"],"clip_sha256":"f8f901be288a067da15e32b5374eb4b6961268e7ec44827fc0e1efcf2bb835e0"},
|
||||
"text/emoji@160x80@25/1":{"buffer":"RGBA8P, 160 x 80, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["7971ccd3260c96e3e1ad4ad97956af04a6c84b9b16fa660b3aa0a4ea2f3b4a21"],"clip_sha256":"1a6a95ea77ed01e8ab5a17e8bbc05646d3d30a8cf6935eba8165d5dad1c810aa"},
|
||||
"runs/arabic-joins@96x48@25/1":{"buffer":"RGBA8P, 96 x 48, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["828d00fc87f087778556d6ed5de3fa8a663aca6ab8455171f6fde0b32a64aff9"],"clip_sha256":"10583262cf57dc99cf295a5ee9211922618353fa41268d5732ab3b063a7599a4"},
|
||||
"runs/arabic-joins-split@96x48@25/1":{"buffer":"RGBA8P, 96 x 48, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=0 at 25/1 fps, in order","frames_sha256":["6b605b7066f21ddcfc5545748a575c1158de85a39bb1d31bfb094d73bbdc20ea"],"clip_sha256":"4bce3a67a81481a01b29f1d930cb08a41260dcc5fd8df07a9a2e6deec5bcf55b"},
|
||||
"motion/stroke-slice@320x180@25/1":{"buffer":"RGBA8P, 320 x 180, row-major, premultiplied, sRGB-encoded, bytes R G B A; frames g = 0..=49 at 25/1 fps, in order","frames_sha256":["51f9c33fb9f7c65fc675cf9a8f286a95e39d11eed58a9bd52b91da323ac583ea","24af64a8b20b655727d5e62c2964474e58ad993a696354d190b8f1a45ca863cd","db89480066e6c1cd54ba1048e45c964f476a46aca8dadd2af9a10267c7ec0c09","f0e46cbd094e20023dd845ca7416dc4954381319a6587fab862bb5cdc76daa1c","e8f05e1feecd24a6365ec7c25152dde7809f1bf1dd51edd63b14d19457af5c4e","c8cf3704099bd7d955e781205b9c7e050c9e98ca80549c54713b9ec2fe0e00d7","65bfcac71d5184201c42c2e7351ee3cda8c35da9d43864302829bf13462c9103","6d244b03beceff5c11e2bdfe0e0ab9c46abfd0af7ecf85eeaf4a02676867a662","94f0bf7aeaf7082ebfdb8285ade3d7c6310a93eb799d2cb4081a7bb34c32d659","1c5cb976f91ff2b16bedf7e6c11b47cdcf04fc68b60dca09ce4881892a11fd7e","82facce1e6ace3c5f2dd3950d0ecf17c26dd5897f849a4a512a73b7a68d15be5","4eb8e66960ff2b8dd5bc5a8eafa29a04ce55ce234c3abe45dfd0b6ca4b10b6f6","2308d9f1cc973be6e38c90d8a40d400e6098e18ee0e5235857a3c9b64a7ede3a","ea1b0c888c5857d4fa136cc8fcdfc05c4b3d0f40e87d39dbae7852d5850abbcb","b54e2d5ba0feca12e0fef1330bc1b8799e06bf7e69442405fc0e371084f83b56","f0aec5846b8a7ffa9675e569793bf6c66b9dd72b64b6d9487318710f412d761b","802a585234f5bf690b0163cb1744c29a5aba2768e5a7b794516603f4359f2ac2","2724930371428790b3c67d870e066bbcaf1d4d3237bbbaf0e1e24fec1d342728","973db68e7ec16691846999513f76acb5b0b60264cd09c9c3b4f061ed84857747","a52d6e5d92df1d2d6363b2f4fd5a679f08f3f6d91dcde2b2472925a818b13a03","5cff1ffd3f5afd9f8e2638ffe81d73c3c730e324841720f8312c1e8d2a07ee8f","ff529fbffabfe7f369a64552493306c647871c0e92f69502e2105cec9e1820ac","0d9d7100639681c499921c3350d94e3c8b430f34134e572da3d944dbe7ff53c4","42f1d26e6ba83fbaeaa2c529b0803884676f6d90fa8925c33bdbf7a6412b4724","fe17cf005b9b92a9a87615b917cd26ea9445974ad0ac2ea6cad1f85ce415d8d2","41bca0991e394f37dbea57547dae054f215f52679e11156f5ba7979d36db8dc3","7e48b6b4aa98b1d2c864982a9a02c52e601941feed5d86b5868c3d1a1f96dc18","4d4c3bd23afeb979a1cb0034c75bb5bdbbb54b261cd7ea4f3ac39c4c515784d6","eddeb7d5d5cc73c4959e678607ca9c41087cf37b3e9915a5eb7a4333b625698d","3b7b02f68a0e884802455183e5b7da724d2a8bf7aad600d9a56e1f1d4ff15ed9","3e1fca6d53f6ddb3593f15c80f0d9497e5946fec1cb4a0237284f38eb5e6129d","d3d830844430aea59a3bc0f2ba0c78eebff23d1176bd97d5c68f11e994dab90a","0ce0e9b42a968d189717f0fa70784941d8ea6a80f117ddc1ecd02bd6abdb6e9a","ca66ee4fb115569f49639b0e283fc1e8d83aa7d69f15e145a635a0ad42a00ab3","68abea21cbcea92374a5d4b535f19e606245b56d263dbf2029c7188b2afba706","b9704483f218a364462a753c6a9fa86eeeb31907f9360c6d7b86f571a8d4372d","e736441c2e198921e930da342190caac604e89fe8bd78a2fa29e6d841712044b","fddfc9d19e1407a3020e81bfa95d6ff44eec9bc5f2558902ccbec7a2be71dbc9","678c26a298fac8829d90179f7efdc6e0ad4599f5dbfb780bf16e94887eea669b","de36b5bd62944fcc7d0b405d902a13e4b4b97c29774522cea546a8e510addde3","297c16b9eb660a49ed5d0f1ae49079842db1f4b7931935688d1472e2e4f3e763","4bd50eb0ab2fe83261de4c761ea03c6ebc5baabd7342b41a3da1d19ec82cdf8f","9be53e089b51bf5d3ad39942c820a7457e6838f735b5eade03cdb21b19166160","7170ce78e75c7b712e4fbf6584ff4f7afb5182580ffe02bb643047020a09456e","160a4bc34b110689eae4bf90b9506d5ec4fbbb93e57018825dd3b80f6e169c16","6fcaac14edaef1282473e0fd61f4a142794e0a6eb47bcc94eb5c53834a43edae","6cbd88fb80d87b14e384b2ca5dba058bba8e305ad848100abb50c3784f3771e6","f8da02c0c14dd4fadd511f9ff14d198fb3214c3cf1b8613a5e96b5c8f00390aa","75b7c82c389fa10aff769a103fef2dc963edb6583ac7d40ddb532c5b1f73f81a","16898aeb46ad07fb8974a4bff2c476de5df3ad85c90617bcdfa838b6685117c4"],"clip_sha256":"efca9b207efa9d9d487552bb9ed6c1e4718fdfba22c56b075988268a76716a8b"}
|
||||
}
|
||||
|
|
|
|||
BIN
wai/vector-conformance/sources/stroke-ascii.wsf1
Normal file
BIN
wai/vector-conformance/sources/stroke-ascii.wsf1
Normal file
Binary file not shown.
BIN
wai/vector-conformance/sources/test-repertoire.wsf1
Normal file
BIN
wai/vector-conformance/sources/test-repertoire.wsf1
Normal file
Binary file not shown.
|
|
@ -2,5 +2,6 @@
|
|||
"easing":"one item per easing mode: hold, linear, a monotone table and a table that is not: accepted",
|
||||
"lower-third":"a lower third that slides in on an easing table, holds and fades out, as one group: accepted",
|
||||
"roll-fractional":"the same roll at 1.5 design pixels per frame: no whole-pixel shift between frames: accepted",
|
||||
"roll-integer":"a credit roll at 2 design pixels per frame, keys on frame ticks: frame k is frame 0 moved 2k pixels at the design size, 4k at twice it (P2): accepted"
|
||||
"roll-integer":"a credit roll at 2 design pixels per frame, keys on frame ticks: frame k is frame 0 moved 2k pixels at the design size, 4k at twice it (P2): accepted",
|
||||
"stroke-slice":"a lower third in the stroke set (a bar and two lines that slide in on an easing table, hold and fade out as one group) and a short credit roll moving up three design pixels a frame: accepted"
|
||||
}
|
||||
|
|
|
|||
211
wai/vector-conformance/vectors/motion/stroke-slice.json
Normal file
211
wai/vector-conformance/vectors/motion/stroke-slice.json
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":240000,"design_w":320,"design_h":180,"n_sizes":1,"n_rates":1,"n_sets":1,"n_tables":1,"n_paints":5,"n_texts":5,"n_tracks":3,"n_paths":1,"n_items":8,"n_keys":7,"n_glyphs":71,"n_clusters":71,"path_contours":1,"path_points":4,"strings_len":81,"inline_len":0,"held_len":105523,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":320,"h":180,"cost":16863033}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":105523,"sha256":"05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9"}
|
||||
],
|
||||
"tables":[
|
||||
[0,9000,17000,24000,30500,36000,41000,45500,49500,53000,56000,58500,60700,62500,64000,65000,65536]
|
||||
],
|
||||
"paints":[
|
||||
[20,40,120,220],
|
||||
[255,255,255,255],
|
||||
[200,200,200,255],
|
||||
[220,40,40,255],
|
||||
[40,80,220,255]
|
||||
],
|
||||
"tracks":[
|
||||
{"kind":2,"zero@1":0,"zero@2":0,"n_keys":2},
|
||||
{"kind":1,"zero@1":0,"zero@2":0,"n_keys":3},
|
||||
{"kind":2,"zero@1":0,"zero@2":0,"n_keys":2}
|
||||
],
|
||||
"keys":[
|
||||
[0,-76800,2,[0,0,0]],
|
||||
[48000,0,0,[0,0,0]],
|
||||
[0,255,0,[0,0,0]],
|
||||
[192000,255,1,[0,0,0]],
|
||||
[240000,0,0,[0,0,0]],
|
||||
[0,0,1,[0,0,0]],
|
||||
[240000,-38400,0,[0,0,0]]
|
||||
],
|
||||
"paths":[
|
||||
{"n_contours":1,"n_points":4,"zero@4":0,"xmin":12,"ymin":128,"xmax":236,"ymax":172}
|
||||
],
|
||||
"path_ends":[3],
|
||||
"path_points":[
|
||||
[12,128,0],
|
||||
[236,128,0],
|
||||
[236,172,0],
|
||||
[12,172,0]
|
||||
],
|
||||
"texts":[
|
||||
{"purpose":3,"dir":0,"lang_len":2,"mode":0,"group":1,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":10},
|
||||
{"purpose":3,"dir":0,"lang_len":2,"mode":0,"group":1,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":28},
|
||||
{"purpose":2,"dir":0,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":14},
|
||||
{"purpose":2,"dir":0,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":7},
|
||||
{"purpose":2,"dir":0,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":12}
|
||||
],
|
||||
"items":[
|
||||
{"kind":0,"rule":0,"zero@2":0,"ref":65535,"paint":65535,"text":65535,"zero@10":0,"n_glyphs":0,"n_clusters":0,"t_on":0,"t_off":240000,"unit_scale":0,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":0,"ty":65535,"op":1,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":2,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":65535,"zero@10":0,"n_glyphs":0,"n_clusters":0,"t_on":0,"t_off":240000,"unit_scale":16777216,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":1,"text":0,"zero@10":0,"n_glyphs":10,"n_clusters":10,"t_on":0,"t_off":240000,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":2,"text":1,"zero@10":0,"n_glyphs":28,"n_clusters":28,"t_on":0,"t_off":240000,"unit_scale":131072,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":1,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":0,"n_clusters":0,"t_on":0,"t_off":0,"unit_scale":0,"m00":0,"m01":0,"m10":0,"m11":0,"tx":0,"ty":0,"op":0,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":1,"text":2,"zero@10":0,"n_glyphs":14,"n_clusters":14,"t_on":0,"t_off":240000,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":2,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":3,"text":3,"zero@10":0,"n_glyphs":7,"n_clusters":7,"t_on":0,"t_off":240000,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":2,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":4,"text":4,"zero@10":0,"n_glyphs":12,"n_clusters":12,"t_on":0,"t_off":240000,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":2,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[83,0,5120,37888],
|
||||
[116,0,8570,37888],
|
||||
[114,0,10730,37888],
|
||||
[111,0,13010,37888],
|
||||
[107,0,16370,37888],
|
||||
[101,0,19370,37888],
|
||||
[32,0,22610,37888],
|
||||
[115,0,24410,37888],
|
||||
[101,0,27230,37888],
|
||||
[116,0,30470,37888],
|
||||
[73,0,5120,41984],
|
||||
[110,0,6160,41984],
|
||||
[116,0,8400,41984],
|
||||
[101,0,9840,41984],
|
||||
[103,0,12000,41984],
|
||||
[101,0,14200,41984],
|
||||
[114,0,16360,41984],
|
||||
[32,0,17880,41984],
|
||||
[111,0,19080,41984],
|
||||
[117,0,21320,41984],
|
||||
[116,0,23560,41984],
|
||||
[108,0,25000,41984],
|
||||
[105,0,25960,41984],
|
||||
[110,0,26920,41984],
|
||||
[101,0,29160,41984],
|
||||
[115,0,31320,41984],
|
||||
[44,0,33200,41984],
|
||||
[32,0,34080,41984],
|
||||
[100,0,35280,41984],
|
||||
[114,0,37520,41984],
|
||||
[97,0,39040,41984],
|
||||
[119,0,41200,41984],
|
||||
[110,0,44200,41984],
|
||||
[32,0,46440,41984],
|
||||
[104,0,47640,41984],
|
||||
[101,0,49880,41984],
|
||||
[114,0,52040,41984],
|
||||
[101,0,53560,41984],
|
||||
[79,0,22270,23040],
|
||||
[112,0,26530,23040],
|
||||
[101,0,29890,23040],
|
||||
[110,0,33130,23040],
|
||||
[105,0,36490,23040],
|
||||
[110,0,37930,23040],
|
||||
[103,0,41290,23040],
|
||||
[32,0,44590,23040],
|
||||
[116,0,46390,23040],
|
||||
[105,0,48550,23040],
|
||||
[116,0,49990,23040],
|
||||
[108,0,52150,23040],
|
||||
[101,0,53590,23040],
|
||||
[115,0,56830,23040],
|
||||
[67,0,31360,31744],
|
||||
[114,0,35260,31744],
|
||||
[101,0,37540,31744],
|
||||
[100,0,40780,31744],
|
||||
[105,0,44140,31744],
|
||||
[116,0,45580,31744],
|
||||
[115,0,47740,31744],
|
||||
[76,0,24085,40448],
|
||||
[111,0,27235,40448],
|
||||
[119,0,30595,40448],
|
||||
[101,0,35095,40448],
|
||||
[114,0,38335,40448],
|
||||
[32,0,40615,40448],
|
||||
[116,0,42415,40448],
|
||||
[104,0,44575,40448],
|
||||
[105,0,47935,40448],
|
||||
[114,0,49375,40448],
|
||||
[100,0,51655,40448],
|
||||
[115,0,55015,40448]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1],
|
||||
[1,0,5,1],
|
||||
[1,0,6,1],
|
||||
[1,0,7,1],
|
||||
[1,0,8,1],
|
||||
[1,0,9,1],
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1],
|
||||
[1,0,5,1],
|
||||
[1,0,6,1],
|
||||
[1,0,7,1],
|
||||
[1,0,8,1],
|
||||
[1,0,9,1],
|
||||
[1,0,10,1],
|
||||
[1,0,11,1],
|
||||
[1,0,12,1],
|
||||
[1,0,13,1],
|
||||
[1,0,14,1],
|
||||
[1,0,15,1],
|
||||
[1,0,16,1],
|
||||
[1,0,17,1],
|
||||
[1,0,18,1],
|
||||
[1,0,19,1],
|
||||
[1,0,20,1],
|
||||
[1,0,21,1],
|
||||
[1,0,22,1],
|
||||
[1,0,23,1],
|
||||
[1,0,24,1],
|
||||
[1,0,25,1],
|
||||
[1,0,26,1],
|
||||
[1,0,27,1],
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1],
|
||||
[1,0,5,1],
|
||||
[1,0,6,1],
|
||||
[1,0,7,1],
|
||||
[1,0,8,1],
|
||||
[1,0,9,1],
|
||||
[1,0,10,1],
|
||||
[1,0,11,1],
|
||||
[1,0,12,1],
|
||||
[1,0,13,1],
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1],
|
||||
[1,0,5,1],
|
||||
[1,0,6,1],
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1],
|
||||
[1,0,5,1],
|
||||
[1,0,6,1],
|
||||
[1,0,7,1],
|
||||
[1,0,8,1],
|
||||
[1,0,9,1],
|
||||
[1,0,10,1],
|
||||
[1,0,11,1]
|
||||
],
|
||||
"strings":"enStroke setenInteger outlines, drawn hereenOpening titlesenCreditsenLower thirds",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/motion/stroke-slice.wvg1
Normal file
BIN
wai/vector-conformance/vectors/motion/stroke-slice.wvg1
Normal file
Binary file not shown.
40
wai/vector-conformance/vectors/runs/arabic-joins-split.json
Normal file
40
wai/vector-conformance/vectors/runs/arabic-joins-split.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":96,"design_h":48,"n_sizes":1,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":2,"n_keys":0,"n_glyphs":3,"n_clusters":3,"path_contours":0,"path_points":0,"strings_len":8,"inline_len":0,"held_len":59879,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":96,"h":48,"cost":718113}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":1,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":6}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":1,"n_clusters":1,"t_on":0,"t_off":4800,"unit_scale":655360,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":2,"n_clusters":2,"t_on":0,"t_off":4800,"unit_scale":655360,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[65174,0,2304,7680],
|
||||
[65268,0,11904,7680],
|
||||
[65169,0,17104,7680]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,4,2],
|
||||
[1,0,2,2],
|
||||
[1,0,0,2]
|
||||
],
|
||||
"strings":"arبيت",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/runs/arabic-joins-split.wvg1
Normal file
BIN
wai/vector-conformance/vectors/runs/arabic-joins-split.wvg1
Normal file
Binary file not shown.
39
wai/vector-conformance/vectors/runs/arabic-joins.json
Normal file
39
wai/vector-conformance/vectors/runs/arabic-joins.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":96,"design_h":48,"n_sizes":1,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":1,"n_keys":0,"n_glyphs":3,"n_clusters":3,"path_contours":0,"path_points":0,"strings_len":8,"inline_len":0,"held_len":59879,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":96,"h":48,"cost":718535}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":1,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":6}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":3,"n_clusters":3,"t_on":0,"t_off":4800,"unit_scale":655360,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[65174,0,2304,7680],
|
||||
[65268,0,11904,7680],
|
||||
[65169,0,17104,7680]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,4,2],
|
||||
[1,0,2,2],
|
||||
[1,0,0,2]
|
||||
],
|
||||
"strings":"arبيت",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/runs/arabic-joins.wvg1
Normal file
BIN
wai/vector-conformance/vectors/runs/arabic-joins.wvg1
Normal file
Binary file not shown.
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"abutting":"three glyphs abutting mid-pixel: the seam pixels read 255 (the glyph-wise union): accepted",
|
||||
"arabic-joins":"an Arabic joining sequence from the test repertoire in one run, large: each connector's round cap passes its glyph's edge and overlaps the neighbour, and the glyph-wise union over-covers the partly covered edge pixels of each overlap (the join knot, §3.13): accepted",
|
||||
"arabic-joins-split":"the same joining sequence split across two runs, which an encoder never writes (one joining sequence per run): the runs composite by source-over, so the join's pixels differ from the one-run vector: accepted",
|
||||
"marks":"marks overlapping their bases: accepted",
|
||||
"overlapping":"glyphs that overlap: the partly covered edge pixels of the overlap over-cover (the knot, §3.13): accepted",
|
||||
"phases":"one glyph at all 256 subpixel phases of the x axis: accepted",
|
||||
|
|
|
|||
50
wai/vector-conformance/vectors/text/arabic.json
Normal file
50
wai/vector-conformance/vectors/text/arabic.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":160,"design_h":64,"n_sizes":2,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":1,"n_keys":0,"n_glyphs":9,"n_clusters":7,"path_contours":0,"path_points":0,"strings_len":21,"inline_len":0,"held_len":59879,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":160,"h":64,"cost":1822464},
|
||||
{"w":320,"h":128,"cost":1923747}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":1,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":19}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":9,"n_clusters":7,"t_on":0,"t_off":4800,"unit_scale":327680,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[65249,0,7012,10240],
|
||||
[65276,0,10812,10240],
|
||||
[65203,0,16312,10240],
|
||||
[32,0,20812,10240],
|
||||
[65174,0,23812,10240],
|
||||
[65268,0,28612,10240],
|
||||
[1618,0,29912,10240],
|
||||
[65169,0,31212,10240],
|
||||
[1614,0,33012,10240]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,17,2],
|
||||
[1,0,13,4],
|
||||
[1,0,11,2],
|
||||
[1,0,10,1],
|
||||
[1,0,8,2],
|
||||
[2,0,4,4],
|
||||
[2,0,0,4]
|
||||
],
|
||||
"strings":"arبَيْت سلام",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/arabic.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/arabic.wvg1
Normal file
Binary file not shown.
49
wai/vector-conformance/vectors/text/devanagari.json
Normal file
49
wai/vector-conformance/vectors/text/devanagari.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":128,"design_h":48,"n_sizes":2,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":1,"n_keys":0,"n_glyphs":10,"n_clusters":5,"path_contours":0,"path_points":0,"strings_len":36,"inline_len":0,"held_len":59879,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":128,"h":48,"cost":2409572},
|
||||
{"w":256,"h":96,"cost":2538475}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":0,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":34}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":10,"n_clusters":5,"t_on":0,"t_off":4800,"unit_scale":262144,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[2367,0,3149,9728],
|
||||
[2361,0,4749,9728],
|
||||
[2306,0,7789,9728],
|
||||
[2342,0,8749,9728],
|
||||
[2368,0,12509,9728],
|
||||
[32,0,14109,9728],
|
||||
[2367,0,16509,9728],
|
||||
[2361,0,18109,9728],
|
||||
[57344,0,22109,9728],
|
||||
[2368,0,27309,9728]
|
||||
],
|
||||
"clusters":[
|
||||
[3,0,0,9],
|
||||
[2,0,9,6],
|
||||
[1,0,15,1],
|
||||
[2,0,16,6],
|
||||
[2,0,22,12]
|
||||
],
|
||||
"strings":"hiहिंदी हिन्दी",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/devanagari.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/devanagari.wvg1
Normal file
Binary file not shown.
44
wai/vector-conformance/vectors/text/emoji.json
Normal file
44
wai/vector-conformance/vectors/text/emoji.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":80,"design_h":40,"n_sizes":2,"n_rates":1,"n_sets":2,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":2,"n_keys":0,"n_glyphs":4,"n_clusters":4,"path_contours":0,"path_points":0,"strings_len":9,"inline_len":0,"held_len":165402,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":80,"h":40,"cost":1001617},
|
||||
{"w":160,"h":80,"cost":1046609}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":105523,"sha256":"05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9"},
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":0,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":7}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":3,"n_clusters":3,"t_on":0,"t_off":4800,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":1,"paint":0,"text":0,"zero@10":0,"n_glyphs":1,"n_clusters":1,"t_on":0,"t_off":4800,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[79,0,1536,7680],
|
||||
[75,0,5796,7680],
|
||||
[32,0,9396,7680],
|
||||
[57345,0,11196,7680]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,4]
|
||||
],
|
||||
"strings":"enOK 😀",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/emoji.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/emoji.wvg1
Normal file
Binary file not shown.
42
wai/vector-conformance/vectors/text/hebrew.json
Normal file
42
wai/vector-conformance/vectors/text/hebrew.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":128,"design_h":48,"n_sizes":2,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":1,"n_keys":0,"n_glyphs":4,"n_clusters":4,"path_contours":0,"path_points":0,"strings_len":10,"inline_len":0,"held_len":59879,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":128,"h":48,"cost":533026},
|
||||
{"w":256,"h":96,"cost":575728}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":1,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":8}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":4,"n_clusters":4,"t_on":0,"t_off":4800,"unit_scale":262144,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[1501,0,8448,8704],
|
||||
[1493,0,12688,8704],
|
||||
[1500,0,15088,8704],
|
||||
[1513,0,19088,8704]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,6,2],
|
||||
[1,0,4,2],
|
||||
[1,0,2,2],
|
||||
[1,0,0,2]
|
||||
],
|
||||
"strings":"heשלום",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/hebrew.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/hebrew.wvg1
Normal file
Binary file not shown.
11
wai/vector-conformance/vectors/text/index.json
Normal file
11
wai/vector-conformance/vectors/text/index.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"arabic":"Arabic, right to left, in the test repertoire: initial, medial, final and isolated forms chosen by joining type, the lam-alef ligature, fatha and sukun on their bases; glyphs in visual order and clusters with byte_first decreasing; joins whose connectors overlap: accepted",
|
||||
"devanagari":"Devanagari in the test repertoire, one word spelt with the anusvara and with the conjunct: the i-matra drawn before the consonant it follows in logical order, the anusvara above its base, the conjunct na-da as one glyph; one cluster per syllable; headlines that overlap: accepted",
|
||||
"emoji":"U+1F600 drawn by one monochrome outline glyph from the test repertoire, one cluster mapping its four bytes to that glyph: accepted",
|
||||
"hebrew":"Hebrew, right to left, in the test repertoire, with the final mem: accepted",
|
||||
"latin-ascii":"printable ASCII, U+0020-U+007E, from the held stroke set: one block drawn by three runs, one per line, one cluster per glyph: accepted",
|
||||
"mixed-direction":"an Arabic block, right to left, holding a Latin word and digits: the left-to-right run from the stroke set, then the right-to-left run with its space from the test repertoire, both drawing parts of one block: accepted",
|
||||
"ruby":"a ruby block, Latin from the stroke set, annotating the whole string of a Devanagari base block from the test repertoire: accepted",
|
||||
"split-lines":"one block broken over three runs, one per line: the space at the first break drawn by no glyph (a cluster of zero glyphs), and a hyphen inserted at the second (a cluster of no bytes): accepted",
|
||||
"vertical-rl":"a vertical-rl block (mode 1): Latin from the stroke set, upright and stacked; the sink draws the glyphs where the payload puts them, and the mode is in the block's identity: accepted"
|
||||
}
|
||||
226
wai/vector-conformance/vectors/text/latin-ascii.json
Normal file
226
wai/vector-conformance/vectors/text/latin-ascii.json
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":320,"design_h":96,"n_sizes":2,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":3,"n_keys":0,"n_glyphs":95,"n_clusters":95,"path_contours":0,"path_points":0,"strings_len":98,"inline_len":0,"held_len":105523,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":320,"h":96,"cost":19235383},
|
||||
{"w":640,"h":192,"cost":19733375}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":105523,"sha256":"05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":0,"lang_len":3,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":95}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":32,"n_clusters":32,"t_on":0,"t_off":4800,"unit_scale":131072,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":32,"n_clusters":32,"t_on":0,"t_off":4800,"unit_scale":131072,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":31,"n_clusters":31,"t_on":0,"t_off":4800,"unit_scale":131072,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[32,0,2048,5632],
|
||||
[33,0,3248,5632],
|
||||
[34,0,4288,5632],
|
||||
[35,0,5808,5632],
|
||||
[36,0,8288,5632],
|
||||
[37,0,10588,5632],
|
||||
[38,0,13588,5632],
|
||||
[39,0,16188,5632],
|
||||
[40,0,17068,5632],
|
||||
[41,0,18348,5632],
|
||||
[42,0,19628,5632],
|
||||
[43,0,21428,5632],
|
||||
[44,0,23628,5632],
|
||||
[45,0,24508,5632],
|
||||
[46,0,26028,5632],
|
||||
[47,0,26908,5632],
|
||||
[48,0,28508,5632],
|
||||
[49,0,30748,5632],
|
||||
[50,0,32988,5632],
|
||||
[51,0,35228,5632],
|
||||
[52,0,37468,5632],
|
||||
[53,0,39708,5632],
|
||||
[54,0,41948,5632],
|
||||
[55,0,44188,5632],
|
||||
[56,0,46428,5632],
|
||||
[57,0,48668,5632],
|
||||
[58,0,50908,5632],
|
||||
[59,0,51788,5632],
|
||||
[60,0,52668,5632],
|
||||
[61,0,54868,5632],
|
||||
[62,0,57068,5632],
|
||||
[63,0,59268,5632],
|
||||
[64,0,2048,12800],
|
||||
[65,0,5448,12800],
|
||||
[66,0,8008,12800],
|
||||
[67,0,10508,12800],
|
||||
[68,0,13108,12800],
|
||||
[69,0,15708,12800],
|
||||
[70,0,18008,12800],
|
||||
[71,0,20208,12800],
|
||||
[72,0,22908,12800],
|
||||
[73,0,25508,12800],
|
||||
[74,0,26548,12800],
|
||||
[75,0,28548,12800],
|
||||
[76,0,30948,12800],
|
||||
[77,0,33048,12800],
|
||||
[78,0,36048,12800],
|
||||
[79,0,38648,12800],
|
||||
[80,0,41488,12800],
|
||||
[81,0,43888,12800],
|
||||
[82,0,46728,12800],
|
||||
[83,0,49228,12800],
|
||||
[84,0,51528,12800],
|
||||
[85,0,53828,12800],
|
||||
[86,0,56428,12800],
|
||||
[87,0,58928,12800],
|
||||
[88,0,62328,12800],
|
||||
[89,0,64728,12800],
|
||||
[90,0,67128,12800],
|
||||
[91,0,69428,12800],
|
||||
[92,0,70628,12800],
|
||||
[93,0,72228,12800],
|
||||
[94,0,73428,12800],
|
||||
[95,0,75428,12800],
|
||||
[96,0,2048,19968],
|
||||
[97,0,3248,19968],
|
||||
[98,0,5408,19968],
|
||||
[99,0,7648,19968],
|
||||
[100,0,9648,19968],
|
||||
[101,0,11888,19968],
|
||||
[102,0,14048,19968],
|
||||
[103,0,15448,19968],
|
||||
[104,0,17648,19968],
|
||||
[105,0,19888,19968],
|
||||
[106,0,20848,19968],
|
||||
[107,0,21888,19968],
|
||||
[108,0,23888,19968],
|
||||
[109,0,24848,19968],
|
||||
[110,0,27688,19968],
|
||||
[111,0,29928,19968],
|
||||
[112,0,32168,19968],
|
||||
[113,0,34408,19968],
|
||||
[114,0,36648,19968],
|
||||
[115,0,38168,19968],
|
||||
[116,0,40048,19968],
|
||||
[117,0,41488,19968],
|
||||
[118,0,43728,19968],
|
||||
[119,0,45808,19968],
|
||||
[120,0,48808,19968],
|
||||
[121,0,50808,19968],
|
||||
[122,0,52888,19968],
|
||||
[123,0,54808,19968],
|
||||
[124,0,56208,19968],
|
||||
[125,0,57168,19968],
|
||||
[126,0,58568,19968]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1],
|
||||
[1,0,5,1],
|
||||
[1,0,6,1],
|
||||
[1,0,7,1],
|
||||
[1,0,8,1],
|
||||
[1,0,9,1],
|
||||
[1,0,10,1],
|
||||
[1,0,11,1],
|
||||
[1,0,12,1],
|
||||
[1,0,13,1],
|
||||
[1,0,14,1],
|
||||
[1,0,15,1],
|
||||
[1,0,16,1],
|
||||
[1,0,17,1],
|
||||
[1,0,18,1],
|
||||
[1,0,19,1],
|
||||
[1,0,20,1],
|
||||
[1,0,21,1],
|
||||
[1,0,22,1],
|
||||
[1,0,23,1],
|
||||
[1,0,24,1],
|
||||
[1,0,25,1],
|
||||
[1,0,26,1],
|
||||
[1,0,27,1],
|
||||
[1,0,28,1],
|
||||
[1,0,29,1],
|
||||
[1,0,30,1],
|
||||
[1,0,31,1],
|
||||
[1,0,32,1],
|
||||
[1,0,33,1],
|
||||
[1,0,34,1],
|
||||
[1,0,35,1],
|
||||
[1,0,36,1],
|
||||
[1,0,37,1],
|
||||
[1,0,38,1],
|
||||
[1,0,39,1],
|
||||
[1,0,40,1],
|
||||
[1,0,41,1],
|
||||
[1,0,42,1],
|
||||
[1,0,43,1],
|
||||
[1,0,44,1],
|
||||
[1,0,45,1],
|
||||
[1,0,46,1],
|
||||
[1,0,47,1],
|
||||
[1,0,48,1],
|
||||
[1,0,49,1],
|
||||
[1,0,50,1],
|
||||
[1,0,51,1],
|
||||
[1,0,52,1],
|
||||
[1,0,53,1],
|
||||
[1,0,54,1],
|
||||
[1,0,55,1],
|
||||
[1,0,56,1],
|
||||
[1,0,57,1],
|
||||
[1,0,58,1],
|
||||
[1,0,59,1],
|
||||
[1,0,60,1],
|
||||
[1,0,61,1],
|
||||
[1,0,62,1],
|
||||
[1,0,63,1],
|
||||
[1,0,64,1],
|
||||
[1,0,65,1],
|
||||
[1,0,66,1],
|
||||
[1,0,67,1],
|
||||
[1,0,68,1],
|
||||
[1,0,69,1],
|
||||
[1,0,70,1],
|
||||
[1,0,71,1],
|
||||
[1,0,72,1],
|
||||
[1,0,73,1],
|
||||
[1,0,74,1],
|
||||
[1,0,75,1],
|
||||
[1,0,76,1],
|
||||
[1,0,77,1],
|
||||
[1,0,78,1],
|
||||
[1,0,79,1],
|
||||
[1,0,80,1],
|
||||
[1,0,81,1],
|
||||
[1,0,82,1],
|
||||
[1,0,83,1],
|
||||
[1,0,84,1],
|
||||
[1,0,85,1],
|
||||
[1,0,86,1],
|
||||
[1,0,87,1],
|
||||
[1,0,88,1],
|
||||
[1,0,89,1],
|
||||
[1,0,90,1],
|
||||
[1,0,91,1],
|
||||
[1,0,92,1],
|
||||
[1,0,93,1],
|
||||
[1,0,94,1]
|
||||
],
|
||||
"strings":"und !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/latin-ascii.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/latin-ascii.wvg1
Normal file
Binary file not shown.
60
wai/vector-conformance/vectors/text/mixed-direction.json
Normal file
60
wai/vector-conformance/vectors/text/mixed-direction.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":224,"design_h":48,"n_sizes":2,"n_rates":1,"n_sets":2,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":2,"n_keys":0,"n_glyphs":12,"n_clusters":12,"path_contours":0,"path_points":0,"strings_len":19,"inline_len":0,"held_len":165402,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":224,"h":48,"cost":2301369},
|
||||
{"w":448,"h":96,"cost":2437421}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":105523,"sha256":"05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9"},
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":1,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":17}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":8,"n_clusters":8,"t_on":0,"t_off":4800,"unit_scale":262144,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":1,"paint":0,"text":0,"zero@10":0,"n_glyphs":4,"n_clusters":4,"t_on":0,"t_off":4800,"unit_scale":262144,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[87,0,4608,9216],
|
||||
[65,0,11408,9216],
|
||||
[73,0,16528,9216],
|
||||
[32,0,18608,9216],
|
||||
[50,0,21008,9216],
|
||||
[48,0,25488,9216],
|
||||
[50,0,29968,9216],
|
||||
[54,0,34448,9216],
|
||||
[32,0,38928,9216],
|
||||
[65249,0,41328,9216],
|
||||
[65276,0,44368,9216],
|
||||
[65203,0,48768,9216]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,9,1],
|
||||
[1,0,10,1],
|
||||
[1,0,11,1],
|
||||
[1,0,12,1],
|
||||
[1,0,13,1],
|
||||
[1,0,14,1],
|
||||
[1,0,15,1],
|
||||
[1,0,16,1],
|
||||
[1,0,8,1],
|
||||
[1,0,6,2],
|
||||
[1,0,2,4],
|
||||
[1,0,0,2]
|
||||
],
|
||||
"strings":"arسلام WAI 2026",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/mixed-direction.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/mixed-direction.wvg1
Normal file
Binary file not shown.
54
wai/vector-conformance/vectors/text/ruby.json
Normal file
54
wai/vector-conformance/vectors/text/ruby.json
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":80,"design_h":56,"n_sizes":2,"n_rates":1,"n_sets":2,"n_tables":0,"n_paints":1,"n_texts":2,"n_tracks":0,"n_paths":0,"n_items":2,"n_keys":0,"n_glyphs":10,"n_clusters":7,"path_contours":0,"path_points":0,"strings_len":29,"inline_len":0,"held_len":165402,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":80,"h":56,"cost":2260523},
|
||||
{"w":160,"h":112,"cost":2339549}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":59879,"sha256":"2310ab64ddcf4d851d4d7c08bbce141fba178f03fdcbb07b05db3863e179846f"},
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":105523,"sha256":"05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":0,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":15},
|
||||
{"purpose":7,"dir":0,"lang_len":7,"mode":0,"group":0,"ruby":1,"ruby_start":0,"ruby_len":15,"str_len":5}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":5,"n_clusters":2,"t_on":0,"t_off":4800,"unit_scale":262144,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":1,"paint":0,"text":1,"zero@10":0,"n_glyphs":5,"n_clusters":5,"t_on":0,"t_off":4800,"unit_scale":98304,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[2367,0,4639,11776],
|
||||
[2361,0,6239,11776],
|
||||
[2306,0,9279,11776],
|
||||
[2342,0,10239,11776],
|
||||
[2368,0,13999,11776],
|
||||
[104,0,6912,3584],
|
||||
[105,0,8592,3584],
|
||||
[110,0,9312,3584],
|
||||
[100,0,10992,3584],
|
||||
[105,0,12672,3584]
|
||||
],
|
||||
"clusters":[
|
||||
[3,0,0,9],
|
||||
[2,0,9,6],
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1]
|
||||
],
|
||||
"strings":"hiहिंदीhi-Latnhindi",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/ruby.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/ruby.wvg1
Normal file
Binary file not shown.
113
wai/vector-conformance/vectors/text/split-lines.json
Normal file
113
wai/vector-conformance/vectors/text/split-lines.json
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":208,"design_h":96,"n_sizes":2,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":3,"n_keys":0,"n_glyphs":38,"n_clusters":39,"path_contours":0,"path_points":0,"strings_len":40,"inline_len":0,"held_len":105523,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":208,"h":96,"cost":8058342},
|
||||
{"w":416,"h":192,"cost":8346462}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":105523,"sha256":"05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":2,"dir":0,"lang_len":2,"mode":0,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":38}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":12,"n_clusters":13,"t_on":0,"t_off":4800,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":16,"n_clusters":16,"t_on":0,"t_off":4800,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]},
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":10,"n_clusters":10,"t_on":0,"t_off":4800,"unit_scale":196608,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[84,0,2048,6144],
|
||||
[105,0,5498,6144],
|
||||
[116,0,6938,6144],
|
||||
[108,0,9098,6144],
|
||||
[101,0,10538,6144],
|
||||
[115,0,13778,6144],
|
||||
[32,0,16598,6144],
|
||||
[100,0,18398,6144],
|
||||
[114,0,21758,6144],
|
||||
[97,0,24038,6144],
|
||||
[119,0,27278,6144],
|
||||
[110,0,31778,6144],
|
||||
[102,0,2048,13824],
|
||||
[114,0,4148,13824],
|
||||
[111,0,6428,13824],
|
||||
[109,0,9788,13824],
|
||||
[32,0,14048,13824],
|
||||
[105,0,15848,13824],
|
||||
[110,0,17288,13824],
|
||||
[116,0,20648,13824],
|
||||
[101,0,22808,13824],
|
||||
[103,0,26048,13824],
|
||||
[101,0,29348,13824],
|
||||
[114,0,32588,13824],
|
||||
[32,0,34868,13824],
|
||||
[105,0,36668,13824],
|
||||
[110,0,38108,13824],
|
||||
[45,0,41468,13824],
|
||||
[115,0,2048,21504],
|
||||
[116,0,4868,21504],
|
||||
[114,0,7028,21504],
|
||||
[117,0,9308,21504],
|
||||
[99,0,12668,21504],
|
||||
[116,0,15668,21504],
|
||||
[105,0,17828,21504],
|
||||
[111,0,19268,21504],
|
||||
[110,0,22628,21504],
|
||||
[115,0,25988,21504]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1],
|
||||
[1,0,3,1],
|
||||
[1,0,4,1],
|
||||
[1,0,5,1],
|
||||
[1,0,6,1],
|
||||
[1,0,7,1],
|
||||
[1,0,8,1],
|
||||
[1,0,9,1],
|
||||
[1,0,10,1],
|
||||
[1,0,11,1],
|
||||
[0,0,12,1],
|
||||
[1,0,13,1],
|
||||
[1,0,14,1],
|
||||
[1,0,15,1],
|
||||
[1,0,16,1],
|
||||
[1,0,17,1],
|
||||
[1,0,18,1],
|
||||
[1,0,19,1],
|
||||
[1,0,20,1],
|
||||
[1,0,21,1],
|
||||
[1,0,22,1],
|
||||
[1,0,23,1],
|
||||
[1,0,24,1],
|
||||
[1,0,25,1],
|
||||
[1,0,26,1],
|
||||
[1,0,27,1],
|
||||
[1,0,28,0],
|
||||
[1,0,28,1],
|
||||
[1,0,29,1],
|
||||
[1,0,30,1],
|
||||
[1,0,31,1],
|
||||
[1,0,32,1],
|
||||
[1,0,33,1],
|
||||
[1,0,34,1],
|
||||
[1,0,35,1],
|
||||
[1,0,36,1],
|
||||
[1,0,37,1]
|
||||
],
|
||||
"strings":"enTitles drawn from integer instructions",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/split-lines.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/split-lines.wvg1
Normal file
Binary file not shown.
40
wai/vector-conformance/vectors/text/vertical-rl.json
Normal file
40
wai/vector-conformance/vectors/text/vertical-rl.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"header":{"magic":"WVG1","format":1,"tb":120000,"t0":0,"dur":4800,"design_w":48,"design_h":96,"n_sizes":2,"n_rates":1,"n_sets":1,"n_tables":0,"n_paints":1,"n_texts":1,"n_tracks":0,"n_paths":0,"n_items":1,"n_keys":0,"n_glyphs":3,"n_clusters":3,"path_contours":0,"path_points":0,"strings_len":5,"inline_len":0,"held_len":105523,"zero@72":[0,0,0,0,0,0,0,0]},
|
||||
"sizes":[
|
||||
{"w":48,"h":96,"cost":420615},
|
||||
{"w":96,"h":192,"cost":460590}
|
||||
],
|
||||
"rates":[
|
||||
{"num":25,"den":1}
|
||||
],
|
||||
"sets":[
|
||||
{"carriage":0,"zero@1":[0,0,0],"length":105523,"sha256":"05476c4658ef7366c7700736487eac4c3c1b7d21264978c7d05bd4c7427736c9"}
|
||||
],
|
||||
"tables":[],
|
||||
"paints":[
|
||||
[255,255,255,255]
|
||||
],
|
||||
"tracks":[],
|
||||
"keys":[],
|
||||
"paths":[],
|
||||
"path_ends":[],
|
||||
"path_points":[],
|
||||
"texts":[
|
||||
{"purpose":7,"dir":0,"lang_len":2,"mode":1,"group":0,"ruby":0,"ruby_start":0,"ruby_len":0,"str_len":3}
|
||||
],
|
||||
"items":[
|
||||
{"kind":3,"rule":0,"zero@2":0,"ref":0,"paint":0,"text":0,"zero@10":0,"n_glyphs":3,"n_clusters":3,"t_on":0,"t_off":4800,"unit_scale":262144,"m00":65535,"m01":65535,"m10":65535,"m11":65535,"tx":65535,"ty":65535,"op":65535,"zero@42":[0,0,0,0,0,0]}
|
||||
],
|
||||
"glyphs":[
|
||||
[87,0,2744,7680],
|
||||
[65,0,3584,14480],
|
||||
[73,0,5104,21280]
|
||||
],
|
||||
"clusters":[
|
||||
[1,0,0,1],
|
||||
[1,0,1,1],
|
||||
[1,0,2,1]
|
||||
],
|
||||
"strings":"enWAI",
|
||||
"inline":""
|
||||
}
|
||||
BIN
wai/vector-conformance/vectors/text/vertical-rl.wvg1
Normal file
BIN
wai/vector-conformance/vectors/text/vertical-rl.wvg1
Normal file
Binary file not shown.
|
|
@ -25,10 +25,16 @@ path = "src/lib.rs"
|
|||
[[bin]]
|
||||
name = "wai-vector"
|
||||
path = "src/bin/wai-vector.rs"
|
||||
required-features = ["std"]
|
||||
required-features = ["std", "author"]
|
||||
|
||||
[features]
|
||||
default = ["author"]
|
||||
std = []
|
||||
# Authoring: the `WSF1` stroke source and its expansion into a `WOS1` (Annex A), the
|
||||
# in-house sets' drawings, and the corpus generator that needs them. A sink has no
|
||||
# stroker (§3.14), so a sink's build turns this off (`default-features = false`) and
|
||||
# keeps the parsers, the raster and the recomputation of every vector.
|
||||
author = []
|
||||
# The 32-bit identities of RS1 (§3.5), for a fabric without `i64` and for the model
|
||||
# checker; the unit tests build them too.
|
||||
i32-forms = []
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ fn verify(wai: &Path) -> Result<(), String> {
|
|||
}
|
||||
}
|
||||
let mut have: BTreeSet<String> = BTreeSet::new();
|
||||
for sub in ["sets", "vectors", "expected"] {
|
||||
for sub in vectors::GENERATED_DIRS {
|
||||
walk(wai, &corpus.join(sub), &mut have);
|
||||
}
|
||||
walk(wai, &wai.join(SEEDS), &mut have);
|
||||
|
|
|
|||
27
wai/wai-vector/src/builtin/mod.rs
Normal file
27
wai/wai-vector/src/builtin/mod.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//! The default stroke set (`graphics-vector.md` §4, Annex B): printable ASCII,
|
||||
//! U+0020–U+007E, each glyph ID the code point it draws. It ships with every reference
|
||||
//! sink and is always held: a payload names it by SHA-256 and length in a held `SET`
|
||||
//! record, and a sink resolves it from these bytes.
|
||||
//!
|
||||
//! The bytes are the corpus's `sets/stroke-ascii.wos1`, which `wai-vector vectors gen`
|
||||
//! writes by expanding `sources/stroke-ascii.wsf1` (Annex A). Their digest is
|
||||
//! [`STROKE_ASCII_SHA256`], which Annex B states; the tests hold the three equal.
|
||||
|
||||
use crate::wvg1::SetStore;
|
||||
|
||||
/// The bundled stroke set, a `WOS1`.
|
||||
pub const STROKE_ASCII: &[u8] = include_bytes!("../../../vector-conformance/sets/stroke-ascii.wos1");
|
||||
|
||||
/// Its SHA-256, as Annex B states it.
|
||||
pub const STROKE_ASCII_SHA256: [u8; 32] = [
|
||||
0x05, 0x47, 0x6c, 0x46, 0x58, 0xef, 0x73, 0x66, 0xc7, 0x70, 0x07, 0x36, 0x48, 0x7e, 0xac, 0x4c,
|
||||
0x3c, 0x1b, 0x7d, 0x21, 0x26, 0x49, 0x78, 0xc7, 0xd0, 0x5b, 0xd4, 0xc7, 0x42, 0x77, 0x36, 0xc9,
|
||||
];
|
||||
|
||||
/// Its exact length, as a held `SET` record states it.
|
||||
pub const STROKE_ASCII_LEN: u32 = 105_523;
|
||||
|
||||
/// Hold the bundled sets in `store`, as a reference sink does.
|
||||
pub fn hold(store: &mut SetStore) {
|
||||
store.add(STROKE_ASCII.to_vec());
|
||||
}
|
||||
|
|
@ -14,6 +14,10 @@
|
|||
//! - [`arith`]: `R_rat`, `R_half_up`, `div255`, the quantisers and the straight-alpha
|
||||
//! channel (§3.0, §3.8, §3.9, §2.9).
|
||||
//! - [`scene`]: a builder that writes canonical payloads (authoring).
|
||||
//! - [`wsf1`] (feature `author`): the stroke source format and its integer expansion
|
||||
//! into a `WOS1` (Annex A).
|
||||
//! - [`builtin`]: the default stroke set's bytes and digest (§4, Annex B), bundled with
|
||||
//! every reference sink.
|
||||
//! - [`form`]: the JSON authoring form (§2.12, informative).
|
||||
//! - [`vectors`]: the conformance vectors of `../vector-conformance/`, generated and
|
||||
//! recomputed.
|
||||
|
|
@ -27,6 +31,7 @@ extern crate alloc;
|
|||
|
||||
pub mod arith;
|
||||
pub mod bounds;
|
||||
pub mod builtin;
|
||||
pub mod clock;
|
||||
pub mod cover;
|
||||
pub mod form;
|
||||
|
|
@ -41,6 +46,8 @@ pub mod sha256;
|
|||
pub mod track;
|
||||
pub mod vectors;
|
||||
pub mod wos1;
|
||||
#[cfg(feature = "author")]
|
||||
pub mod wsf1;
|
||||
pub mod wvg1;
|
||||
|
||||
pub use refuse::{Code, Refusal};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
//!
|
||||
//! Layout, under `vector-conformance/`:
|
||||
//! - `sets/<name>.wos1`: the outline sets the vectors name. A held set is found by its
|
||||
//! SHA-256 among these files.
|
||||
//! SHA-256 among these files. Two of them are drawn here as stroke sources and
|
||||
//! expanded by Annex A: the ASCII stroke set and the test repertoire.
|
||||
//! - `sources/<name>.wsf1`: those two sets' `WSF1` sources.
|
||||
//! - `vectors/<group>/<name>.wvg1`, with its JSON authoring form (§2.12) beside it as
|
||||
//! `<name>.json`; `vectors/<group>/<name>.wos1`, an outline set checked on its own;
|
||||
//! and, in `arith` and `flatten`, `<name>.json`, an arithmetic input.
|
||||
|
|
@ -18,16 +20,32 @@
|
|||
//!
|
||||
//! [`generate`] writes every file. [`recompute`] gives a vector's expected file from
|
||||
//! the vector alone and the sets, which is what every fabric leg runs.
|
||||
//!
|
||||
//! The generator is authoring code. Without the feature `author` (a sink's build) the
|
||||
//! module keeps [`recompute`] and what it needs, and the generator's entry points
|
||||
//! are absent.
|
||||
|
||||
#[cfg_attr(not(feature = "author"), allow(dead_code))]
|
||||
mod arith_vectors;
|
||||
#[cfg_attr(not(feature = "author"), allow(dead_code))]
|
||||
mod canonical;
|
||||
#[cfg_attr(not(feature = "author"), allow(dead_code))]
|
||||
mod cost;
|
||||
#[cfg_attr(not(feature = "author"), allow(dead_code))]
|
||||
mod geometry;
|
||||
pub mod motion;
|
||||
pub mod raster;
|
||||
#[cfg_attr(not(feature = "author"), allow(dead_code))]
|
||||
mod refusals;
|
||||
#[cfg(feature = "author")]
|
||||
pub mod repertoire;
|
||||
pub mod sets;
|
||||
#[cfg(feature = "author")]
|
||||
pub mod stroke;
|
||||
#[cfg(feature = "author")]
|
||||
pub mod text;
|
||||
|
||||
#[cfg(feature = "author")]
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
|
|
@ -35,7 +53,9 @@ use alloc::vec::Vec;
|
|||
|
||||
use crate::clock;
|
||||
use crate::form;
|
||||
use crate::json::{self, Value, hex, int, obj, string};
|
||||
#[cfg(feature = "author")]
|
||||
use crate::json;
|
||||
use crate::json::{Value, hex, int, obj, string};
|
||||
use crate::sha256::sha256;
|
||||
use crate::wos1;
|
||||
use crate::wvg1::{self, Doc, SetStore};
|
||||
|
|
@ -44,6 +64,8 @@ use crate::wvg1::{self, Doc, SetStore};
|
|||
pub const CORPUS: &str = "vector-conformance";
|
||||
/// The fuzz seeds directory, relative to `wai/`.
|
||||
pub const SEEDS: &str = "wai-vector/fuzz/seeds";
|
||||
/// The corpus directories the generator writes, every file in them generated.
|
||||
pub const GENERATED_DIRS: [&str; 4] = ["sets", "sources", "vectors", "expected"];
|
||||
|
||||
/// One generated vector: a group, a name, its bytes and extension, and what it pins.
|
||||
pub struct Vector {
|
||||
|
|
@ -67,6 +89,7 @@ impl Vector {
|
|||
}
|
||||
|
||||
/// Every vector of the corpus, in group order.
|
||||
#[cfg(feature = "author")]
|
||||
pub fn all_vectors() -> Vec<Vector> {
|
||||
let mut v = Vec::new();
|
||||
v.extend(arith_vectors::vectors());
|
||||
|
|
@ -76,6 +99,7 @@ pub fn all_vectors() -> Vec<Vector> {
|
|||
v.extend(cost::vectors());
|
||||
v.extend(raster::vectors());
|
||||
v.extend(motion::vectors());
|
||||
v.extend(text::vectors());
|
||||
v
|
||||
}
|
||||
|
||||
|
|
@ -83,9 +107,10 @@ pub fn all_vectors() -> Vec<Vector> {
|
|||
pub const INPUT_GROUPS: [&str; 2] = ["arith", "flatten"];
|
||||
|
||||
/// The groups whose expected files state every frame's digest.
|
||||
pub const FRAME_GROUPS: [&str; 6] = ["fills", "runs", "composite", "motion", "clock", "slicing"];
|
||||
pub const FRAME_GROUPS: [&str; 7] = ["fills", "runs", "composite", "motion", "clock", "slicing", "text"];
|
||||
|
||||
/// A resolver holding every set under `sets/`.
|
||||
#[cfg(feature = "author")]
|
||||
pub fn corpus_store() -> SetStore {
|
||||
let mut s = SetStore::new();
|
||||
for (_, b) in sets::corpus_sets() {
|
||||
|
|
@ -95,6 +120,7 @@ pub fn corpus_store() -> SetStore {
|
|||
}
|
||||
|
||||
/// The vector of this name in this group.
|
||||
#[cfg(feature = "author")]
|
||||
pub fn find(group: &str, name: &str) -> Option<Vector> {
|
||||
all_vectors().into_iter().find(|v| v.group == group && v.name == name)
|
||||
}
|
||||
|
|
@ -264,6 +290,7 @@ pub fn form_of(bytes: &[u8]) -> String {
|
|||
}
|
||||
|
||||
/// `index.json`: each vector's name and what it pins, one per line.
|
||||
#[cfg(feature = "author")]
|
||||
fn index_text(vs: &[&Vector]) -> String {
|
||||
let mut m: BTreeMap<&str, &str> = BTreeMap::new();
|
||||
for v in vs {
|
||||
|
|
@ -285,9 +312,11 @@ fn index_text(vs: &[&Vector]) -> String {
|
|||
}
|
||||
|
||||
/// An entry of the tree-wide digest sidecar: its key, its buffer, and its digest fields.
|
||||
#[cfg(feature = "author")]
|
||||
type SidecarEntry = (String, String, Vec<(&'static str, Vec<String>)>);
|
||||
|
||||
/// The tree-wide digest sidecar: entries `(key, buffer, [(field, digests)])`.
|
||||
#[cfg(feature = "author")]
|
||||
fn sidecar_text(entries: &[SidecarEntry]) -> String {
|
||||
let mut s = String::from("{\n");
|
||||
for (i, (k, buffer, fields)) in entries.iter().enumerate() {
|
||||
|
|
@ -322,6 +351,7 @@ fn sidecar_text(entries: &[SidecarEntry]) -> String {
|
|||
}
|
||||
|
||||
/// Every file of the corpus and of the fuzz seeds, as `(path relative to wai/, bytes)`.
|
||||
#[cfg(feature = "author")]
|
||||
pub fn generate() -> Result<Vec<(String, Vec<u8>)>, String> {
|
||||
let mut out: Vec<(String, Vec<u8>)> = Vec::new();
|
||||
let mut store = SetStore::new();
|
||||
|
|
@ -329,6 +359,9 @@ pub fn generate() -> Result<Vec<(String, Vec<u8>)>, String> {
|
|||
store.add(bytes.clone());
|
||||
out.push((format!("{CORPUS}/sets/{name}.wos1"), bytes));
|
||||
}
|
||||
for (name, bytes) in sets::corpus_sources() {
|
||||
out.push((format!("{CORPUS}/sources/{name}.wsf1"), bytes));
|
||||
}
|
||||
let vs = all_vectors();
|
||||
let mut groups: Vec<&'static str> = Vec::new();
|
||||
for v in &vs {
|
||||
|
|
|
|||
470
wai/wai-vector/src/vectors/repertoire.rs
Normal file
470
wai/wai-vector/src/vectors/repertoire.rs
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
//! The test repertoire (`graphics-vector.md` §4): Arabic, Hebrew and Devanagari glyphs
|
||||
//! drawn here as integer centre lines, Apache-2.0, for the corpus only. It is never
|
||||
//! bundled with a sink and never a default for authors. It holds only what the test
|
||||
//! words need, and a small shaper for them: a joining table that picks Arabic
|
||||
//! contextual forms by Unicode joining type, the lam-alef ligature, Devanagari's
|
||||
//! reordered i-matra and one conjunct, and authored mark positions.
|
||||
//!
|
||||
//! **It is not a real shaper on a real font.** It lets the corpus pin right-to-left
|
||||
//! visual order, contextual forms, a conjunct, a reordered matra, marks and clusters,
|
||||
//! with no third-party font. The fetched-font job tests the real authoring path.
|
||||
//!
|
||||
//! The test words: Arabic بَيْت and سلام (initial, medial, final and isolated forms,
|
||||
//! lam-alef, fatha and sukun); Hebrew שלום (with the final mem); Devanagari हिंदी and
|
||||
//! हिन्दी (the i-matra before its consonant, the ii-matra, the anusvara above, and the
|
||||
//! conjunct न्द); and U+1F600, drawn as one monochrome glyph.
|
||||
//!
|
||||
//! Glyph IDs are the code points the glyphs draw where those fit 16 bits; the Arabic
|
||||
//! contextual forms take the code points of their presentation forms; the conjunct
|
||||
//! न्द is U+E000 and the U+1F600 face U+E001, in the private use area.
|
||||
//!
|
||||
//! Joins overlap the way real fonts' joins do: each joining form's connector runs to
|
||||
//! the edge of its advance, so its round cap passes the edge by the pen's radius and
|
||||
//! overlaps the neighbour's connector; each Devanagari headline does the same. Inside
|
||||
//! one run those overlaps over-cover their partly covered edge pixels (the knot of
|
||||
//! §3.13).
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::stroke::{Pen, RADIUS, UPEM2, glyph, source_notes};
|
||||
use crate::wsf1::{self, Glyph, Wsf1};
|
||||
|
||||
// Arabic contextual forms: (isolated, final, initial, medial).
|
||||
pub const BEH: [u16; 4] = [0xFE8F, 0xFE90, 0xFE91, 0xFE92];
|
||||
pub const TEH: [u16; 4] = [0xFE95, 0xFE96, 0xFE97, 0xFE98];
|
||||
pub const SEEN: [u16; 4] = [0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4];
|
||||
pub const LAM: [u16; 4] = [0xFEDD, 0xFEDE, 0xFEDF, 0xFEE0];
|
||||
pub const MEEM: [u16; 4] = [0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4];
|
||||
pub const YEH: [u16; 4] = [0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4];
|
||||
pub const ALEF: [u16; 2] = [0xFE8D, 0xFE8E];
|
||||
pub const LAM_ALEF: [u16; 2] = [0xFEFB, 0xFEFC];
|
||||
pub const FATHA: u16 = 0x064E;
|
||||
pub const SUKUN: u16 = 0x0652;
|
||||
pub const CONJUNCT_NDA: u16 = 0xE000;
|
||||
pub const FACE: u16 = 0xE001;
|
||||
pub const SPACE: u16 = 0x0020;
|
||||
|
||||
/// A letter's Unicode joining type, as far as the test words need it.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Joining {
|
||||
/// Dual-joining: joins on both sides.
|
||||
Dual,
|
||||
/// Right-joining: joins only to the letter before it.
|
||||
Right,
|
||||
}
|
||||
|
||||
/// The joining table: each letter of the test words, its joining type, and its forms
|
||||
/// (isolated, final, initial, medial; a right-joining letter has no initial or medial
|
||||
/// form, and repeats its isolated and final ones).
|
||||
pub const JOINING: [(char, Joining, [u16; 4]); 7] = [
|
||||
('\u{0627}', Joining::Right, [ALEF[0], ALEF[1], ALEF[0], ALEF[1]]),
|
||||
('\u{0628}', Joining::Dual, BEH),
|
||||
('\u{062A}', Joining::Dual, TEH),
|
||||
('\u{0633}', Joining::Dual, SEEN),
|
||||
('\u{0644}', Joining::Dual, LAM),
|
||||
('\u{0645}', Joining::Dual, MEEM),
|
||||
('\u{064A}', Joining::Dual, YEH),
|
||||
];
|
||||
|
||||
const HL: i32 = -1300;
|
||||
const J: i32 = 0;
|
||||
|
||||
/// Where a mark above or below a base sits, in the base's outline units: the x of its
|
||||
/// main stroke.
|
||||
pub fn anchor(base: u16) -> i32 {
|
||||
match base {
|
||||
0xFE91 | 0xFE97 | 0xFEF3 => 360,
|
||||
0xFE92 | 0xFE98 | 0xFEF4 => 260,
|
||||
0xFE8F | 0xFE90 | 0xFE95 | 0xFE96 => 480,
|
||||
0x0939 => 760,
|
||||
0x0926 => 480,
|
||||
_ => 300,
|
||||
}
|
||||
}
|
||||
|
||||
fn dots(p: &mut Pen, x: i32, y: i32, n: i32) {
|
||||
if n == 1 {
|
||||
p.dot(x, y);
|
||||
} else {
|
||||
p.dot(x - 120, y).dot(x + 120, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// The beh family's forms (beh, teh, yeh share the tooth), with `n` dots above (`up`)
|
||||
/// or below.
|
||||
fn beh_like(forms: [u16; 4], n: i32, up: bool, yeh: bool) -> Vec<Glyph> {
|
||||
let dy = |base: i32| if up { -620 } else { base };
|
||||
let mut v = Vec::new();
|
||||
// Isolated and final: the bowl (beh, teh) or the swooping tail (yeh).
|
||||
if yeh {
|
||||
v.push(glyph(forms[0], 960, |p| {
|
||||
let (x, y) = Pen::at(640, -380, 160, 160, 60);
|
||||
p.m(x, y).arc(640, -380, 160, 160, 60, 24).arc(460, -20, 380, 260, -8, 36);
|
||||
dots(p, 460, 420, n);
|
||||
}));
|
||||
v.push(glyph(forms[1], 960, |p| {
|
||||
p.m(960, J).arc(460, -20, 380, 260, 0, 36);
|
||||
dots(p, 460, 420, n);
|
||||
}));
|
||||
} else {
|
||||
v.push(glyph(forms[0], 960, |p| {
|
||||
let (x, y) = Pen::at(480, -300, 340, 300, 0);
|
||||
p.m(x, y).arc(480, -300, 340, 300, 0, 32);
|
||||
dots(p, 480, dy(300), n);
|
||||
}));
|
||||
v.push(glyph(forms[1], 960, |p| {
|
||||
p.m(960, J).l(480, J).arc(480, -300, 340, 300, 16, 32);
|
||||
dots(p, 480, dy(300), n);
|
||||
}));
|
||||
}
|
||||
// Initial and medial: the tooth.
|
||||
v.push(glyph(forms[2], 520, |p| {
|
||||
p.m(360, -440).l(360, J).l(0, J);
|
||||
dots(p, 360, dy(300), n);
|
||||
}));
|
||||
v.push(glyph(forms[3], 520, |p| {
|
||||
p.m(520, J).l(0, J).m(260, -440).l(260, J);
|
||||
dots(p, 260, dy(300), n);
|
||||
}));
|
||||
v
|
||||
}
|
||||
|
||||
/// Every glyph of the test repertoire, in glyph-ID order.
|
||||
pub fn repertoire_glyphs() -> Vec<Glyph> {
|
||||
let mut v: Vec<Glyph> = Vec::new();
|
||||
v.push(glyph(SPACE, 600, |_| {}));
|
||||
// Hebrew.
|
||||
v.push(glyph(0x05D5, 600, |p| {
|
||||
p.m(160, -1100).l(380, -1100).l(380, 0);
|
||||
}));
|
||||
v.push(glyph(0x05DC, 1000, |p| {
|
||||
p.m(200, -1550).l(200, -1050).l(820, -1050).l(820, -600).l(560, -200);
|
||||
}));
|
||||
v.push(glyph(0x05DD, 1060, |p| {
|
||||
p.m(160, -1100).l(900, -1100).l(900, 0).l(160, 0).l(160, -1100);
|
||||
}));
|
||||
v.push(glyph(0x05E9, 1260, |p| {
|
||||
p.m(160, -1100).l(300, 0).l(1000, 0).l(1100, -1100).m(640, -1100).l(600, 0);
|
||||
}));
|
||||
// Arabic marks, zero advance, drawn about x = 0.
|
||||
v.push(glyph(FATHA, 0, |p| {
|
||||
p.m(-120, -620).l(120, -760);
|
||||
}));
|
||||
v.push(glyph(SUKUN, 0, |p| {
|
||||
p.ell(0, -720, 130, 120);
|
||||
}));
|
||||
// Devanagari.
|
||||
v.push(glyph(0x0902, 0, |p| {
|
||||
p.dot(0, HL - 240);
|
||||
}));
|
||||
v.push(glyph(0x0926, 940, |p| {
|
||||
p.m(0, HL).l(940, HL).m(400, HL).l(400, -880).arc(520, -560, 300, 320, 44, 80).l(280, -40);
|
||||
}));
|
||||
v.push(glyph(0x0939, 1000, |p| {
|
||||
p.m(0, HL).l(1000, HL).m(380, HL).l(380, -980).l(640, -980).arc(640, -820, 180, 160, 48, 80).l(360, -660).l(740, -200).arc(620, -200, 120, 140, 0, 30);
|
||||
}));
|
||||
v.push(glyph(0x093F, 400, |p| {
|
||||
p.m(0, HL).l(400, HL).m(200, 0).l(200, HL).arc(520, HL, 320, 300, 32, 58);
|
||||
}));
|
||||
v.push(glyph(0x0940, 400, |p| {
|
||||
p.m(0, HL).l(400, HL).m(200, 0).l(200, HL).arc(-120, HL, 320, 300, 64, 38);
|
||||
}));
|
||||
v.push(glyph(CONJUNCT_NDA, 1300, |p| {
|
||||
p.m(0, HL).l(1300, HL).m(320, HL).l(320, -760).l(120, -760);
|
||||
p.m(760, HL).l(760, -880).arc(880, -560, 300, 320, 44, 80).l(640, -40);
|
||||
}));
|
||||
// The U+1F600 face: one monochrome outline glyph.
|
||||
v.push(glyph(FACE, 1640, |p| {
|
||||
p.ell(820, -600, 640, 640).dot(600, -820).dot(1040, -820);
|
||||
let (x, y) = Pen::at(820, -600, 400, 340, 4);
|
||||
p.m(x, y).arc(820, -600, 400, 340, 4, 28);
|
||||
}));
|
||||
// Arabic letters.
|
||||
v.push(glyph(ALEF[0], 480, |p| {
|
||||
p.m(240, -1500).l(240, J);
|
||||
}));
|
||||
v.push(glyph(ALEF[1], 480, |p| {
|
||||
p.m(240, -1500).l(240, J).l(480, J);
|
||||
}));
|
||||
v.extend(beh_like(BEH, 1, false, false));
|
||||
v.extend(beh_like(TEH, 2, true, false));
|
||||
// Seen: three teeth, and the bowl in the isolated and final forms.
|
||||
v.push(glyph(SEEN[0], 1400, |p| {
|
||||
p.m(1240, -360).l(1240, J).l(720, J).arc(400, J, 320, 260, 0, 36);
|
||||
p.m(1000, -360).l(1000, J);
|
||||
p.m(760, -360).l(760, J);
|
||||
}));
|
||||
v.push(glyph(SEEN[1], 1500, |p| {
|
||||
p.m(1500, J).l(720, J).arc(400, J, 320, 260, 0, 36);
|
||||
p.m(1300, -360).l(1300, J);
|
||||
p.m(1060, -360).l(1060, J);
|
||||
p.m(820, -360).l(820, J);
|
||||
}));
|
||||
v.push(glyph(SEEN[2], 900, |p| {
|
||||
p.m(740, -360).l(740, J).l(0, J);
|
||||
p.m(500, -360).l(500, J);
|
||||
p.m(260, -360).l(260, J);
|
||||
}));
|
||||
v.push(glyph(SEEN[3], 900, |p| {
|
||||
p.m(900, J).l(0, J);
|
||||
p.m(680, -360).l(680, J);
|
||||
p.m(450, -360).l(450, J);
|
||||
p.m(220, -360).l(220, J);
|
||||
}));
|
||||
// Lam.
|
||||
v.push(glyph(LAM[0], 900, |p| {
|
||||
p.m(760, -1500).l(760, -200).arc(440, -200, 320, 260, 0, 36);
|
||||
}));
|
||||
v.push(glyph(LAM[1], 920, |p| {
|
||||
p.m(760, -1500).l(760, -200).arc(440, -200, 320, 260, 0, 36);
|
||||
p.m(920, J).l(645, J);
|
||||
}));
|
||||
v.push(glyph(LAM[2], 520, |p| {
|
||||
p.m(360, -1500).l(360, J).l(0, J);
|
||||
}));
|
||||
v.push(glyph(LAM[3], 520, |p| {
|
||||
p.m(520, J).l(0, J).m(260, -1500).l(260, J);
|
||||
}));
|
||||
// Meem.
|
||||
v.push(glyph(MEEM[0], 760, |p| {
|
||||
p.ell(480, -170, 190, 170).m(290, -170).l(270, 480);
|
||||
}));
|
||||
v.push(glyph(MEEM[1], 860, |p| {
|
||||
p.m(860, J).l(480, J).ell(480, -170, 190, 170).m(290, -170).l(270, 480);
|
||||
}));
|
||||
v.push(glyph(MEEM[2], 700, |p| {
|
||||
p.ell(480, -170, 190, 170).m(480, J).l(0, J);
|
||||
}));
|
||||
v.push(glyph(MEEM[3], 760, |p| {
|
||||
p.m(760, J).l(0, J).ell(420, -170, 180, 170);
|
||||
}));
|
||||
v.extend(beh_like(YEH, 2, false, true));
|
||||
// Lam-alef, isolated and final.
|
||||
v.push(glyph(LAM_ALEF[0], 1000, |p| {
|
||||
p.m(820, -1500).l(820, -200).arc(560, -200, 260, 240, 0, 16).l(200, -1300);
|
||||
}));
|
||||
v.push(glyph(LAM_ALEF[1], 1100, |p| {
|
||||
p.m(820, -1500).l(820, -200).arc(560, -200, 260, 240, 0, 16).l(200, -1300);
|
||||
p.m(1100, J).l(560, 40);
|
||||
}));
|
||||
v.sort_by_key(|g| g.gid);
|
||||
v
|
||||
}
|
||||
|
||||
/// The test repertoire's source.
|
||||
pub fn repertoire_source() -> Wsf1 {
|
||||
Wsf1 {
|
||||
upem2: UPEM2,
|
||||
radius: RADIUS,
|
||||
glyphs: repertoire_glyphs(),
|
||||
notes: source_notes(
|
||||
"drawn in this repository as integer centre lines (wai-vector/src/vectors/repertoire.rs) for the conformance corpus only; no outline is traced from any font; a test repertoire, never a default for authors",
|
||||
"the code points each glyph draws where they fit 16 bits; Arabic contextual forms at their presentation-form code points; U+E000 the conjunct na-da; U+E001 the U+1F600 face",
|
||||
"1550,-480,1300,1000",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repertoire_source_bytes() -> Vec<u8> {
|
||||
repertoire_source().write()
|
||||
}
|
||||
|
||||
pub fn repertoire_set_bytes() -> Vec<u8> {
|
||||
wsf1::expand(&repertoire_source_bytes()).expect("the test repertoire's source passes Annex A's checks").write()
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ shaping
|
||||
|
||||
/// One shaped glyph in logical order: its ID, the byte range of its cluster in the
|
||||
/// string, and, for a mark, the index of its base among the shaped glyphs.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Shaped {
|
||||
pub gid: u16,
|
||||
/// The cluster this glyph belongs to: an index into the clusters, in logical order.
|
||||
pub cluster: usize,
|
||||
pub base: Option<usize>,
|
||||
}
|
||||
|
||||
/// The clusters of a shaped string, in logical order: `(byte_first, byte_len)`.
|
||||
pub type Clusters = Vec<(u32, u32)>;
|
||||
|
||||
/// A run's glyph records, `(gid, x, y)`, and its clusters, `(glyph_count, byte_first,
|
||||
/// byte_len)`, as a run item takes them.
|
||||
pub type RunGlyphs = (Vec<(u16, i32, i32)>, Vec<(u16, u32, u32)>);
|
||||
|
||||
fn joining(c: char) -> Option<(Joining, [u16; 4])> {
|
||||
JOINING.iter().find(|(l, _, _)| *l == c).map(|&(_, j, f)| (j, f))
|
||||
}
|
||||
|
||||
fn is_arabic_mark(c: char) -> bool {
|
||||
matches!(c, '\u{064E}' | '\u{0652}')
|
||||
}
|
||||
|
||||
/// Shape an Arabic string of the test words: contextual forms by joining type, the
|
||||
/// lam-alef ligature, and each mark in its base's cluster. A character outside the
|
||||
/// table (a space) breaks joining and maps to its own glyph.
|
||||
pub fn shape_arabic(s: &str) -> (Vec<Shaped>, Clusters) {
|
||||
let chars: Vec<(usize, char)> = s.char_indices().collect();
|
||||
let bases: Vec<usize> = (0..chars.len()).filter(|&i| !is_arabic_mark(chars[i].1)).collect();
|
||||
let mut out: Vec<Shaped> = Vec::new();
|
||||
let mut clusters: Clusters = Vec::new();
|
||||
let mut b = 0usize;
|
||||
while b < bases.len() {
|
||||
let i = bases[b];
|
||||
let c = chars[i].1;
|
||||
let prev = b.checked_sub(1).and_then(|p| joining(chars[bases[p]].1));
|
||||
let next = bases.get(b + 1).and_then(|&n| joining(chars[n].1));
|
||||
let me = joining(c);
|
||||
let joins_prev = matches!((prev, me), (Some((Joining::Dual, _)), Some(_)));
|
||||
// Lam followed by alef is one ligature glyph, joined to the letter before it.
|
||||
let lam_alef = c == '\u{0644}' && bases.get(b + 1).is_some_and(|&n| chars[n].1 == '\u{0627}');
|
||||
let (gid, consumed) = if lam_alef {
|
||||
(if joins_prev { LAM_ALEF[1] } else { LAM_ALEF[0] }, 2)
|
||||
} else if let Some((j, forms)) = me {
|
||||
let joins_next = j == Joining::Dual && next.is_some();
|
||||
(forms[usize::from(joins_prev) + 2 * usize::from(joins_next)], 1)
|
||||
} else {
|
||||
(c as u16, 1)
|
||||
};
|
||||
// The cluster runs from this base to the next base not consumed.
|
||||
let start = chars[i].0;
|
||||
let end = bases.get(b + consumed).map_or(s.len(), |&n| chars[n].0);
|
||||
let k = clusters.len();
|
||||
clusters.push((start as u32, (end - start) as u32));
|
||||
let base_index = out.len();
|
||||
out.push(Shaped { gid, cluster: k, base: None });
|
||||
for &(_, m) in chars.iter().filter(|(o, m)| *o > start && *o < end && is_arabic_mark(*m)) {
|
||||
out.push(Shaped { gid: m as u16, cluster: k, base: Some(base_index) });
|
||||
}
|
||||
b += consumed;
|
||||
}
|
||||
(out, clusters)
|
||||
}
|
||||
|
||||
/// A Hebrew string: one glyph per character, one cluster each.
|
||||
pub fn shape_hebrew(s: &str) -> (Vec<Shaped>, Clusters) {
|
||||
let mut out = Vec::new();
|
||||
let mut clusters = Vec::new();
|
||||
for (o, c) in s.char_indices() {
|
||||
out.push(Shaped { gid: c as u16, cluster: clusters.len(), base: None });
|
||||
clusters.push((o as u32, c.len_utf8() as u32));
|
||||
}
|
||||
(out, clusters)
|
||||
}
|
||||
|
||||
/// A Devanagari string of the test words, by syllable: a consonant, or na + virama +
|
||||
/// da as the conjunct; then the i-matra, drawn before the consonant; the ii-matra after
|
||||
/// it; and the anusvara above it. Each syllable is one cluster.
|
||||
pub fn shape_devanagari(s: &str) -> (Vec<Shaped>, Clusters) {
|
||||
let chars: Vec<(usize, char)> = s.char_indices().collect();
|
||||
let mut out = Vec::new();
|
||||
let mut clusters: Clusters = Vec::new();
|
||||
let mut i = 0usize;
|
||||
while i < chars.len() {
|
||||
let start = chars[i].0;
|
||||
let k = clusters.len();
|
||||
if chars[i].1 == ' ' {
|
||||
out.push(Shaped { gid: SPACE, cluster: k, base: None });
|
||||
clusters.push((start as u32, 1));
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
// The base: a consonant, or the conjunct.
|
||||
let base_gid = if chars[i].1 == '\u{0928}' && chars.get(i + 1).map(|c| c.1) == Some('\u{094D}') && chars.get(i + 2).map(|c| c.1) == Some('\u{0926}') {
|
||||
i += 3;
|
||||
CONJUNCT_NDA
|
||||
} else {
|
||||
i += 1;
|
||||
chars[i - 1].1 as u16
|
||||
};
|
||||
let mut pre = None;
|
||||
let mut post = Vec::new();
|
||||
while let Some(&(_, c)) = chars.get(i) {
|
||||
match c {
|
||||
'\u{093F}' => pre = Some(c as u16),
|
||||
'\u{0940}' | '\u{0902}' => post.push(c as u16),
|
||||
_ => break,
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let end = chars.get(i).map_or(s.len(), |c| c.0);
|
||||
clusters.push((start as u32, (end - start) as u32));
|
||||
if let Some(m) = pre {
|
||||
out.push(Shaped { gid: m, cluster: k, base: None });
|
||||
}
|
||||
let base_index = out.len();
|
||||
out.push(Shaped { gid: base_gid, cluster: k, base: None });
|
||||
// The ii-matra advances; the anusvara is a mark on the base.
|
||||
for m in post.iter().copied().filter(|&m| m == 0x0940) {
|
||||
out.push(Shaped { gid: m, cluster: k, base: None });
|
||||
}
|
||||
for m in post.iter().copied().filter(|&m| m == 0x0902) {
|
||||
out.push(Shaped { gid: m, cluster: k, base: Some(base_index) });
|
||||
}
|
||||
}
|
||||
(out, clusters)
|
||||
}
|
||||
|
||||
/// The glyphs of a run in visual order, `(gid, x, y)` in outline units from the run's
|
||||
/// origin, and its clusters in glyph order `(glyph_count, byte_first, byte_len)`, from
|
||||
/// shaped glyphs in logical order. A right-to-left run reverses the clusters; within a
|
||||
/// cluster a mark keeps its base's position plus its anchor, and the other glyphs
|
||||
/// advance.
|
||||
pub fn lay_out(set: &Wsf1, shaped: &[Shaped], clusters: &Clusters, rtl: bool, byte_offset: u32) -> RunGlyphs {
|
||||
let mut order: Vec<usize> = (0..clusters.len()).collect();
|
||||
if rtl {
|
||||
order.reverse();
|
||||
}
|
||||
let adv = |g: u16| i32::from(set.glyph(g).map_or(0, |g| g.advance));
|
||||
let mut glyphs = Vec::new();
|
||||
let mut runs = Vec::new();
|
||||
let mut pen = 0i32;
|
||||
for k in order {
|
||||
let members: Vec<usize> = (0..shaped.len()).filter(|&i| shaped[i].cluster == k).collect();
|
||||
// Non-marks first, in logical order (an RTL cluster's bases reversed), then marks.
|
||||
let mut bases: Vec<usize> = members.iter().copied().filter(|&i| shaped[i].base.is_none()).collect();
|
||||
if rtl {
|
||||
bases.reverse();
|
||||
}
|
||||
let mut at: Vec<(usize, i32)> = Vec::new();
|
||||
for &i in &bases {
|
||||
at.push((i, pen));
|
||||
glyphs.push((shaped[i].gid, pen, 0));
|
||||
pen += adv(shaped[i].gid);
|
||||
}
|
||||
for &i in members.iter().filter(|&&i| shaped[i].base.is_some()) {
|
||||
let b = shaped[i].base.unwrap_or(i);
|
||||
let bx = at.iter().find(|(j, _)| *j == b).map_or(pen, |&(_, x)| x);
|
||||
let ax = anchor(shaped[b].gid);
|
||||
glyphs.push((shaped[i].gid, bx + ax, 0));
|
||||
}
|
||||
runs.push((members.len() as u16, clusters[k].0 + byte_offset, clusters[k].1));
|
||||
}
|
||||
(glyphs, runs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
|
||||
#[test]
|
||||
fn the_joining_table_picks_the_forms_of_the_test_words() {
|
||||
// The expected IDs are Unicode's code points for each form, written out.
|
||||
for (word, want) in super::super::text::test_words() {
|
||||
assert_eq!(shape_arabic(word).0.iter().map(|g| g.gid).collect::<Vec<u16>>(), want, "{word}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clusters_follow_the_syllables() {
|
||||
let (g, c) = shape_devanagari("\u{0939}\u{093F}\u{0902}\u{0926}\u{0940}");
|
||||
assert_eq!(g.iter().map(|g| g.gid).collect::<Vec<u16>>(), vec![0x093F, 0x0939, 0x0902, 0x0926, 0x0940]);
|
||||
assert_eq!(c, vec![(0, 9), (9, 6)]);
|
||||
let (g, c) = shape_devanagari("\u{0939}\u{093F}\u{0928}\u{094D}\u{0926}\u{0940}");
|
||||
assert_eq!(g.iter().map(|g| g.gid).collect::<Vec<u16>>(), vec![0x093F, 0x0939, CONJUNCT_NDA, 0x0940]);
|
||||
assert_eq!(c, vec![(0, 6), (6, 12)]);
|
||||
let (_, c) = shape_arabic("\u{0633}\u{0644}\u{0627}\u{0645}");
|
||||
assert_eq!(c, vec![(0, 2), (2, 4), (6, 2)]);
|
||||
}
|
||||
}
|
||||
|
|
@ -88,13 +88,28 @@ pub fn set_bad_notes() -> Vec<u8> {
|
|||
w.write()
|
||||
}
|
||||
|
||||
/// The sets under `sets/`, by name.
|
||||
/// The sets under `sets/`, by name: the synthetic sets drawn here from formulas, and
|
||||
/// the two in-house sets expanded from their stroke sources by Annex A.
|
||||
#[cfg(feature = "author")]
|
||||
pub fn corpus_sets() -> Vec<(&'static str, Vec<u8>)> {
|
||||
vec![("synthetic-a", set_a().write()), ("synthetic-b", set_b().write()), ("synthetic-bad-notes", set_bad_notes())]
|
||||
vec![
|
||||
("stroke-ascii", super::stroke::ascii_set_bytes()),
|
||||
("synthetic-a", set_a().write()),
|
||||
("synthetic-b", set_b().write()),
|
||||
("synthetic-bad-notes", set_bad_notes()),
|
||||
("test-repertoire", super::repertoire::repertoire_set_bytes()),
|
||||
]
|
||||
}
|
||||
|
||||
/// The stroke sources under `sources/`, by name.
|
||||
#[cfg(feature = "author")]
|
||||
pub fn corpus_sources() -> Vec<(&'static str, Vec<u8>)> {
|
||||
vec![("stroke-ascii", super::stroke::ascii_source_bytes()), ("test-repertoire", super::repertoire::repertoire_source_bytes())]
|
||||
}
|
||||
|
||||
/// The fuzz seeds under `wai-vector/fuzz/seeds/`: a payload that uses every table,
|
||||
/// and an outline set with every point tag.
|
||||
#[cfg(feature = "author")]
|
||||
pub fn fuzz_seeds() -> Vec<(String, Vec<u8>)> {
|
||||
vec![(String::from("every-table.wvg1"), super::canonical::every_table().bytes()), (String::from("set-a.wos1"), set_a().write())]
|
||||
}
|
||||
|
|
|
|||
451
wai/wai-vector/src/vectors/stroke.rs
Normal file
451
wai/wai-vector/src/vectors/stroke.rs
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
//! The default stroke set (`graphics-vector.md` §4, Annexes A and B): printable ASCII,
|
||||
//! U+0020–U+007E, drawn here as integer centre lines and expanded into a `WOS1` by
|
||||
//! Annex A. Apache-2.0, like the rest of this repository.
|
||||
//!
|
||||
//! **The drawing source.** Every glyph below is drawn here from original centre lines:
|
||||
//! straight segments, and arcs whose points are ellipse points taken from Annex A's
|
||||
//! pinned table of unit vectors, so the drawing is integers throughout. No outline is
|
||||
//! traced from any font. The set's notes say so, and carry the licence text.
|
||||
//!
|
||||
//! Outline units: an em is 2,000 (so `upem2` = 2000); the baseline is y = 0 and y points
|
||||
//! down; caps are 1,400 tall, the x-height 1,000, ascenders 1,480 and descenders 440. The
|
||||
//! pen's radius is 80, so stems are 160 wide. A glyph's ID is its code point, and its
|
||||
//! advance is in the source for the authoring tool's layout.
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::arith::r_half_up;
|
||||
use crate::json::hex;
|
||||
use crate::sha256::sha256;
|
||||
use crate::wsf1::{self, Glyph, UNIT, Wsf1};
|
||||
|
||||
/// The pen radius of the in-house sets, in outline units.
|
||||
pub const RADIUS: u16 = 80;
|
||||
/// Twice the units per em.
|
||||
pub const UPEM2: u16 = 2000;
|
||||
|
||||
/// The licence text the in-house sets carry in their notes: the Apache License,
|
||||
/// Version 2.0, as the repository states it.
|
||||
pub const LICENCE: &str = include_str!("../../../../LICENSE");
|
||||
|
||||
/// A centre-line builder: `m` starts a stroke path, `l` draws to a point, `arc` draws
|
||||
/// through ellipse points from Annex A's table, and `dot` is a one-point stroke.
|
||||
#[derive(Default)]
|
||||
pub struct Pen {
|
||||
pub strokes: Vec<Vec<(i16, i16)>>,
|
||||
}
|
||||
|
||||
impl Pen {
|
||||
pub fn m(&mut self, x: i32, y: i32) -> &mut Pen {
|
||||
self.strokes.push(vec![(x as i16, y as i16)]);
|
||||
self
|
||||
}
|
||||
/// A point, unless it equals the last one.
|
||||
pub fn l(&mut self, x: i32, y: i32) -> &mut Pen {
|
||||
let p = (x as i16, y as i16);
|
||||
match self.strokes.last_mut() {
|
||||
Some(s) if s.last() == Some(&p) => {}
|
||||
Some(s) => s.push(p),
|
||||
None => self.strokes.push(vec![p]),
|
||||
}
|
||||
self
|
||||
}
|
||||
/// The ellipse point of table direction `k` (any integer, taken mod 64).
|
||||
pub fn at(cx: i32, cy: i32, rx: i32, ry: i32, k: i32) -> (i32, i32) {
|
||||
let u = UNIT[k.rem_euclid(64) as usize];
|
||||
(cx + r_half_up(i64::from(rx) * i64::from(u.0), 15) as i32, cy + r_half_up(i64::from(ry) * i64::from(u.1), 15) as i32)
|
||||
}
|
||||
/// Ellipse points from direction `k0` to `k1` in steps of `step` table directions,
|
||||
/// `k1` always included; drawn on from the current point.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn arc_s(&mut self, cx: i32, cy: i32, rx: i32, ry: i32, k0: i32, k1: i32, step: i32) -> &mut Pen {
|
||||
let s = if k1 >= k0 { step } else { -step };
|
||||
let mut k = k0;
|
||||
loop {
|
||||
let (x, y) = Pen::at(cx, cy, rx, ry, k);
|
||||
self.l(x, y);
|
||||
if k == k1 {
|
||||
break;
|
||||
}
|
||||
k = if (k1 - k).abs() <= step { k1 } else { k + s };
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn arc(&mut self, cx: i32, cy: i32, rx: i32, ry: i32, k0: i32, k1: i32) -> &mut Pen {
|
||||
self.arc_s(cx, cy, rx, ry, k0, k1, 2)
|
||||
}
|
||||
/// A closed ellipse, as its own stroke path.
|
||||
pub fn ell(&mut self, cx: i32, cy: i32, rx: i32, ry: i32) -> &mut Pen {
|
||||
let (x, y) = Pen::at(cx, cy, rx, ry, 0);
|
||||
self.m(x, y).arc(cx, cy, rx, ry, 0, 64)
|
||||
}
|
||||
pub fn dot(&mut self, x: i32, y: i32) -> &mut Pen {
|
||||
self.m(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
/// A glyph from its ID, its advance and a drawing.
|
||||
pub fn glyph(gid: u16, advance: u16, draw: impl FnOnce(&mut Pen)) -> Glyph {
|
||||
let mut p = Pen::default();
|
||||
draw(&mut p);
|
||||
Glyph { gid, advance, strokes: p.strokes }
|
||||
}
|
||||
|
||||
/// The notes of an in-house source: its key lines, a blank line, then the licence text.
|
||||
pub fn source_notes(source: &str, glyphs: &str, metrics: &str) -> Vec<u8> {
|
||||
let mut s = String::from("licence=Apache-2.0\nlicence_sha256=");
|
||||
s.push_str(&hex(&sha256(LICENCE.as_bytes())));
|
||||
s.push_str("\ncopyright=Copyright (c) 2026 Transaction Science, Inc.\nrfn=none\nsource=");
|
||||
s.push_str(source);
|
||||
s.push_str("\nglyphs=");
|
||||
s.push_str(glyphs);
|
||||
s.push_str("\nmetrics=");
|
||||
s.push_str(metrics);
|
||||
s.push_str("\n\n");
|
||||
s.push_str(LICENCE);
|
||||
s.into_bytes()
|
||||
}
|
||||
|
||||
// Vertical metrics, outline units, y down.
|
||||
const CAP: i32 = -1400;
|
||||
const XH: i32 = -1000;
|
||||
const ASC: i32 = -1480;
|
||||
const DSC: i32 = 440;
|
||||
/// A dot's centre sits one radius above the baseline, so its ink rests on it.
|
||||
const DOT: i32 = -80;
|
||||
|
||||
/// Every glyph of the stroke set, in code-point order.
|
||||
pub fn ascii_glyphs() -> Vec<Glyph> {
|
||||
let mut v: Vec<Glyph> = Vec::new();
|
||||
let mut g = |c: char, adv: u16, draw: &dyn Fn(&mut Pen)| v.push(glyph(c as u16, adv, draw));
|
||||
g(' ', 600, &|_| {});
|
||||
g('!', 520, &|p| {
|
||||
p.m(260, CAP).l(260, -440).dot(260, DOT);
|
||||
});
|
||||
g('"', 760, &|p| {
|
||||
p.m(240, CAP).l(240, -1000).m(520, CAP).l(520, -1000);
|
||||
});
|
||||
g('#', 1240, &|p| {
|
||||
p.m(460, -1300).l(380, -100).m(880, -1300).l(800, -100).m(180, -900).l(1080, -900).m(160, -500).l(1060, -500);
|
||||
});
|
||||
g('$', 1150, &|p| {
|
||||
p.m(Pen::at(575, -1000, 380, 300, 56).0, Pen::at(575, -1000, 380, 300, 56).1).arc(575, -1000, 380, 300, 56, 16).arc(575, -400, 400, 300, 48, 88);
|
||||
p.m(575, -1520).l(575, 120);
|
||||
});
|
||||
g('%', 1500, &|p| {
|
||||
p.ell(380, -1080, 220, 300).ell(1120, -320, 220, 300).m(1240, CAP).l(260, 0);
|
||||
});
|
||||
g('&', 1300, &|p| {
|
||||
p.m(1180, 0).l(470, -905).arc(640, -1110, 240, 290, 24, 72).arc(560, -330, 300, 330, 40, 8).l(1100, -560);
|
||||
});
|
||||
g('\'', 440, &|p| {
|
||||
p.m(220, CAP).l(220, -1000);
|
||||
});
|
||||
g('(', 640, &|p| {
|
||||
let (x, y) = Pen::at(840, -575, 580, 1000, 42);
|
||||
p.m(x, y).arc(840, -575, 580, 1000, 42, 22);
|
||||
});
|
||||
g(')', 640, &|p| {
|
||||
let (x, y) = Pen::at(-200, -575, 580, 1000, 54);
|
||||
p.m(x, y).arc(-200, -575, 580, 1000, 54, 74);
|
||||
});
|
||||
g('*', 900, &|p| {
|
||||
p.m(450, CAP).l(450, -800).m(190, -1250).l(710, -950).m(190, -950).l(710, -1250);
|
||||
});
|
||||
g('+', 1100, &|p| {
|
||||
p.m(550, -1000).l(550, -200).m(150, -600).l(950, -600);
|
||||
});
|
||||
g(',', 440, &|p| {
|
||||
p.m(260, -60).l(170, 240);
|
||||
});
|
||||
g('-', 760, &|p| {
|
||||
p.m(160, -560).l(600, -560);
|
||||
});
|
||||
g('.', 440, &|p| {
|
||||
p.dot(220, DOT);
|
||||
});
|
||||
g('/', 800, &|p| {
|
||||
p.m(140, 100).l(660, -1450);
|
||||
});
|
||||
g('0', 1120, &|p| {
|
||||
p.ell(560, -700, 420, 700);
|
||||
});
|
||||
g('1', 1120, &|p| {
|
||||
p.m(320, -1180).l(600, CAP).l(600, 0);
|
||||
});
|
||||
g('2', 1120, &|p| {
|
||||
let (x, y) = Pen::at(560, -1030, 400, 370, 38);
|
||||
p.m(x, y).arc(560, -1030, 400, 370, 38, 70).l(160, 0).l(980, 0);
|
||||
});
|
||||
g('3', 1120, &|p| {
|
||||
let (x, y) = Pen::at(550, -1060, 380, 340, 40);
|
||||
p.m(x, y).arc(550, -1060, 380, 340, 40, 80).arc(550, -360, 410, 360, 48, 88);
|
||||
});
|
||||
g('4', 1120, &|p| {
|
||||
p.m(820, 0).l(820, CAP).l(140, -440).l(1000, -440);
|
||||
});
|
||||
g('5', 1120, &|p| {
|
||||
p.m(920, CAP).l(250, CAP).arc(560, -420, 400, 420, 40, 88);
|
||||
});
|
||||
g('6', 1120, &|p| {
|
||||
let (x, y) = Pen::at(600, -700, 440, 700, 54);
|
||||
p.m(x, y).arc(600, -700, 440, 700, 54, 32).l(160, -450);
|
||||
p.ell(560, -450, 400, 450);
|
||||
});
|
||||
g('7', 1120, &|p| {
|
||||
p.m(140, CAP).l(980, CAP).l(420, 0);
|
||||
});
|
||||
g('8', 1120, &|p| {
|
||||
p.ell(560, -1060, 340, 340).ell(560, -360, 400, 360);
|
||||
});
|
||||
g('9', 1120, &|p| {
|
||||
p.ell(560, -950, 400, 450);
|
||||
p.m(960, -950).l(960, -700).arc(520, -700, 440, 700, 0, 22);
|
||||
});
|
||||
g(':', 440, &|p| {
|
||||
p.dot(220, DOT).dot(220, -920);
|
||||
});
|
||||
g(';', 440, &|p| {
|
||||
p.dot(220, -920).m(240, -60).l(150, 240);
|
||||
});
|
||||
g('<', 1100, &|p| {
|
||||
p.m(940, -1050).l(160, -600).l(940, -150);
|
||||
});
|
||||
g('=', 1100, &|p| {
|
||||
p.m(160, -800).l(940, -800).m(160, -400).l(940, -400);
|
||||
});
|
||||
g('>', 1100, &|p| {
|
||||
p.m(160, -1050).l(940, -600).l(160, -150);
|
||||
});
|
||||
g('?', 1000, &|p| {
|
||||
let (x, y) = Pen::at(500, -1080, 340, 320, 36);
|
||||
p.m(x, y).arc(500, -1080, 340, 320, 36, 72).l(500, -620).l(500, -440).dot(500, DOT);
|
||||
});
|
||||
g('@', 1700, &|p| {
|
||||
p.ell(800, -560, 240, 300);
|
||||
p.m(1040, -860).l(1040, -400).arc(1220, -400, 180, 200, 32, 0).arc(800, -560, 600, 760, 0, -54);
|
||||
});
|
||||
g('A', 1280, &|p| {
|
||||
p.m(140, 0).l(640, CAP).l(1140, 0).m(311, -480).l(969, -480);
|
||||
});
|
||||
g('B', 1250, &|p| {
|
||||
p.m(180, 0).l(180, CAP).l(680, CAP).arc(680, -1050, 350, 350, 48, 80).l(180, -700);
|
||||
p.m(180, -700).l(720, -700).arc(720, -350, 370, 350, 48, 80).l(180, 0);
|
||||
});
|
||||
g('C', 1300, &|p| {
|
||||
let (x, y) = Pen::at(700, -700, 540, 700, 56);
|
||||
p.m(x, y).arc(700, -700, 540, 700, 56, 8);
|
||||
});
|
||||
g('D', 1300, &|p| {
|
||||
p.m(180, 0).l(180, CAP).l(600, CAP).arc(600, -700, 520, 700, 48, 80).l(180, 0);
|
||||
});
|
||||
g('E', 1150, &|p| {
|
||||
p.m(1000, CAP).l(180, CAP).l(180, 0).l(1000, 0).m(180, -700).l(900, -700);
|
||||
});
|
||||
g('F', 1100, &|p| {
|
||||
p.m(1000, CAP).l(180, CAP).l(180, 0).m(180, -700).l(880, -700);
|
||||
});
|
||||
g('G', 1350, &|p| {
|
||||
let (x, y) = Pen::at(720, -700, 540, 700, 56);
|
||||
p.m(x, y).arc(720, -700, 540, 700, 56, 0).l(780, -700);
|
||||
});
|
||||
g('H', 1300, &|p| {
|
||||
p.m(180, 0).l(180, CAP).m(1120, 0).l(1120, CAP).m(180, -700).l(1120, -700);
|
||||
});
|
||||
g('I', 520, &|p| {
|
||||
p.m(260, 0).l(260, CAP);
|
||||
});
|
||||
g('J', 1000, &|p| {
|
||||
p.m(820, CAP).l(820, -420).arc(470, -420, 350, 420, 0, 26);
|
||||
});
|
||||
g('K', 1200, &|p| {
|
||||
p.m(180, 0).l(180, CAP).m(1060, CAP).l(180, -520).m(470, -810).l(1080, 0);
|
||||
});
|
||||
g('L', 1050, &|p| {
|
||||
p.m(180, CAP).l(180, 0).l(980, 0);
|
||||
});
|
||||
g('M', 1500, &|p| {
|
||||
p.m(180, 0).l(180, CAP).l(750, -300).l(1320, CAP).l(1320, 0);
|
||||
});
|
||||
g('N', 1300, &|p| {
|
||||
p.m(180, 0).l(180, CAP).l(1120, 0).l(1120, CAP);
|
||||
});
|
||||
g('O', 1420, &|p| {
|
||||
p.ell(710, -700, 550, 700);
|
||||
});
|
||||
g('P', 1200, &|p| {
|
||||
p.m(180, 0).l(180, CAP).l(680, CAP).arc(680, -1030, 400, 370, 48, 80).l(180, -660);
|
||||
});
|
||||
g('Q', 1420, &|p| {
|
||||
p.ell(710, -700, 550, 700).m(860, -260).l(1220, 100);
|
||||
});
|
||||
g('R', 1250, &|p| {
|
||||
p.m(180, 0).l(180, CAP).l(680, CAP).arc(680, -1030, 400, 370, 48, 80).l(180, -660);
|
||||
p.m(640, -660).l(1100, 0);
|
||||
});
|
||||
g('S', 1150, &|p| {
|
||||
let (x, y) = Pen::at(580, -1050, 420, 350, 56);
|
||||
p.m(x, y).arc(580, -1050, 420, 350, 56, 16).arc(580, -350, 440, 350, 48, 88);
|
||||
});
|
||||
g('T', 1150, &|p| {
|
||||
p.m(100, CAP).l(1050, CAP).m(575, CAP).l(575, 0);
|
||||
});
|
||||
g('U', 1300, &|p| {
|
||||
p.m(180, CAP).l(180, -480).arc(650, -480, 470, 480, 32, 0).l(1120, CAP);
|
||||
});
|
||||
g('V', 1250, &|p| {
|
||||
p.m(120, CAP).l(625, 0).l(1130, CAP);
|
||||
});
|
||||
g('W', 1700, &|p| {
|
||||
p.m(110, CAP).l(450, 0).l(850, -1100).l(1250, 0).l(1590, CAP);
|
||||
});
|
||||
g('X', 1200, &|p| {
|
||||
p.m(140, CAP).l(1060, 0).m(1060, CAP).l(140, 0);
|
||||
});
|
||||
g('Y', 1200, &|p| {
|
||||
p.m(120, CAP).l(600, -700).l(1080, CAP).m(600, -700).l(600, 0);
|
||||
});
|
||||
g('Z', 1150, &|p| {
|
||||
p.m(160, CAP).l(1000, CAP).l(160, 0).l(1000, 0);
|
||||
});
|
||||
g('[', 600, &|p| {
|
||||
p.m(480, -1450).l(220, -1450).l(220, 250).l(480, 250);
|
||||
});
|
||||
g('\\', 800, &|p| {
|
||||
p.m(140, -1450).l(660, 100);
|
||||
});
|
||||
g(']', 600, &|p| {
|
||||
p.m(120, -1450).l(380, -1450).l(380, 250).l(120, 250);
|
||||
});
|
||||
g('^', 1000, &|p| {
|
||||
p.m(180, -800).l(500, CAP).l(820, -800);
|
||||
});
|
||||
g('_', 1000, &|p| {
|
||||
p.m(80, 200).l(920, 200);
|
||||
});
|
||||
g('`', 600, &|p| {
|
||||
p.m(200, -1420).l(400, -1150);
|
||||
});
|
||||
g('a', 1080, &|p| {
|
||||
p.ell(520, -500, 360, 500).m(880, XH).l(880, 0);
|
||||
});
|
||||
g('b', 1120, &|p| {
|
||||
p.m(200, ASC).l(200, 0).ell(580, -500, 380, 500);
|
||||
});
|
||||
g('c', 1000, &|p| {
|
||||
let (x, y) = Pen::at(560, -500, 400, 500, 56);
|
||||
p.m(x, y).arc(560, -500, 400, 500, 56, 8);
|
||||
});
|
||||
g('d', 1120, &|p| {
|
||||
p.ell(540, -500, 380, 500).m(920, ASC).l(920, 0);
|
||||
});
|
||||
g('e', 1080, &|p| {
|
||||
p.m(160, -500).l(940, -500).arc(550, -500, 390, 500, 0, -56);
|
||||
});
|
||||
g('f', 700, &|p| {
|
||||
p.m(360, 0).l(360, -1180).arc(580, -1180, 220, 260, 32, 56).m(140, XH).l(640, XH);
|
||||
});
|
||||
g('g', 1100, &|p| {
|
||||
p.ell(520, -500, 360, 500).m(880, XH).l(880, 180).arc(520, 180, 360, 260, 0, 28);
|
||||
});
|
||||
g('h', 1120, &|p| {
|
||||
p.m(200, ASC).l(200, 0).m(200, -620).arc(560, -620, 360, 380, 32, 64).l(920, 0);
|
||||
});
|
||||
g('i', 480, &|p| {
|
||||
p.m(240, 0).l(240, XH).dot(240, -1300);
|
||||
});
|
||||
g('j', 520, &|p| {
|
||||
p.m(320, XH).l(320, 220).arc(100, 220, 220, 220, 0, 24).dot(320, -1300);
|
||||
});
|
||||
g('k', 1000, &|p| {
|
||||
p.m(200, ASC).l(200, 0).m(880, XH).l(200, -380).m(440, -599).l(900, 0);
|
||||
});
|
||||
g('l', 480, &|p| {
|
||||
p.m(240, ASC).l(240, 0);
|
||||
});
|
||||
g('m', 1420, &|p| {
|
||||
p.m(180, XH).l(180, 0);
|
||||
p.m(180, -640).arc(445, -640, 265, 360, 32, 64).l(710, 0);
|
||||
p.m(710, -640).arc(975, -640, 265, 360, 32, 64).l(1240, 0);
|
||||
});
|
||||
g('n', 1120, &|p| {
|
||||
p.m(200, XH).l(200, 0).m(200, -620).arc(560, -620, 360, 380, 32, 64).l(920, 0);
|
||||
});
|
||||
g('o', 1120, &|p| {
|
||||
p.ell(560, -500, 380, 500);
|
||||
});
|
||||
g('p', 1120, &|p| {
|
||||
p.m(200, XH).l(200, DSC).ell(580, -500, 380, 500);
|
||||
});
|
||||
g('q', 1120, &|p| {
|
||||
p.ell(540, -500, 380, 500).m(920, XH).l(920, DSC);
|
||||
});
|
||||
g('r', 760, &|p| {
|
||||
p.m(200, XH).l(200, 0).m(200, -600).arc(520, -600, 320, 400, 32, 56);
|
||||
});
|
||||
g('s', 940, &|p| {
|
||||
let (x, y) = Pen::at(470, -750, 330, 250, 56);
|
||||
p.m(x, y).arc(470, -750, 330, 250, 56, 16).arc(470, -250, 350, 250, 48, 88);
|
||||
});
|
||||
g('t', 720, &|p| {
|
||||
p.m(340, -1300).l(340, -180).arc(540, -180, 200, 180, 32, 8).m(120, XH).l(640, XH);
|
||||
});
|
||||
g('u', 1120, &|p| {
|
||||
p.m(200, XH).l(200, -380).arc(560, -380, 360, 380, 32, 0).m(920, XH).l(920, 0);
|
||||
});
|
||||
g('v', 1040, &|p| {
|
||||
p.m(120, XH).l(520, 0).l(920, XH);
|
||||
});
|
||||
g('w', 1500, &|p| {
|
||||
p.m(100, XH).l(400, 0).l(750, -800).l(1100, 0).l(1400, XH);
|
||||
});
|
||||
g('x', 1000, &|p| {
|
||||
p.m(140, XH).l(860, 0).m(860, XH).l(140, 0);
|
||||
});
|
||||
g('y', 1040, &|p| {
|
||||
p.m(120, XH).l(531, 0).m(920, XH).l(360, DSC);
|
||||
});
|
||||
g('z', 960, &|p| {
|
||||
p.m(160, XH).l(800, XH).l(160, 0).l(800, 0);
|
||||
});
|
||||
g('{', 700, &|p| {
|
||||
p.m(560, -1450).l(400, -1400).l(380, -700).l(220, -575).l(380, -450).l(400, 250).l(560, 300);
|
||||
});
|
||||
g('|', 480, &|p| {
|
||||
p.m(240, -1500).l(240, 300);
|
||||
});
|
||||
g('}', 700, &|p| {
|
||||
p.m(140, -1450).l(300, -1400).l(320, -700).l(480, -575).l(320, -450).l(300, 250).l(140, 300);
|
||||
});
|
||||
g('~', 1020, &|p| {
|
||||
let (x, y) = Pen::at(330, -560, 180, 110, 32);
|
||||
p.m(x, y).arc(330, -560, 180, 110, 32, 64).arc(690, -560, 180, 110, 32, 0);
|
||||
});
|
||||
v
|
||||
}
|
||||
|
||||
/// The stroke set's source.
|
||||
pub fn ascii_source() -> Wsf1 {
|
||||
Wsf1 {
|
||||
upem2: UPEM2,
|
||||
radius: RADIUS,
|
||||
glyphs: ascii_glyphs(),
|
||||
notes: source_notes(
|
||||
"drawn in this repository as integer centre lines (wai-vector/src/vectors/stroke.rs); no outline is traced from any font",
|
||||
"32-126; each glyph ID is the code point it draws",
|
||||
"1480,-440,1400,1000",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// The stroke set's `WSF1` bytes.
|
||||
pub fn ascii_source_bytes() -> Vec<u8> {
|
||||
ascii_source().write()
|
||||
}
|
||||
|
||||
/// The stroke set: its source expanded by Annex A.
|
||||
pub fn ascii_set_bytes() -> Vec<u8> {
|
||||
wsf1::expand(&ascii_source_bytes()).expect("the stroke set's source passes Annex A's checks").write()
|
||||
}
|
||||
328
wai/wai-vector/src/vectors/text.rs
Normal file
328
wai/wai-vector/src/vectors/text.rs
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
//! Vectors drawn from the in-house sets (`graphics-vector.md` §4): the `text` group,
|
||||
//! and the stroke-glyph members of `runs` and `motion`.
|
||||
//!
|
||||
//! `text` renders text runs from the ASCII stroke set and the test repertoire at two
|
||||
//! listed sizes each: Latin; Arabic and Hebrew right to left; Devanagari with a
|
||||
//! reordered matra, a conjunct and a mark; a mixed-direction line whose runs come from
|
||||
//! both sets; a block split over three runs and lines, with an inserted hyphen and a
|
||||
//! zero-glyph cluster; a ruby annotation; a vertical-rl block; and the U+1F600 face. The
|
||||
//! text layer's projection lands later; these vectors pin runs, clusters, blocks and
|
||||
//! frames.
|
||||
//!
|
||||
//! Both sets are held, found by SHA-256 among `sets/`. Every string here is written
|
||||
//! here.
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::Vector;
|
||||
use super::canonical::{TB, held};
|
||||
use super::raster::{BLUE, ONE, RED, WHITE, frame_doc};
|
||||
use super::repertoire::{self, FACE, lay_out, repertoire_source, shape_arabic, shape_devanagari, shape_hebrew};
|
||||
use super::stroke::ascii_source;
|
||||
use crate::arith::r_rat;
|
||||
use crate::scene::*;
|
||||
use crate::wsf1::Wsf1;
|
||||
|
||||
/// Q16 design subpixels per outline unit: `n` subpixels per unit.
|
||||
const fn us(n: u32) -> u32 {
|
||||
n * 65_536
|
||||
}
|
||||
|
||||
/// A glyph's outline-unit position to design subpixels, from an origin.
|
||||
fn place(glyphs: &[(u16, i32, i32)], origin: (i32, i32), unit_scale: u32) -> Vec<(u16, i32, i32)> {
|
||||
let s = i64::from(unit_scale);
|
||||
glyphs.iter().map(|&(g, x, y)| (g, origin.0 + r_rat(i64::from(x) * s, 65_536) as i32, origin.1 + r_rat(i64::from(y) * s, 65_536) as i32)).collect()
|
||||
}
|
||||
|
||||
/// A Latin run's glyph records and clusters, and its width in outline units.
|
||||
type Latin = (Vec<(u16, i32, i32)>, Vec<(u16, u32, u32)>, i32);
|
||||
|
||||
/// A Latin string in the stroke set: one glyph per byte, its ID the byte, advancing
|
||||
/// left to right; one cluster per glyph from `byte_offset`; and its width.
|
||||
fn latin(set: &Wsf1, s: &str, byte_offset: u32) -> Latin {
|
||||
let mut pen = 0i32;
|
||||
let mut g = Vec::new();
|
||||
let mut c = Vec::new();
|
||||
for (i, b) in s.bytes().enumerate() {
|
||||
g.push((u16::from(b), pen, 0));
|
||||
c.push((1, byte_offset + i as u32, 1));
|
||||
pen += i32::from(set.glyph(u16::from(b)).expect("printable ASCII").advance);
|
||||
}
|
||||
(g, c, pen)
|
||||
}
|
||||
|
||||
/// A run item over the whole document.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run(set: usize, text: u16, glyphs: Vec<(u16, i32, i32)>, clusters: Vec<(u16, u32, u32)>, unit_scale: u32, paint: [u8; 4], t: (u32, u32), slots: Slots) -> ItemDef {
|
||||
ItemDef::Run { set, paint, text, t_on: t.0, t_off: t.1, unit_scale, slots, glyphs, clusters }
|
||||
}
|
||||
|
||||
fn block(purpose: u8, lang: &str, dir: u8, s: &str) -> TextDef {
|
||||
let mut t = TextDef::new(purpose, lang, s);
|
||||
t.dir = dir;
|
||||
t
|
||||
}
|
||||
|
||||
/// A one-frame document at `(w, h)`, listed at that size and twice it.
|
||||
fn doc2(w: u16, h: u16) -> SceneDef {
|
||||
let mut s = frame_doc(w, h);
|
||||
s.sizes.push((2 * w, 2 * h));
|
||||
s
|
||||
}
|
||||
|
||||
const PX: i32 = 256;
|
||||
|
||||
pub fn vectors() -> Vec<Vector> {
|
||||
let latin_set = ascii_source();
|
||||
let rep = repertoire_source();
|
||||
let stroke = held(super::stroke::ascii_set_bytes());
|
||||
let tests = held(repertoire::repertoire_set_bytes());
|
||||
let mut v = Vec::new();
|
||||
let mut add = |g: &'static str, name: &str, s: &SceneDef, note: &str| v.push(Vector::wvg1(g, name, s.bytes(), note));
|
||||
|
||||
// ---- text/latin-ascii: the whole stroke set, one block over three runs.
|
||||
{
|
||||
let all: String = (0x20u8..=0x7E).map(char::from).collect();
|
||||
let mut s = doc2(320, 96);
|
||||
s.sets = vec![stroke.clone()];
|
||||
s.texts = vec![block(7, "und", 0, &all)];
|
||||
let u = us(2);
|
||||
for (k, (a, b)) in [(0usize, 32usize), (32, 64), (64, 95)].into_iter().enumerate() {
|
||||
let (g, c, _) = latin(&latin_set, &all[a..b], a as u32);
|
||||
s.items.push(run(0, 0, place(&g, (8 * PX, (22 + 28 * k as i32) * PX), u), c, u, WHITE, (0, ONE), NO_SLOTS));
|
||||
}
|
||||
add("text", "latin-ascii", &s, "printable ASCII, U+0020-U+007E, from the held stroke set: one block drawn by three runs, one per line, one cluster per glyph: accepted");
|
||||
}
|
||||
|
||||
// ---- text/arabic: right to left, contextual forms, lam-alef and two marks.
|
||||
{
|
||||
let word = "\u{0628}\u{064E}\u{064A}\u{0652}\u{062A} \u{0633}\u{0644}\u{0627}\u{0645}";
|
||||
let (sh, cl) = shape_arabic(word);
|
||||
let (g, c) = lay_out(&rep, &sh, &cl, true, 0);
|
||||
let mut s = doc2(160, 64);
|
||||
s.sets = vec![tests.clone()];
|
||||
s.texts = vec![block(7, "ar", 1, word)];
|
||||
let u = us(5);
|
||||
s.items = vec![run(0, 0, place(&g, (27 * PX + 100, 40 * PX), u), c, u, WHITE, (0, ONE), NO_SLOTS)];
|
||||
add("text", "arabic", &s, "Arabic, right to left, in the test repertoire: initial, medial, final and isolated forms chosen by joining type, the lam-alef ligature, fatha and sukun on their bases; glyphs in visual order and clusters with byte_first decreasing; joins whose connectors overlap: accepted");
|
||||
}
|
||||
|
||||
// ---- text/hebrew.
|
||||
{
|
||||
let word = "\u{05E9}\u{05DC}\u{05D5}\u{05DD}";
|
||||
let (sh, cl) = shape_hebrew(word);
|
||||
let (g, c) = lay_out(&rep, &sh, &cl, true, 0);
|
||||
let mut s = doc2(128, 48);
|
||||
s.sets = vec![tests.clone()];
|
||||
s.texts = vec![block(7, "he", 1, word)];
|
||||
let u = us(4);
|
||||
s.items = vec![run(0, 0, place(&g, (33 * PX, 34 * PX), u), c, u, WHITE, (0, ONE), NO_SLOTS)];
|
||||
add("text", "hebrew", &s, "Hebrew, right to left, in the test repertoire, with the final mem: accepted");
|
||||
}
|
||||
|
||||
// ---- text/devanagari: the i-matra before its consonant, the anusvara above, the conjunct.
|
||||
{
|
||||
let words = "\u{0939}\u{093F}\u{0902}\u{0926}\u{0940} \u{0939}\u{093F}\u{0928}\u{094D}\u{0926}\u{0940}";
|
||||
let (sh, cl) = shape_devanagari(words);
|
||||
let (g, c) = lay_out(&rep, &sh, &cl, false, 0);
|
||||
let mut s = doc2(128, 48);
|
||||
s.sets = vec![tests.clone()];
|
||||
s.texts = vec![block(7, "hi", 0, words)];
|
||||
let u = us(4);
|
||||
s.items = vec![run(0, 0, place(&g, (12 * PX + 77, 38 * PX), u), c, u, WHITE, (0, ONE), NO_SLOTS)];
|
||||
add("text", "devanagari", &s, "Devanagari in the test repertoire, one word spelt with the anusvara and with the conjunct: the i-matra drawn before the consonant it follows in logical order, the anusvara above its base, the conjunct na-da as one glyph; one cluster per syllable; headlines that overlap: accepted");
|
||||
}
|
||||
|
||||
// ---- text/mixed-direction: Latin and digits inside an Arabic block, from both sets.
|
||||
{
|
||||
let line = "\u{0633}\u{0644}\u{0627}\u{0645} WAI 2026";
|
||||
let u = us(4);
|
||||
let (la, lc, lw) = latin(&latin_set, "WAI 2026", 9);
|
||||
let (sh, cl) = shape_arabic(&line[..9]);
|
||||
let (ag, ac) = lay_out(&rep, &sh, &cl, true, 0);
|
||||
let mut s = doc2(224, 48);
|
||||
s.sets = vec![stroke.clone(), tests.clone()];
|
||||
s.texts = vec![block(7, "ar", 1, line)];
|
||||
let x0 = 18 * PX;
|
||||
let xa = x0 + r_rat(i64::from(lw) * i64::from(u), 65_536) as i32;
|
||||
s.items = vec![
|
||||
run(0, 0, place(&la, (x0, 36 * PX), u), lc, u, WHITE, (0, ONE), NO_SLOTS),
|
||||
run(1, 0, place(&ag, (xa, 36 * PX), u), ac, u, WHITE, (0, ONE), NO_SLOTS),
|
||||
];
|
||||
add("text", "mixed-direction", &s, "an Arabic block, right to left, holding a Latin word and digits: the left-to-right run from the stroke set, then the right-to-left run with its space from the test repertoire, both drawing parts of one block: accepted");
|
||||
}
|
||||
|
||||
// ---- text/split-lines: one block over three runs, an inserted hyphen, a zero-glyph cluster.
|
||||
{
|
||||
let text = "Titles drawn from integer instructions";
|
||||
let u = us(3);
|
||||
let mut s = doc2(208, 96);
|
||||
s.sets = vec![stroke.clone()];
|
||||
s.texts = vec![block(2, "en", 0, text)];
|
||||
let (g1, mut c1, _) = latin(&latin_set, &text[0..12], 0);
|
||||
c1.push((0, 12, 1));
|
||||
let (mut g2, mut c2, w2) = latin(&latin_set, &text[13..28], 13);
|
||||
g2.push((u16::from(b'-'), w2, 0));
|
||||
c2.push((1, 28, 0));
|
||||
let (g3, c3, _) = latin(&latin_set, &text[28..], 28);
|
||||
for (k, (g, c)) in [(g1, c1), (g2, c2), (g3, c3)].into_iter().enumerate() {
|
||||
s.items.push(run(0, 0, place(&g, (8 * PX, (24 + 30 * k as i32) * PX), u), c, u, WHITE, (0, ONE), NO_SLOTS));
|
||||
}
|
||||
add("text", "split-lines", &s, "one block broken over three runs, one per line: the space at the first break drawn by no glyph (a cluster of zero glyphs), and a hyphen inserted at the second (a cluster of no bytes): accepted");
|
||||
}
|
||||
|
||||
// ---- text/ruby: a Latin transliteration over a Devanagari base.
|
||||
{
|
||||
let base = "\u{0939}\u{093F}\u{0902}\u{0926}\u{0940}";
|
||||
let (sh, cl) = shape_devanagari(base);
|
||||
let (g, c) = lay_out(&rep, &sh, &cl, false, 0);
|
||||
let (rg, rc, _) = latin(&latin_set, "hindi", 0);
|
||||
let mut s = doc2(80, 56);
|
||||
s.sets = vec![tests.clone(), stroke.clone()];
|
||||
let mut ruby = block(7, "hi-Latn", 0, "hindi");
|
||||
ruby.ruby = 1;
|
||||
ruby.ruby_start = 0;
|
||||
ruby.ruby_len = base.len() as u32;
|
||||
s.texts = vec![block(7, "hi", 0, base), ruby];
|
||||
s.items = vec![
|
||||
run(0, 0, place(&g, (18 * PX + 31, 46 * PX), us(4)), c, us(4), WHITE, (0, ONE), NO_SLOTS),
|
||||
run(1, 1, place(&rg, (27 * PX, 14 * PX), 98_304), rc, 98_304, WHITE, (0, ONE), NO_SLOTS),
|
||||
];
|
||||
add("text", "ruby", &s, "a ruby block, Latin from the stroke set, annotating the whole string of a Devanagari base block from the test repertoire: accepted");
|
||||
}
|
||||
|
||||
// ---- text/vertical-rl: upright Latin stacked, mode 1.
|
||||
{
|
||||
let u = us(4);
|
||||
let mut g = Vec::new();
|
||||
let mut c = Vec::new();
|
||||
for (i, b) in "WAI".bytes().enumerate() {
|
||||
let adv = i32::from(latin_set.glyph(u16::from(b)).map_or(0, |g| g.advance));
|
||||
g.push((u16::from(b), -adv / 2, 1_700 * i as i32));
|
||||
c.push((1, i as u32, 1));
|
||||
}
|
||||
let mut s = doc2(48, 96);
|
||||
s.sets = vec![stroke.clone()];
|
||||
let mut t = block(7, "en", 0, "WAI");
|
||||
t.mode = 1;
|
||||
s.texts = vec![t];
|
||||
s.items = vec![run(0, 0, place(&g, (24 * PX, 30 * PX), u), c, u, WHITE, (0, ONE), NO_SLOTS)];
|
||||
add("text", "vertical-rl", &s, "a vertical-rl block (mode 1): Latin from the stroke set, upright and stacked; the sink draws the glyphs where the payload puts them, and the mode is in the block's identity: accepted");
|
||||
}
|
||||
|
||||
// ---- text/emoji: U+1F600 drawn by one monochrome glyph, one cluster of four bytes.
|
||||
{
|
||||
let text = "OK \u{1F600}";
|
||||
let u = us(3);
|
||||
let (lg, lc, lw) = latin(&latin_set, "OK ", 0);
|
||||
let mut s = doc2(80, 40);
|
||||
s.sets = vec![stroke.clone(), tests.clone()];
|
||||
s.texts = vec![block(7, "en", 0, text)];
|
||||
let x0 = 6 * PX;
|
||||
let xf = x0 + r_rat(i64::from(lw) * i64::from(u), 65_536) as i32;
|
||||
s.items = vec![
|
||||
run(0, 0, place(&lg, (x0, 30 * PX), u), lc, u, WHITE, (0, ONE), NO_SLOTS),
|
||||
run(1, 0, place(&[(FACE, 0, 0)], (xf, 30 * PX), u), vec![(1, 3, 4)], u, WHITE, (0, ONE), NO_SLOTS),
|
||||
];
|
||||
add("text", "emoji", &s, "U+1F600 drawn by one monochrome outline glyph from the test repertoire, one cluster mapping its four bytes to that glyph: accepted");
|
||||
}
|
||||
|
||||
// ---- runs: an Arabic joining sequence in one run, and split across two.
|
||||
{
|
||||
let word = "\u{0628}\u{064A}\u{062A}";
|
||||
let (sh, cl) = shape_arabic(word);
|
||||
let (g, c) = lay_out(&rep, &sh, &cl, true, 0);
|
||||
let u = us(10);
|
||||
let origin = (9 * PX, 30 * PX);
|
||||
let mut s = frame_doc(96, 48);
|
||||
s.sets = vec![tests.clone()];
|
||||
s.texts = vec![block(7, "ar", 1, word)];
|
||||
s.items = vec![run(0, 0, place(&g, origin, u), c.clone(), u, WHITE, (0, ONE), NO_SLOTS)];
|
||||
add("runs", "arabic-joins", &s, "an Arabic joining sequence from the test repertoire in one run, large: each connector's round cap passes its glyph's edge and overlaps the neighbour, and the glyph-wise union over-covers the partly covered edge pixels of each overlap (the join knot, §3.13): accepted");
|
||||
// The same glyphs in two runs: the encoder rule keeps one joining sequence in
|
||||
// one run; a sink still renders the split, compositing the runs by source-over.
|
||||
let placed = place(&g, origin, u);
|
||||
s.items = vec![
|
||||
run(0, 0, placed[..1].to_vec(), c[..1].to_vec(), u, WHITE, (0, ONE), NO_SLOTS),
|
||||
run(0, 0, placed[1..].to_vec(), c[1..].to_vec(), u, WHITE, (0, ONE), NO_SLOTS),
|
||||
];
|
||||
add("runs", "arabic-joins-split", &s, "the same joining sequence split across two runs, which an encoder never writes (one joining sequence per run): the runs composite by source-over, so the join's pixels differ from the one-run vector: accepted");
|
||||
}
|
||||
|
||||
// ---- motion/stroke-slice: a lower third and a short credit roll in stroke text.
|
||||
{
|
||||
let frames = 50u32;
|
||||
let end = frames * ONE;
|
||||
let mut s = SceneDef::new(TB, 0, end, 320, 180);
|
||||
s.sizes = vec![(320, 180)];
|
||||
s.rates = vec![(25, 1)];
|
||||
s.sets = vec![stroke.clone()];
|
||||
let roll_lines = ["Opening titles", "Credits", "Lower thirds"];
|
||||
let name = "Stroke set";
|
||||
let role = "Integer outlines, drawn here";
|
||||
s.texts = vec![{
|
||||
let mut t = block(3, "en", 0, name);
|
||||
t.group = 1;
|
||||
t
|
||||
}, {
|
||||
let mut t = block(3, "en", 0, role);
|
||||
t.group = 1;
|
||||
t
|
||||
}];
|
||||
for l in roll_lines {
|
||||
s.texts.push(block(2, "en", 0, l));
|
||||
}
|
||||
// The lower third slides in from the left on an easing table over ten frames,
|
||||
// holds, and fades out over the last ten, as one group.
|
||||
let ease_out = Ease::Table([0, 9_000, 17_000, 24_000, 30_500, 36_000, 41_000, 45_500, 49_500, 53_000, 56_000, 58_500, 60_700, 62_500, 64_000, 65_000, 65_536]);
|
||||
let slide = TrackDef { kind: 2, keys: vec![KeyDef { t: 0, value: -300 * PX, ease: ease_out }, KeyDef { t: 10 * ONE, value: 0, ease: Ease::Hold }] };
|
||||
let fade = TrackDef {
|
||||
kind: 1,
|
||||
keys: vec![KeyDef { t: 0, value: 255, ease: Ease::Hold }, KeyDef { t: 40 * ONE, value: 255, ease: Ease::Linear }, KeyDef { t: end, value: 0, ease: Ease::Hold }],
|
||||
};
|
||||
let u1 = us(3);
|
||||
let u2 = us(2);
|
||||
let (ng, nc, _) = latin(&latin_set, name, 0);
|
||||
let (rg, rc, _) = latin(&latin_set, role, 0);
|
||||
s.items.push(ItemDef::Begin { clip: None, t_on: 0, t_off: end, tx: Some(slide), ty: None, op: Some(fade) });
|
||||
// The bar, in path units of one design pixel (unit_scale 2^24).
|
||||
s.items.push(ItemDef::Path { path: PathDef::rect(12, 128, 236, 172), rule: 0, paint: [20, 40, 120, 220], text: None, t_on: 0, t_off: end, unit_scale: 1 << 24, slots: NO_SLOTS });
|
||||
s.items.push(run(0, 0, place(&ng, (20 * PX, 148 * PX), u1), nc, u1, WHITE, (0, end), NO_SLOTS));
|
||||
s.items.push(run(0, 1, place(&rg, (20 * PX, 164 * PX), u2), rc, u2, [200, 200, 200, 255], (0, end), NO_SLOTS));
|
||||
s.items.push(ItemDef::End);
|
||||
// The roll: three lines moving up three design pixels a frame, keys on frame
|
||||
// ticks, so frame k is frame 0 moved 3k pixels.
|
||||
let ty = TrackDef::linear(2, 0, 0, end, -3 * PX * frames as i32);
|
||||
for (i, l) in roll_lines.iter().enumerate() {
|
||||
let (g, c, w) = latin(&latin_set, l, 0);
|
||||
let x = 160 * PX - r_rat(i64::from(w) * i64::from(u1), 2 * 65_536) as i32;
|
||||
let mut slots = NO_SLOTS;
|
||||
slots[5] = Some(ty.clone());
|
||||
let paint = [WHITE, RED, BLUE][i];
|
||||
s.items.push(run(0, (2 + i) as u16, place(&g, (x, (90 + 34 * i as i32) * PX), u1), c, u1, paint, (0, end), slots));
|
||||
}
|
||||
add("motion", "stroke-slice", &s, "a lower third in the stroke set (a bar and two lines that slide in on an easing table, hold and fade out as one group) and a short credit roll moving up three design pixels a frame: accepted");
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// The test words' shaping, checked by the joining-table test: `(text, gids in logical
|
||||
/// order)`. The expected glyph IDs are written as the code points Unicode assigns to
|
||||
/// each contextual form, never through the table's own constants, so a table that
|
||||
/// names the wrong form cannot also make its own expectation.
|
||||
pub fn test_words() -> Vec<(&'static str, Vec<u16>)> {
|
||||
vec![
|
||||
// بَيْت: beh initial, fatha, yeh medial, sukun, teh final.
|
||||
("\u{0628}\u{064E}\u{064A}\u{0652}\u{062A}", vec![0xFE91, 0x064E, 0xFEF4, 0x0652, 0xFE96]),
|
||||
// سلام: seen initial, lam-alef final, meem isolated.
|
||||
("\u{0633}\u{0644}\u{0627}\u{0645}", vec![0xFEB3, 0xFEFC, 0xFEE1]),
|
||||
// بيت: beh initial, yeh medial, teh final.
|
||||
("\u{0628}\u{064A}\u{062A}", vec![0xFE91, 0xFEF4, 0xFE96]),
|
||||
// A lam-alef that begins a word is isolated; a lone beh is isolated.
|
||||
("\u{0644}\u{0627} \u{0628}", vec![0xFEFB, 0x0020, 0xFE8F]),
|
||||
// beh beh beh: initial, medial, final.
|
||||
("\u{0628}\u{0628}\u{0628}", vec![0xFE91, 0xFE92, 0xFE90]),
|
||||
]
|
||||
}
|
||||
543
wai/wai-vector/src/wsf1.rs
Normal file
543
wai/wai-vector/src/wsf1.rs
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
//! `WSF1`, the stroke source format, and its integer expansion into a `WOS1`
|
||||
//! (`graphics-vector.md` Annex A). Authoring only: a sink never reads a `WSF1` and
|
||||
//! has no stroker (§3.14), so this module is behind the feature `author`.
|
||||
//!
|
||||
//! A source holds, for each glyph, stroke paths: polylines of integer centre-line
|
||||
//! points in outline units, drawn with one round pen of radius `ρ`. The expansion
|
||||
//! turns each stroke path into one closed polygon contour:
|
||||
//! - each segment `(dx, dy)` is offset by `n = (R_rat(−dy·ρ, len), R_rat(dx·ρ, len))`
|
||||
//! with `len = isqrt(dx² + dy²)`;
|
||||
//! - caps and the outer side of joins take their vertices from [`UNIT`], the pinned
|
||||
//! table of 64 unit vectors in Q15, each placed at `R_half_up(ρ·u, 15)`, chosen by
|
||||
//! exact cross-product sign tests;
|
||||
//! - the inner side of a join takes the two offset points.
|
||||
//!
|
||||
//! No trigonometry and no floating point run anywhere: the same source gives the same
|
||||
//! `WOS1` bytes on every machine.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::cmp::Ordering;
|
||||
|
||||
use crate::arith::{r_half_up, r_rat};
|
||||
use crate::json::hex;
|
||||
use crate::sha256::sha256;
|
||||
use crate::wos1::{Point, Wos1};
|
||||
|
||||
/// The header's size.
|
||||
pub const HEADER: usize = 32;
|
||||
/// A glyph record's size.
|
||||
pub const GLYPH: usize = 8;
|
||||
/// The caps of Annex A. They make every expansion fit `WOS1`'s caps (§2.1): a stroke
|
||||
/// of `m ≥ 2` points expands to at most `36·m − 4` points and a dot to 64, so a glyph
|
||||
/// expands to at most `36·256 + 64·64 = 13,312 ≤ 16,383` points, and a source to at
|
||||
/// most `36·2^16 + 64·2^14 ≤ 2^22`.
|
||||
pub const GLYPHS_MAX: u64 = 65_536;
|
||||
pub const STROKES_TOTAL_MAX: u64 = 1 << 14;
|
||||
pub const POINTS_TOTAL_MAX: u64 = 1 << 16;
|
||||
pub const NOTES_MAX: u64 = 1 << 19;
|
||||
pub const GLYPH_STROKES_MAX: u16 = 64;
|
||||
pub const GLYPH_POINTS_MAX: u16 = 256;
|
||||
pub const RADIUS_MAX: u16 = 4096;
|
||||
|
||||
/// The expansion's name and version, written into the notes of every set it makes.
|
||||
pub const CONVERTER: &str = "wsf1-expansion/1";
|
||||
|
||||
/// The pinned table (Annex A): `U[k] = (round(2^15·cos(2πk/64)), round(2^15·sin(2πk/64)))`
|
||||
/// for `k = 0 … 63`, rounded once to the nearest integer (no value lies within 0.02 of
|
||||
/// a tie) and frozen. With y down, `k` increases from +x toward +y. The bytes of
|
||||
/// Annex A, each component an `i32` little-endian in this order, are normative.
|
||||
pub const UNIT: [(i32, i32); 64] = [
|
||||
(32768, 0),
|
||||
(32610, 3212),
|
||||
(32138, 6393),
|
||||
(31357, 9512),
|
||||
(30274, 12540),
|
||||
(28899, 15447),
|
||||
(27246, 18205),
|
||||
(25330, 20788),
|
||||
(23170, 23170),
|
||||
(20788, 25330),
|
||||
(18205, 27246),
|
||||
(15447, 28899),
|
||||
(12540, 30274),
|
||||
(9512, 31357),
|
||||
(6393, 32138),
|
||||
(3212, 32610),
|
||||
(0, 32768),
|
||||
(-3212, 32610),
|
||||
(-6393, 32138),
|
||||
(-9512, 31357),
|
||||
(-12540, 30274),
|
||||
(-15447, 28899),
|
||||
(-18205, 27246),
|
||||
(-20788, 25330),
|
||||
(-23170, 23170),
|
||||
(-25330, 20788),
|
||||
(-27246, 18205),
|
||||
(-28899, 15447),
|
||||
(-30274, 12540),
|
||||
(-31357, 9512),
|
||||
(-32138, 6393),
|
||||
(-32610, 3212),
|
||||
(-32768, 0),
|
||||
(-32610, -3212),
|
||||
(-32138, -6393),
|
||||
(-31357, -9512),
|
||||
(-30274, -12540),
|
||||
(-28899, -15447),
|
||||
(-27246, -18205),
|
||||
(-25330, -20788),
|
||||
(-23170, -23170),
|
||||
(-20788, -25330),
|
||||
(-18205, -27246),
|
||||
(-15447, -28899),
|
||||
(-12540, -30274),
|
||||
(-9512, -31357),
|
||||
(-6393, -32138),
|
||||
(-3212, -32610),
|
||||
(0, -32768),
|
||||
(3212, -32610),
|
||||
(6393, -32138),
|
||||
(9512, -31357),
|
||||
(12540, -30274),
|
||||
(15447, -28899),
|
||||
(18205, -27246),
|
||||
(20788, -25330),
|
||||
(23170, -23170),
|
||||
(25330, -20788),
|
||||
(27246, -18205),
|
||||
(28899, -15447),
|
||||
(30274, -12540),
|
||||
(31357, -9512),
|
||||
(32138, -6393),
|
||||
(32610, -3212),
|
||||
];
|
||||
|
||||
/// The table as Annex A states it: 512 bytes, `cos` then `sin` of each entry, each an
|
||||
/// `i32` little-endian.
|
||||
pub fn unit_table_bytes() -> Vec<u8> {
|
||||
let mut o = Vec::with_capacity(512);
|
||||
for (c, s) in UNIT {
|
||||
o.extend_from_slice(&c.to_le_bytes());
|
||||
o.extend_from_slice(&s.to_le_bytes());
|
||||
}
|
||||
o
|
||||
}
|
||||
|
||||
/// A glyph of a source: its ID, its advance (read by the authoring tool's layout, never
|
||||
/// by a sink), and its stroke paths.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Glyph {
|
||||
pub gid: u16,
|
||||
pub advance: u16,
|
||||
pub strokes: Vec<Vec<(i16, i16)>>,
|
||||
}
|
||||
|
||||
/// A parsed `WSF1`.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Wsf1 {
|
||||
pub upem2: u16,
|
||||
pub radius: u16,
|
||||
pub glyphs: Vec<Glyph>,
|
||||
pub notes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Why a source is refused: the Annex A check that failed, and where.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct SourceError {
|
||||
pub check: u8,
|
||||
pub rule: &'static str,
|
||||
pub index: u64,
|
||||
}
|
||||
|
||||
fn err<T>(check: u8, rule: &'static str, index: u64) -> Result<T, SourceError> {
|
||||
Err(SourceError { check, rule, index })
|
||||
}
|
||||
|
||||
fn rd16(b: &[u8], o: usize) -> u16 {
|
||||
u16::from_le_bytes([b[o], b[o + 1]])
|
||||
}
|
||||
fn rd32(b: &[u8], o: usize) -> u32 {
|
||||
u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]])
|
||||
}
|
||||
|
||||
/// The exact length a header's counts give.
|
||||
pub fn exact_len(n_glyphs: u64, strokes_total: u64, points_total: u64, notes_len: u64) -> u64 {
|
||||
HEADER as u64 + GLYPH as u64 * n_glyphs + 2 * strokes_total + 4 * points_total + notes_len
|
||||
}
|
||||
|
||||
/// `cross(a, b) = a.x·b.y − a.y·b.x`.
|
||||
#[inline]
|
||||
fn cross(a: (i64, i64), b: (i64, i64)) -> i64 {
|
||||
a.0 * b.1 - a.1 * b.0
|
||||
}
|
||||
|
||||
/// Parse a `WSF1`, running Annex A's checks in their order and reporting the first
|
||||
/// failure.
|
||||
pub fn parse(b: &[u8]) -> Result<Wsf1, SourceError> {
|
||||
// Check 1.
|
||||
if b.len() < HEADER {
|
||||
return err(1, "the header is incomplete", 0);
|
||||
}
|
||||
if &b[0..4] != b"WSF1" {
|
||||
return err(1, "magic", 0);
|
||||
}
|
||||
if rd32(b, 4) != 1 {
|
||||
return err(1, "format", 0);
|
||||
}
|
||||
if rd32(b, 28) != 0 {
|
||||
return err(1, "zero", 0);
|
||||
}
|
||||
let upem2 = rd16(b, 8);
|
||||
let radius = rd16(b, 10);
|
||||
// Check 2, in field order.
|
||||
if radius == 0 || radius > RADIUS_MAX {
|
||||
return err(2, "radius", 0);
|
||||
}
|
||||
let n_glyphs = u64::from(rd32(b, 12));
|
||||
let strokes_total = u64::from(rd32(b, 16));
|
||||
let points_total = u64::from(rd32(b, 20));
|
||||
let notes_len = u64::from(rd32(b, 24));
|
||||
if n_glyphs == 0 || n_glyphs > GLYPHS_MAX {
|
||||
return err(2, "n_glyphs", 0);
|
||||
}
|
||||
if strokes_total > STROKES_TOTAL_MAX {
|
||||
return err(2, "strokes_total", 0);
|
||||
}
|
||||
if points_total > POINTS_TOTAL_MAX {
|
||||
return err(2, "points_total", 0);
|
||||
}
|
||||
if notes_len == 0 || notes_len > NOTES_MAX {
|
||||
return err(2, "notes_len", 0);
|
||||
}
|
||||
// Check 3.
|
||||
if exact_len(n_glyphs, strokes_total, points_total, notes_len) != b.len() as u64 {
|
||||
return err(3, "exact length", 0);
|
||||
}
|
||||
let (ng, ns, np) = (n_glyphs as usize, strokes_total as usize, points_total as usize);
|
||||
let e0 = HEADER + GLYPH * ng;
|
||||
let p0 = e0 + 2 * ns;
|
||||
let n0 = p0 + 4 * np;
|
||||
// Check 4: each glyph record.
|
||||
let mut recs: Vec<(u16, u16, u16, u16)> = Vec::with_capacity(ng);
|
||||
for i in 0..ng {
|
||||
let o = HEADER + GLYPH * i;
|
||||
let (gid, n_strokes, n_points, advance) = (rd16(b, o), rd16(b, o + 2), rd16(b, o + 4), rd16(b, o + 6));
|
||||
if n_strokes > GLYPH_STROKES_MAX || n_points > GLYPH_POINTS_MAX {
|
||||
return err(4, "a glyph's strokes or points above its cap", i as u64);
|
||||
}
|
||||
if n_strokes > n_points || (n_points >= 1 && n_strokes == 0) {
|
||||
return err(4, "n_strokes against n_points", i as u64);
|
||||
}
|
||||
recs.push((gid, n_strokes, n_points, advance));
|
||||
}
|
||||
// Check 5.
|
||||
for i in 1..ng {
|
||||
if recs[i].0 <= recs[i - 1].0 {
|
||||
return err(5, "gids strictly increase", i as u64);
|
||||
}
|
||||
}
|
||||
// Check 6.
|
||||
let ss: u64 = recs.iter().map(|r| u64::from(r.1)).sum();
|
||||
let sp: u64 = recs.iter().map(|r| u64::from(r.2)).sum();
|
||||
if ss != strokes_total || sp != points_total {
|
||||
return err(6, "sums", 0);
|
||||
}
|
||||
// Check 7: stroke ends.
|
||||
let ends: Vec<u16> = (0..ns).map(|i| rd16(b, e0 + 2 * i)).collect();
|
||||
let pts: Vec<(i16, i16)> = (0..np).map(|i| (rd16(b, p0 + 4 * i) as i16, rd16(b, p0 + 4 * i + 2) as i16)).collect();
|
||||
let (mut fe, mut fp) = (0usize, 0usize);
|
||||
let mut glyphs: Vec<Glyph> = Vec::with_capacity(ng);
|
||||
for (i, &(gid, n_strokes, n_points, advance)) in recs.iter().enumerate() {
|
||||
let ge = &ends[fe..fe + n_strokes as usize];
|
||||
if ge.windows(2).any(|w| w[1] <= w[0]) || ge.last().is_some_and(|&l| u32::from(l) + 1 != u32::from(n_points)) {
|
||||
return err(7, "stroke ends", i as u64);
|
||||
}
|
||||
let gp = &pts[fp..fp + n_points as usize];
|
||||
let mut strokes = Vec::with_capacity(ge.len());
|
||||
let mut start = 0usize;
|
||||
for &e in ge {
|
||||
strokes.push(gp[start..=e as usize].to_vec());
|
||||
start = e as usize + 1;
|
||||
}
|
||||
glyphs.push(Glyph { gid, advance, strokes });
|
||||
fe += n_strokes as usize;
|
||||
fp += n_points as usize;
|
||||
}
|
||||
// Check 8: every point leaves room for the pen inside i16.
|
||||
let r = i32::from(radius);
|
||||
if let Some(i) = pts.iter().position(|&(x, y)| {
|
||||
let ok = |v: i16| i32::from(v) - r >= i32::from(i16::MIN) && i32::from(v) + r <= i32::from(i16::MAX);
|
||||
!(ok(x) && ok(y))
|
||||
}) {
|
||||
return err(8, "a point within the radius of the i16 range's edge", i as u64);
|
||||
}
|
||||
// Check 9: consecutive points differ, and no interior vertex reverses.
|
||||
for (i, g) in glyphs.iter().enumerate() {
|
||||
for s in &g.strokes {
|
||||
let d: Vec<(i64, i64)> = s.windows(2).map(|w| (i64::from(w[1].0) - i64::from(w[0].0), i64::from(w[1].1) - i64::from(w[0].1))).collect();
|
||||
if d.contains(&(0, 0)) {
|
||||
return err(9, "two consecutive points of a stroke are equal", i as u64);
|
||||
}
|
||||
if d.windows(2).any(|w| cross(w[0], w[1]) == 0 && w[0].0 * w[1].0 + w[0].1 * w[1].1 < 0) {
|
||||
return err(9, "a stroke reverses on itself", i as u64);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check 10.
|
||||
if core::str::from_utf8(&b[n0..]).is_err() {
|
||||
return err(10, "notes UTF-8", 0);
|
||||
}
|
||||
Ok(Wsf1 { upem2, radius, glyphs, notes: b[n0..].to_vec() })
|
||||
}
|
||||
|
||||
impl Wsf1 {
|
||||
/// The canonical bytes.
|
||||
pub fn write(&self) -> Vec<u8> {
|
||||
let ns: usize = self.glyphs.iter().map(|g| g.strokes.len()).sum();
|
||||
let np: usize = self.glyphs.iter().flat_map(|g| g.strokes.iter()).map(Vec::len).sum();
|
||||
let mut o = Vec::with_capacity(exact_len(self.glyphs.len() as u64, ns as u64, np as u64, self.notes.len() as u64) as usize);
|
||||
o.extend_from_slice(b"WSF1");
|
||||
o.extend_from_slice(&1u32.to_le_bytes());
|
||||
o.extend_from_slice(&self.upem2.to_le_bytes());
|
||||
o.extend_from_slice(&self.radius.to_le_bytes());
|
||||
o.extend_from_slice(&(self.glyphs.len() as u32).to_le_bytes());
|
||||
o.extend_from_slice(&(ns as u32).to_le_bytes());
|
||||
o.extend_from_slice(&(np as u32).to_le_bytes());
|
||||
o.extend_from_slice(&(self.notes.len() as u32).to_le_bytes());
|
||||
o.extend_from_slice(&0u32.to_le_bytes());
|
||||
for g in &self.glyphs {
|
||||
let n: usize = g.strokes.iter().map(Vec::len).sum();
|
||||
for v in [g.gid, g.strokes.len() as u16, n as u16, g.advance] {
|
||||
o.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
}
|
||||
for g in &self.glyphs {
|
||||
let mut end = 0usize;
|
||||
for s in &g.strokes {
|
||||
end += s.len();
|
||||
o.extend_from_slice(&((end - 1) as u16).to_le_bytes());
|
||||
}
|
||||
}
|
||||
for g in &self.glyphs {
|
||||
for &(x, y) in g.strokes.iter().flatten() {
|
||||
o.extend_from_slice(&x.to_le_bytes());
|
||||
o.extend_from_slice(&y.to_le_bytes());
|
||||
}
|
||||
}
|
||||
o.extend_from_slice(&self.notes);
|
||||
o
|
||||
}
|
||||
|
||||
/// The glyph of `gid`.
|
||||
pub fn glyph(&self, gid: u16) -> Option<&Glyph> {
|
||||
self.glyphs.binary_search_by_key(&gid, |g| g.gid).ok().map(|i| &self.glyphs[i])
|
||||
}
|
||||
}
|
||||
|
||||
/// `isqrt(n)`: the largest `r ≥ 0` with `r² ≤ n` ([integer-operators] §2), by Newton's
|
||||
/// iteration from above, which is exact on integers.
|
||||
pub fn isqrt(n: u64) -> u64 {
|
||||
if n < 2 {
|
||||
return n;
|
||||
}
|
||||
let mut x = n;
|
||||
let mut y = x.div_ceil(2);
|
||||
while y < x {
|
||||
x = y;
|
||||
y = (x + n / x) / 2;
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
/// The offset of a segment `d` for pen radius `ρ`: `(R_rat(−dy·ρ, len), R_rat(dx·ρ, len))`
|
||||
/// with `len = isqrt(dx² + dy²)`.
|
||||
pub fn normal(d: (i64, i64), rho: i64) -> (i64, i64) {
|
||||
let len = isqrt((d.0 * d.0 + d.1 * d.1) as u64) as i64;
|
||||
(r_rat(-d.1 * rho, len), r_rat(d.0 * rho, len))
|
||||
}
|
||||
|
||||
fn add(p: (i64, i64), n: (i64, i64)) -> (i64, i64) {
|
||||
(p.0 + n.0, p.1 + n.1)
|
||||
}
|
||||
fn sub(p: (i64, i64), n: (i64, i64)) -> (i64, i64) {
|
||||
(p.0 - n.0, p.1 - n.1)
|
||||
}
|
||||
fn neg(n: (i64, i64)) -> (i64, i64) {
|
||||
(-n.0, -n.1)
|
||||
}
|
||||
|
||||
fn unit(k: usize) -> (i64, i64) {
|
||||
(i64::from(UNIT[k].0), i64::from(UNIT[k].1))
|
||||
}
|
||||
|
||||
/// The arc vertices at `p` from the offset `a` toward `b`, in the negative sense (the
|
||||
/// sense of decreasing `k`): every table direction strictly between them, in order
|
||||
/// from `a`. With `b = None` the arc is a cap, from `a` to `−a`: the directions `u`
|
||||
/// with `cross(a, u) < 0`. With `b = Some(b)` it is a join, and is empty unless
|
||||
/// `cross(a, b) < 0`: the directions with `cross(a, u) < 0` and `cross(u, b) < 0`.
|
||||
fn arc(out: &mut Vec<(i64, i64)>, p: (i64, i64), a: (i64, i64), b: Option<(i64, i64)>, rho: i64) {
|
||||
if let Some(b) = b
|
||||
&& cross(a, b) >= 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
let mut ks: Vec<usize> = (0..64).filter(|&k| cross(a, unit(k)) < 0 && b.is_none_or(|b| cross(unit(k), b) < 0)).collect();
|
||||
// All selected directions lie in one open half-plane, so `cross` orders them: `u`
|
||||
// comes before `v` when `v` lies in the negative sense from `u`.
|
||||
ks.sort_by(|&i, &j| match cross(unit(i), unit(j)) {
|
||||
c if c < 0 => Ordering::Less,
|
||||
c if c > 0 => Ordering::Greater,
|
||||
_ => Ordering::Equal,
|
||||
});
|
||||
for k in ks {
|
||||
let u = unit(k);
|
||||
out.push((p.0 + r_half_up(rho * u.0, 15), p.1 + r_half_up(rho * u.1, 15)));
|
||||
}
|
||||
}
|
||||
|
||||
/// One stroke path's closed contour (Annex A): the left offsets forward, the end cap,
|
||||
/// the right offsets backward, then the start cap; a single point is a dot, every
|
||||
/// table direction from `k = 0` in the negative sense. A vertex equal to the one before
|
||||
/// it is written once, and a last vertex equal to the first is dropped.
|
||||
pub fn stroke_contour(stroke: &[(i16, i16)], rho: i64) -> Vec<Point> {
|
||||
let p: Vec<(i64, i64)> = stroke.iter().map(|&(x, y)| (i64::from(x), i64::from(y))).collect();
|
||||
let m = p.len();
|
||||
let mut v: Vec<(i64, i64)> = Vec::new();
|
||||
if m == 1 {
|
||||
for t in 0..64 {
|
||||
let u = unit((64 - t) % 64);
|
||||
v.push((p[0].0 + r_half_up(rho * u.0, 15), p[0].1 + r_half_up(rho * u.1, 15)));
|
||||
}
|
||||
} else {
|
||||
let d: Vec<(i64, i64)> = p.windows(2).map(|w| (w[1].0 - w[0].0, w[1].1 - w[0].1)).collect();
|
||||
let n: Vec<(i64, i64)> = d.iter().map(|&d| normal(d, rho)).collect();
|
||||
// The left side, forward. Turning in the negative sense, it is the outer side.
|
||||
v.push(add(p[0], n[0]));
|
||||
for i in 1..m - 1 {
|
||||
v.push(add(p[i], n[i - 1]));
|
||||
if cross(d[i - 1], d[i]) < 0 {
|
||||
arc(&mut v, p[i], n[i - 1], Some(n[i]), rho);
|
||||
}
|
||||
v.push(add(p[i], n[i]));
|
||||
}
|
||||
v.push(add(p[m - 1], n[m - 2]));
|
||||
// The end cap, from n to −n through the last direction.
|
||||
arc(&mut v, p[m - 1], n[m - 2], None, rho);
|
||||
// The right side, backward. Turning in the positive sense, it is the outer side.
|
||||
v.push(sub(p[m - 1], n[m - 2]));
|
||||
for i in (1..m - 1).rev() {
|
||||
v.push(sub(p[i], n[i]));
|
||||
if cross(d[i - 1], d[i]) > 0 {
|
||||
arc(&mut v, p[i], neg(n[i]), Some(neg(n[i - 1])), rho);
|
||||
}
|
||||
v.push(sub(p[i], n[i - 1]));
|
||||
}
|
||||
v.push(sub(p[0], n[0]));
|
||||
// The start cap, from −n to n through the reverse of the first direction.
|
||||
arc(&mut v, p[0], neg(n[0]), None, rho);
|
||||
}
|
||||
let mut out: Vec<Point> = Vec::with_capacity(v.len());
|
||||
for (x, y) in v {
|
||||
let q = Point { x: x as i16, y: y as i16, tag: 0 };
|
||||
if out.last() != Some(&q) {
|
||||
out.push(q);
|
||||
}
|
||||
}
|
||||
while out.len() > 1 && out.last() == out.first() {
|
||||
out.pop();
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The notes of the set a source expands to: the line `parent_sha256=` with the
|
||||
/// source's SHA-256 in lowercase hex, the line `converter=wsf1-expansion/1`, then the
|
||||
/// source's notes, byte for byte.
|
||||
pub fn expanded_notes(src: &[u8], notes: &[u8]) -> Vec<u8> {
|
||||
let mut o = Vec::new();
|
||||
o.extend_from_slice(b"parent_sha256=");
|
||||
o.extend_from_slice(hex(&sha256(src)).as_bytes());
|
||||
o.extend_from_slice(b"\nconverter=");
|
||||
o.extend_from_slice(CONVERTER.as_bytes());
|
||||
o.push(b'\n');
|
||||
o.extend_from_slice(notes);
|
||||
o
|
||||
}
|
||||
|
||||
/// Expand a source into its `WOS1` (Annex A): one glyph record per source glyph, with
|
||||
/// the same `gid`; one contour per stroke path; `upem2` copied; `flags` 0, since
|
||||
/// overlaps are not removed; and the notes of [`expanded_notes`].
|
||||
pub fn expand(src: &[u8]) -> Result<Wos1, SourceError> {
|
||||
let s = parse(src)?;
|
||||
let rho = i64::from(s.radius);
|
||||
let glyphs: Vec<(u16, Vec<Vec<Point>>)> =
|
||||
s.glyphs.iter().map(|g| (g.gid, g.strokes.iter().map(|st| stroke_contour(st, rho)).collect())).collect();
|
||||
Ok(Wos1::from_outlines(s.upem2, 0, &glyphs, &expanded_notes(src, &s.notes)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
|
||||
#[test]
|
||||
fn isqrt_is_the_floor() {
|
||||
for n in 0u64..20_000 {
|
||||
let r = isqrt(n);
|
||||
assert!(r * r <= n && (r + 1) * (r + 1) > n, "{n}");
|
||||
}
|
||||
for n in [u64::from(u32::MAX), (1u64 << 33) - 1, 2 * 65_535 * 65_535] {
|
||||
let r = isqrt(n);
|
||||
assert!(r * r <= n && (r + 1) * (r + 1) > n, "{n}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_table_is_symmetric_and_unit() {
|
||||
for k in 0..64 {
|
||||
let (c, s) = UNIT[k];
|
||||
assert_eq!(UNIT[(k + 16) % 64], (-s, c), "a quarter turn of {k}");
|
||||
assert_eq!(UNIT[(64 - k) % 64], (c, -s), "the reflection of {k}");
|
||||
let r2 = i64::from(c) * i64::from(c) + i64::from(s) * i64::from(s);
|
||||
assert!((r2 - (1 << 30)).abs() < 1 << 17, "{k}: |u|² = {r2}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_horizontal_stroke_is_a_stadium() {
|
||||
let c = stroke_contour(&[(0, 0), (1000, 0)], 100);
|
||||
// Left offsets (y + 100: n = (0, 100)), the cap round +x, the right offsets,
|
||||
// the cap round −x.
|
||||
assert_eq!(c[0], Point { x: 0, y: 100, tag: 0 });
|
||||
assert_eq!(c[1], Point { x: 1000, y: 100, tag: 0 });
|
||||
assert!(c.iter().all(|p| p.x >= -100 && p.x <= 1100 && p.y >= -100 && p.y <= 100));
|
||||
assert!(c.contains(&Point { x: 1100, y: 0, tag: 0 }) && c.contains(&Point { x: -100, y: 0, tag: 0 }));
|
||||
// Each cap holds the 31 directions strictly inside its half-plane.
|
||||
assert_eq!(c.len(), 4 + 31 + 31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_dot_is_a_polygon_of_the_table() {
|
||||
let c = stroke_contour(&[(500, -500)], 80);
|
||||
assert_eq!(c.len(), 64);
|
||||
assert_eq!(c[0], Point { x: 580, y: -500, tag: 0 });
|
||||
assert_eq!(c[16], Point { x: 500, y: -580, tag: 0 }, "the negative sense: up after right");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
let s = Wsf1 {
|
||||
upem2: 2000,
|
||||
radius: 80,
|
||||
glyphs: vec![
|
||||
Glyph { gid: 32, advance: 600, strokes: vec![] },
|
||||
Glyph { gid: 65, advance: 1200, strokes: vec![vec![(0, 0), (500, -1400), (1000, 0)], vec![(300, -500)]] },
|
||||
],
|
||||
notes: b"licence=Apache-2.0\n\ntext".to_vec(),
|
||||
};
|
||||
let b = s.write();
|
||||
assert_eq!(parse(&b), Ok(s));
|
||||
let w = expand(&b).unwrap();
|
||||
assert_eq!(crate::wos1::parse(&w.write()).unwrap(), w);
|
||||
}
|
||||
}
|
||||
221
wai/wai-vector/tests/stroke_sets.rs
Normal file
221
wai/wai-vector/tests/stroke_sets.rs
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
//! The in-house sets (`graphics-vector.md` §4, Annexes A and B): the default stroke set's
|
||||
//! digest equals Annex B; Annex A states the expansion's table; the expansion is
|
||||
//! deterministic and re-derives both committed sets from their committed sources; the
|
||||
//! sets' notes carry their licence; and the test repertoire's joining table picks the
|
||||
//! forms of the test words.
|
||||
//!
|
||||
//! Every leg runs these: natively, and under WASI with the `wai` directory preopened.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use wai_vector::builtin;
|
||||
use wai_vector::json::hex;
|
||||
use wai_vector::sha256::sha256;
|
||||
use wai_vector::vectors::{repertoire, stroke, text};
|
||||
use wai_vector::wsf1;
|
||||
|
||||
fn wai() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
||||
}
|
||||
|
||||
fn read(rel: &str) -> Vec<u8> {
|
||||
std::fs::read(wai().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
|
||||
}
|
||||
|
||||
/// The text of the extension from `heading` up to the next heading of the same level.
|
||||
fn section(heading: &str) -> String {
|
||||
let doc = String::from_utf8(read("extensions/graphics-vector.md")).unwrap();
|
||||
let start = doc.find(heading).unwrap_or_else(|| panic!("no {heading}"));
|
||||
let level = heading.split(' ').next().unwrap();
|
||||
let rest = &doc[start + heading.len()..];
|
||||
let end = rest.find(&format!("\n{level} ")).unwrap_or(rest.len());
|
||||
rest[..end].to_string()
|
||||
}
|
||||
|
||||
/// The first run of 64 lowercase hexadecimal digits on the line holding `label`.
|
||||
fn digest_on(text: &str, label: &str) -> String {
|
||||
let line = text.lines().find(|l| l.contains(label)).unwrap_or_else(|| panic!("no line with {label}"));
|
||||
line.split(|c: char| !c.is_ascii_hexdigit()).find(|w| w.len() == 64).unwrap_or_else(|| panic!("no digest on {line}")).to_string()
|
||||
}
|
||||
|
||||
/// The number before " bytes" on the line holding `label`, commas removed.
|
||||
fn length_on(text: &str, label: &str) -> usize {
|
||||
let line = text.lines().find(|l| l.contains(label)).unwrap();
|
||||
let before = &line[..line.find(" bytes").unwrap()];
|
||||
before.rsplit("length ").next().unwrap().replace(',', "").parse().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_stroke_sets_digest_equals_annex_b() {
|
||||
let b = section("## Annex B.");
|
||||
let (set_digest, src_digest) = (digest_on(&b, "the set (`WOS1`)"), digest_on(&b, "its source (`WSF1`)"));
|
||||
// The bundled bytes, their constant and the annex agree.
|
||||
assert_eq!(hex(&sha256(builtin::STROKE_ASCII)), set_digest, "the bundled set's SHA-256 is Annex B's");
|
||||
assert_eq!(hex(&builtin::STROKE_ASCII_SHA256), set_digest, "the digest constant is Annex B's");
|
||||
assert_eq!(builtin::STROKE_ASCII.len(), length_on(&b, "the set (`WOS1`)"));
|
||||
assert_eq!(builtin::STROKE_ASCII_LEN as usize, builtin::STROKE_ASCII.len());
|
||||
// The committed source is Annex B's, and expanding it by Annex A gives the set.
|
||||
let src = read("vector-conformance/sources/stroke-ascii.wsf1");
|
||||
assert_eq!(hex(&sha256(&src)), src_digest, "the committed source's SHA-256 is Annex B's");
|
||||
assert_eq!(src.len(), length_on(&b, "its source (`WSF1`)"));
|
||||
let set = wsf1::expand(&src).unwrap().write();
|
||||
assert_eq!(hex(&sha256(&set)), set_digest, "Annex A's expansion of the source gives Annex B's set");
|
||||
assert!(set == builtin::STROKE_ASCII);
|
||||
// Coverage: printable ASCII, each glyph ID its code point.
|
||||
let w = wai_vector::wos1::parse(builtin::STROKE_ASCII).unwrap();
|
||||
assert_eq!(w.glyphs.iter().map(|g| g.gid).collect::<Vec<u16>>(), (0x20..=0x7E).collect::<Vec<u16>>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annex_a_states_the_table() {
|
||||
let a = section("### A.2 ");
|
||||
let block = &a[a.find("```text").unwrap() + 7..];
|
||||
let block = &block[..block.find("```").unwrap()];
|
||||
let hexs: String = block.split_whitespace().collect();
|
||||
assert_eq!(hexs, hex(&wsf1::unit_table_bytes()), "Annex A's bytes are the table the expansion reads");
|
||||
assert_eq!(digest_on(&a, "Their SHA-256"), hex(&sha256(&wsf1::unit_table_bytes())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_expansion_is_deterministic() {
|
||||
// Offsets worked by hand. (300, 400) has an exact length; (100, 200) does not:
|
||||
// isqrt(50000) = 223, and −18000/223 = −80.72 rounds to −81, 9000/223 = 40.36 to 40.
|
||||
assert_eq!(wsf1::normal((300, 400), 80), (-64, 48));
|
||||
assert_eq!(wsf1::normal((100, 200), 90), (-81, 40));
|
||||
assert_eq!(wsf1::normal((-7, 3), 80), (-34, -80), "isqrt(58) = 7: (R_rat(−240, 7), R_rat(−560, 7))");
|
||||
// Both committed sets re-derive from their committed sources, twice, byte for byte.
|
||||
for name in ["stroke-ascii", "test-repertoire"] {
|
||||
let src = read(&format!("vector-conformance/sources/{name}.wsf1"));
|
||||
let set = read(&format!("vector-conformance/sets/{name}.wos1"));
|
||||
let a = wsf1::expand(&src).unwrap().write();
|
||||
let b = wsf1::expand(&src).unwrap().write();
|
||||
assert!(a == b, "{name}: two expansions agree");
|
||||
assert!(a == set, "{name}: the expansion reproduces the committed set");
|
||||
// The source written by the generator is the committed one.
|
||||
let generated = if name == "stroke-ascii" { stroke::ascii_source_bytes() } else { repertoire::repertoire_source_bytes() };
|
||||
assert!(generated == src, "{name}: the generator writes the committed source");
|
||||
// The set's notes name its source by SHA-256, and the expansion.
|
||||
let w = wai_vector::wos1::parse(&set).unwrap();
|
||||
let notes = String::from_utf8(w.notes.clone()).unwrap();
|
||||
assert!(notes.starts_with(&format!("parent_sha256={}\nconverter=wsf1-expansion/1\n", hex(&sha256(&src)))), "{name}");
|
||||
// A polygon-only set: every point on-curve, overlaps not removed.
|
||||
assert!(w.points.iter().all(|p| p.tag == 0) && w.flags == 0, "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_set_carries_its_licence() {
|
||||
for name in ["stroke-ascii", "test-repertoire"] {
|
||||
let set = read(&format!("vector-conformance/sets/{name}.wos1"));
|
||||
let w = wai_vector::wos1::parse(&set).unwrap();
|
||||
let notes = String::from_utf8(w.notes).unwrap();
|
||||
let (keys, licence) = notes.split_once("\n\n").expect("key lines, a blank line, the licence text");
|
||||
let key = |k: &str| keys.lines().find_map(|l| l.strip_prefix(&format!("{k}="))).unwrap_or_else(|| panic!("{name}: no {k}"));
|
||||
assert_eq!(key("licence"), "Apache-2.0", "{name}");
|
||||
assert_eq!(key("licence_sha256"), hex(&sha256(licence.as_bytes())), "{name}: the verifier's licence check");
|
||||
assert_eq!(key("rfn"), "none", "{name}");
|
||||
assert!(key("source").contains("no outline is traced from any font"), "{name}");
|
||||
assert!(licence.contains("Apache License") && licence.contains("Version 2.0"), "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_joining_table_picks_the_forms_of_the_test_words() {
|
||||
for (word, want) in text::test_words() {
|
||||
let got: Vec<u16> = repertoire::shape_arabic(word).0.iter().map(|g| g.gid).collect();
|
||||
assert_eq!(got, want, "{word}");
|
||||
}
|
||||
// Every form the table names is drawn in the test repertoire.
|
||||
let src = repertoire::repertoire_source();
|
||||
for (_, _, forms) in repertoire::JOINING {
|
||||
for f in forms {
|
||||
assert!(src.glyph(f).is_some(), "form {f:04X} is drawn");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The winding number of the closed polygon contours of a glyph about `(px, py)`.
|
||||
fn winding(contours: &[Vec<(i64, i64)>], px: i64, py: i64) -> i64 {
|
||||
let mut w = 0;
|
||||
for c in contours {
|
||||
for i in 0..c.len() {
|
||||
let (a, b) = (c[i], c[(i + 1) % c.len()]);
|
||||
let side = (b.0 - a.0) * (py - a.1) - (b.1 - a.1) * (px - a.0);
|
||||
if a.1 <= py && py < b.1 && side > 0 {
|
||||
w += 1;
|
||||
} else if b.1 <= py && py < a.1 && side < 0 {
|
||||
w -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
w
|
||||
}
|
||||
|
||||
/// Whether `(px, py)` lies within `r` of a centre-line polyline, exactly in integers.
|
||||
fn within(stroke: &[(i16, i16)], px: i64, py: i64, r: i64) -> bool {
|
||||
let p: Vec<(i64, i64)> = stroke.iter().map(|&(x, y)| (i64::from(x), i64::from(y))).collect();
|
||||
let d2 = |a: (i64, i64)| (px - a.0).pow(2) + (py - a.1).pow(2) <= r * r;
|
||||
if p.len() == 1 {
|
||||
return d2(p[0]);
|
||||
}
|
||||
p.windows(2).any(|s| {
|
||||
let (a, b) = (s[0], s[1]);
|
||||
let (dx, dy) = (b.0 - a.0, b.1 - a.1);
|
||||
let l = dx * dx + dy * dy;
|
||||
let t = (px - a.0) * dx + (py - a.1) * dy;
|
||||
if t <= 0 {
|
||||
d2(a)
|
||||
} else if t >= l {
|
||||
d2(b)
|
||||
} else {
|
||||
let c = i128::from((px - a.0) * dy - (py - a.1) * dx);
|
||||
c * c <= i128::from(r * r) * i128::from(l)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_expansion_covers_the_pens_path() {
|
||||
// Sampled every 20 outline units: a point within ρ − 4 of a centre line is inside
|
||||
// the glyph's fill (non-zero winding), and a point farther than ρ + 8 from every
|
||||
// centre line is outside it. The bands absorb the 64-gon's chords and the offsets'
|
||||
// rounding; the inner loops of joins must not open a hole, and no outer join may
|
||||
// cut its corner.
|
||||
for src in [stroke::ascii_source(), repertoire::repertoire_source()] {
|
||||
let rho = i64::from(src.radius);
|
||||
let set = wsf1::expand(&src.write()).unwrap();
|
||||
for g in &src.glyphs {
|
||||
let rec = set.glyph(g.gid).unwrap();
|
||||
let pts = set.glyph_points(rec);
|
||||
let mut contours = Vec::new();
|
||||
let mut start = 0usize;
|
||||
for &e in set.glyph_ends(rec) {
|
||||
contours.push(pts[start..=e as usize].iter().map(|p| (i64::from(p.x), i64::from(p.y))).collect::<Vec<_>>());
|
||||
start = e as usize + 1;
|
||||
}
|
||||
let (x0, y0, x1, y1) = (i64::from(rec.xmin) - 20, i64::from(rec.ymin) - 20, i64::from(rec.xmax) + 20, i64::from(rec.ymax) + 20);
|
||||
let (mut inside, mut outside) = (0, 0);
|
||||
let mut y = y0 + 3;
|
||||
while y <= y1 {
|
||||
let mut x = x0 + 7;
|
||||
while x <= x1 {
|
||||
let near = g.strokes.iter().any(|s| within(s, x, y, rho - 4));
|
||||
let far = !g.strokes.iter().any(|s| within(s, x, y, rho + 8));
|
||||
let w = winding(&contours, x, y);
|
||||
if near {
|
||||
inside += 1;
|
||||
assert!(w != 0, "glyph {:04X}: ({x}, {y}) is within the pen's path but not filled", g.gid);
|
||||
}
|
||||
if far {
|
||||
outside += 1;
|
||||
assert!(w == 0, "glyph {:04X}: ({x}, {y}) is outside the pen's path but winds {w}", g.gid);
|
||||
}
|
||||
x += 20;
|
||||
}
|
||||
y += 20;
|
||||
}
|
||||
assert!(g.strokes.is_empty() || inside > 0, "glyph {:04X}: the samples reach its strokes", g.gid);
|
||||
assert!(outside > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,8 @@ crate-type = ["cdylib"]
|
|||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
wai-vector = { path = ".." }
|
||||
# The recomputation needs no authoring code: a sink's build, with no stroker.
|
||||
wai-vector = { path = "..", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
// Recompute every expected file of vector-conformance under this engine's
|
||||
// WebAssembly, and require each to equal the committed file byte for byte; and the
|
||||
// authoring form beside each payload.
|
||||
// authoring form beside each payload. The default stroke set's SHA-256 is required to
|
||||
// be the one Annex B of the extension states.
|
||||
//
|
||||
// node verify.mjs <wai_vector_wasm.wasm> <vector-conformance dir>
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
|
|
@ -32,6 +34,21 @@ for (const f of readdirSync(join(root, 'sets')).filter((n) => n.endsWith('.wos1'
|
|||
}
|
||||
|
||||
let vectors = 0, forms = 0, bad = 0;
|
||||
|
||||
// Annex B: the default stroke set's digest.
|
||||
{
|
||||
const doc = readFileSync(join(root, '..', 'extensions', 'graphics-vector.md'), 'utf8');
|
||||
const annex = doc.slice(doc.indexOf('## Annex B.'));
|
||||
const line = annex.split('\n').find((l) => l.includes('the set (`WOS1`)')) || '';
|
||||
const stated = (line.match(/[0-9a-f]{64}/) || [''])[0];
|
||||
const have = createHash('sha256').update(readFileSync(join(root, 'sets', 'stroke-ascii.wos1'))).digest('hex');
|
||||
if (stated.length !== 64 || have !== stated) {
|
||||
bad += 1;
|
||||
console.error(`ANNEX B: sets/stroke-ascii.wos1 hashes to ${have}, Annex B states ${stated || 'nothing'}`);
|
||||
} else {
|
||||
console.log(`the default stroke set is Annex B's: ${have}`);
|
||||
}
|
||||
}
|
||||
for (const group of readdirSync(join(root, 'vectors')).sort()) {
|
||||
for (const f of readdirSync(join(root, 'vectors', group)).sort()) {
|
||||
const dot = f.lastIndexOf('.');
|
||||
|
|
|
|||
Loading…
Reference in a new issue