Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_austenite/web/pearl-reader/README.md

4.8 KiB, 52 runs

created by r1870400018:38476, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1# Pearl web reader
2
3A vanilla-JS browser reader for the Pearl (`.prl`) document format. It fetches a real `.prl`, parses its
4text jdat directly in the browser, and renders every page to inline SVG from the format's own data model
5-- glyph outlines stored once and placed per leaf, plus fills, strokes, rules and rasters -- reproducing
6the transform the Austenite SVG arm applies, so the page is pixel-identical to Austenite's own SVG output
7(which already matches the PDF).
8
9No framework, no build step, no wasm, and **no JSON projection**. Just `index.html` + `jdat.js` +
10`pearl.js`, reading the `.prl` the engine writes.
11
12## Run
13
14```bash
15cd fe2o3_austenite/web/pearl-reader
16cargo run -p oxedyne_fe2o3_net --no-default-features --bin localserve -- . --port 8137
17# then open http://127.0.0.1:8137/index.html
18```
19
20A static server is needed because the reader `fetch()`es the `.prl`; a bare `file://` open is blocked by
21the browser's local-file CORS rule.
22
23## How it reads the format
24
25The `.prl` is text jdat: an ordered map of ordered maps, lists, strings and typed scalar atoms.
26`jdat.js` is a minimal recursive-descent parser for exactly the subset a v1 `.prl` uses:
27
28| jdat text | shape | JS value |
29|--------------------------|--------------|-----------------------------|
30| `(omap\|{ "k": v, ... })` | ordered map | object, insertion order kept |
31| `[ v, v, ... ]` | list | array |
32| `"..."` | string | string (RFC 8259 escapes) |
33| `(u32\|N)` `(i32\|N)` `(u8\|N)` | integer atom | Number |
34| `(f32\|X.YeZ)` | float atom | Number |
35
36There are no byte-strings in a v1 `.prl`: a raster's PNG rides as a **base64 string** (the writer stores
37`base64::encode(png)`), so the whole file is these five shapes. Stripping the type tag yields the same
38plain value the old JSON projection did, so `pearl.js`'s renderer consumes the parsed document directly,
39with no further translation step. Unknown map keys and type tags are tolerated, so a parallel lane adding
40fields does not break the reader.
41
42A `text` leaf's fields past its rigid geometry and outline glyphs -- point size, the selectable spans
43behind the `.tsel` layer below, and the optional fill colour -- ride in one keyed object (`leaf[7]`)
44rather than further positional list elements, so a future field never shifts an index an old reader still
45expects; see `emit/pearl.rs`'s own comment on the v0 leaf-shape fault this replaced.
46
47### Selectable text
48
49Every page carries an invisible, selectable `.tsel` layer over its glyph outlines -- the same answer
50Typst.ts gives for the same problem (outline-only SVG has nothing a browser can select or search). One
51`<text class="tsel">` spans the whole page, its `<tspan>`s built from each `text` leaf's `spans` (a
52glyph's cluster mapped to its source text, spaces included, straight from `ShapedText::glyph_text` on the
53Rust side): a single page-wide element, not one per run, because Chromium's `window.find`/Ctrl+F was
54found not to search across sibling `<text>` elements once their tspans carry per-glyph `x`/`y`. A leading
55space precedes every run but the page's first, standing in for the interword gap the line breaker leaves
56as pure position rather than a glyph.
57
58Regenerate the samples with the engine:
59
60```bash
61T=~/.cache/cargo-targets/$RC_SLOT/austenite-pearl/debug # or your target dir
62$T/austenite --pearl samples/keystone.typ out/ # writes out/document.prl
63cp out/document.prl samples/keystone.prl
64```
65
66## Verify (pixel parity)
67
68Rasterise the reader's SVG and Austenite's reference `page-001.svg` through the same rasteriser
69(inkscape) and pixel-diff them. Differing pixels of 500,990 against the SVG-arm reference:
70
71| Document | exact-match diff | at 12% fuzz |
72|-----------|------------------|-------------|
73| keystone | 560 (0.112%) | 0 |
74| maths | 40 (0.008%) | 0 |
75| raster | 45 (0.009%) | 0 |
76
77Zero differing pixels at 12% fuzz means no glyph, path or raster is misplaced: the residual is sub-pixel
78antialiasing on glyph and hairline edges, from placing a transformed outline versus a baked one. The
79raster page (`raster.prl`, a PNG carried as base64) diffs to zero at fuzz -- the `<image>` leaf renders
80identically.
81
82## Byte parity
83
84Not met, and the reason is architectural rather than number formatting. `pearl_render` **bakes** each
85translate/matrix transform into the path `d` coordinates and emits a bare `<path d="...">`; the reader
86keeps the stored `d` and applies a `transform` attribute. Same pixels, different SVG text. Closing it
87would mean porting `fe2o3_graphics`'s `Path::transform` + `write_path_data` float formatting into JS --
88worthwhile only if a byte-identical SVG is itself a requirement; pixel parity above is the shippable
89metric.