oxedyne/fe2o3/fe2o3_austenite/tests/oracle/fixtures/float_fixture.typ
8.3 KiB, 3 runs
created by r1870400018:46113, 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 | // The oracle harness's float fixture -- the corpus's first root exercising the A1 FLOAT primitive |
| 2 | // (`figure(placement: auto | top | bottom)`). Self-contained -- no `#import` of any shared template -- |
| 3 | // so it compiles under typst 0.15.1 with no symbol-modifier patch, and austenite reads the `aside-box` |
| 4 | // furniture from its own `#let` definition exactly as it does in the books. |
| 5 | // |
| 6 | // The page geometry is austenite's own `PageGeometry::a4()` (A4, uniform 56.9 pt margins) set explicitly |
| 7 | // here so Typst lays the body on the same measure -- austenite ignores a lone `#set page(width/height)` |
| 8 | // and always uses A4, so the two only agree when the fixture names A4 (see `marginalia_fixture.typ` for |
| 9 | // the same idiom). The floats are aside-boxes carrying plain text, not `rect`/image figures: austenite |
| 10 | // renders an image figure as a measure-wide PLACEHOLDER and always sets a "Figure N." caption, so an |
| 11 | // image float could never match Typst, whereas an aside-box of text sets from the same body in both. Each |
| 12 | // aside-box is a `figure(placement: auto, ...)` under the bonnet, so it is a floating figure to Typst and |
| 13 | // a `Float` anchor to austenite -- the two the oracle order-matches, and for THIS root the harness holds |
| 14 | // them to exact page agreement and a per-float side (top/foot) and y check. |
| 15 | // |
| 16 | // The body is long and plain (both engines break it identically), so the document spans more than one page |
| 17 | // and the four asides are spread across it -- some drawn to the top, some to the foot -- so the engine's |
| 18 | // queue, midpoint and document-order stacking are all exercised, and the two engines agree float-for-float. |
| 19 | // |
| 20 | // TRUE TYPST-PARITY GATE. Each aside's floated placement (page, side, y) is asserted against Typst's own, |
| 21 | // read through the in-float `#context here().position()` probe above -- Typst's plain `query(figure)` |
| 22 | // reports a float's ANCHOR position, not where it lands, so the probe is what makes an exact comparison |
| 23 | // possible. A Fable trace confirmed Austenite's midpoint (`used = base - remaining`, ratio <= 0.5 -> top) |
| 24 | // reproduces Typst's (compose.rs:319-326) exactly. |
| 25 | // |
| 26 | // NO LEVEL-1 HEADING: the fixture opens straight into body on purpose. A lone-file `= Heading` is lowered |
| 27 | // by austenite as a ~136pt CHAPTER-OPENER grid where Typst (a lone file) sets a plain ~18pt heading; that |
| 28 | // ~118pt confound pushed the flow deeper and tipped aside-2's midpoint from top to foot, which is a |
| 29 | // heading-rendering bug (tracked as A18: lone-file/standalone `=` should set a plain heading, not a |
| 30 | // chapter opener), NOT a float bug. Removing the heading isolates this fixture to what it gates -- FLOAT |
| 31 | // placement -- so it stays green through the A18 fix rather than coupling to it. |
| 32 | |
| 33 | #set page(width: 595.276pt, height: 841.89pt, margin: 56.9pt) |
| 34 | #set text(size: 11pt, font: "Libertinus Serif") |
| 35 | #set par(justify: true, leading: 0.65em, spacing: 1.2em, first-line-indent: 0pt) |
| 36 | |
| 37 | #let aside-box(float: true, body) = { |
| 38 | let inner = box( |
| 39 | width: 100%, |
| 40 | inset: (x: 10pt, y: 10pt), |
| 41 | fill: luma(240), |
| 42 | stroke: (left: 2pt + luma(80)), |
| 43 | // The `#context here().position()` probe reports the aside's TRUE floated position (it is laid out |
| 44 | // inside the floated box, so context resolves to where the float lands, not its in-flow anchor). Typst |
| 45 | // reads it through the `<fp>` label; austenite ignores the unknown `#context` construct, so it changes |
| 46 | // no ink in either engine and the aside's height is identical to a plain one. |
| 47 | [#text(size: 0.9em)[#context [#metadata((page: here().position().page, y: int(calc.round(here().position().y / 1pt))))<fp>]#body]], |
| 48 | ) |
| 49 | if float { figure(placement: auto, inner, caption: none) } else { inner } |
| 50 | } |
| 51 | |
| 52 | This opening section runs on at plain length so that both engines break its lines at exactly the same |
| 53 | points and the reading cursor reaches the same depth in each. The words carry no emphasis and no special |
| 54 | construct, only ordinary prose set to the same measure, so that the page fills identically and the first |
| 55 | aside, when it is met, has the same space above it in both engines. We write on to fill the upper part of |
| 56 | the first page before the aside arrives, so its midpoint sits high and it is drawn to the top of the page. |
| 57 | Line after plain line, the same in both engines, the measure the same, the leading the same, the breaks |
| 58 | the same, until the aside is due. |
| 59 | |
| 60 | #aside-box[A first aside, met high on the first page, so its midpoint sits in the upper half and it is |
| 61 | drawn to the top of the page above the prose that continues beneath it.] |
| 62 | |
| 63 | The body continues after the first aside with a solid block of ordinary prose whose only job is to carry |
| 64 | the reading cursor down the first page, so that the second aside, arriving low, has its midpoint in the |
| 65 | lower half and is drawn to the foot. We keep writing plainly and at length, without emphasis or any |
| 66 | special construct, so that both engines break these lines at exactly the same points and the cursor |
| 67 | reaches the same depth in each before the second aside is met. A little more prose keeps the measure |
| 68 | honest and the paragraph long enough to matter for the placement decision that follows it, which is the |
| 69 | whole point of setting this fixture down at this deliberate length. Line after plain line accumulates, |
| 70 | each the same in both engines, until the page is better than half full and the midpoint rule has a clear |
| 71 | answer to give for the aside that comes next in the source, drawing it down to the foot of the page. |
| 72 | |
| 73 | #aside-box[A second aside, met once the cursor has run well down the first page, so its midpoint falls in |
| 74 | the lower half and the engine draws it to the foot of the page instead of the top -- the midpoint rule |
| 75 | that tells an automatic float which end of the page it belongs on. It carries a couple of sentences so it |
| 76 | has real height, and its clearance is laid above it where it meets the body rather than below.] |
| 77 | |
| 78 | Now a longer passage of prose carries the reader onto the second page and down it. There is nothing to |
| 79 | read here and nothing to see but text, which is the whole intent: text is what the two engines set |
| 80 | identically, so text is what makes their page breaks agree. We set it down line by line, plain and |
| 81 | unadorned, at the one size and the one measure, the leading fixed and the justification on, so the ragged |
| 82 | edge never enters into it and every line is full to the same width in both. On the prose goes, filling the |
| 83 | column, turning the page, resuming at the head of the next, the same words at the same places, carrying |
| 84 | the cursor down the second page toward its foot, so that the tall aside that comes next cannot fit in the |
| 85 | space that remains and must defer. We write on to be sure the cursor is deep when the tall aside is |
| 86 | reached, and that there is real prose after it to do the backfilling, because a float that fits where it |
| 87 | stands is not deferred and would not exercise the queue at all. |
| 88 | |
| 89 | #aside-box[A third and much taller aside. It carries several sentences so that its height is large enough |
| 90 | that, arriving low on the second page, it cannot fit in the space that remains. It therefore defers to the |
| 91 | queue and is set at the top of the next page, while the prose written after it in the source flows up to |
| 92 | fill the foot of the second page. This is the backfill behaviour that distinguishes a real float queue |
| 93 | from the old in-flow lowering, and it is the reason a document with floats paginates as Typst does. A few |
| 94 | more sentences give the aside the height it needs to be certain of deferring rather than fitting in |
| 95 | whatever slack the page still holds, so the scenario is robust to small differences in where the cursor |
| 96 | happens to sit when the aside is reached, and the deferred aside opens the third page at its very top.] |
| 97 | |
| 98 | A short paragraph of plain prose follows the tall aside in the source and backfills the foot of the second |
| 99 | page, set before the deferred aside on the page it was carried to. It is ordinary text, the same in both |
| 100 | engines, so it fills the same space in each and the backfill is identical. |
| 101 | |
| 102 | #aside-box[A fourth aside, met just after the tall one, so it queues behind it and is set on the third |
| 103 | page beneath the deferred aside -- document order running down the top band, the earlier float highest.] |
| 104 | |
| 105 | A closing paragraph of plain prose ends the document on the third page, part of the material that fills |
| 106 | the page the floats settled on, so the fixture finishes cleanly with body below the floats drawn there, |
| 107 | and both engines set that closing prose at the same place because it is, like all the rest, only text. |