feat(renderer): scene 2 text: bundled fonts, measurement before drawing, stated fit rules (P2) #16
Merged
james.maes
merged 3 commits from 2026-11-12 23:11:04 +00:00
feat/scene2-text into develop
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
589b6d41ee
|
fix(renderer): scene 2 text after independent review: a proof that never cuts, a bounded shrink search, named refusals
The proof of `maxLength` - The proof never cuts. With `overflow: truncate` the proof was given a cut block, which counts as fitting, so a bound element whose box held ten characters validated clean with `maxLength: 40` and every long value was cut at print. The proof now runs with cutting off. When it fails, an element that did not ask for cutting is refused as before; one that did still validates, since cutting is what its author chose, with the warning FIT_NOT_PROVEN at every validation carrying `measured.provenCharacters`, the longest length proven to fit whole (found by halving: fewer characters never fit worse), against `limit.characters`. No new code: FIT_NOT_PROVEN already means that the fit of a bound value is not proven. - A bound element with `maxLength` is judged for legibility, when validated, at the size the proof shrank to (the floor when it fails and the element cuts), not at its stated size. The shrink search - The size chosen is still exactly the one a search of every quarter-point step gives, but that search is no longer made. A count that errs only toward yes says whether a size could hold the text: lines filled at any character, each held to a width no real line can be under, and a block to its lines' ascent, pitches and descent. It never refuses a size that fits and, once it allows a size, allows every smaller one, so the largest size it allows is found by halving and only the steps from there are laid out. When one line at the floor is higher than the box nothing is searched, and a cut tries no line count the box is too low for. - 1024 characters at 600 dpi from 1000 pt to 1 pt took 180 to 250 ms a fit and now takes under 1 ms: at most 21 555 characters visited where the search of every size visits 15.6 million. - A text is fitted once: what `check` composed is what `paint` draws. - The text of one scene may take 64 million steps (a character visited while fitting is one, a character to be drawn 16, a glyph the scene draws for the first time at a size 4096, since the runtime rasterises it once). The element that passes it, and every text after it, is SCENE_INVALID at the element with `textWorkSteps`, as the painted-area limit is. The count is integer arithmetic: the same on every machine, and the same validated or rendered. A thousand of the worst elements validate in well under a second. Ink outside the measure - TEXT_INK_OUTSIDE_MEASURE was an IllegalStateException thrown while painting, which failed the whole render after part of the text was on the label. The dots of a text are now gathered first and reach the label only when none strayed; otherwise the one element is refused as TEXT_DOES_NOT_FIT with `glyphsOutsideMeasure` and `dotsOutsideMeasure`. A literal is held to the glyph bounds when the scene is validated. - The bound is tested at 1, 2, 300 and 1000 pt as well as from 5 to 72 pt, for every character of every face at the three resolutions; nothing strays. Schema and vocabulary - `fit` declares `maxLines`, `shrinkTo` and `overflow` under `properties` with `additionalProperties: false`. The three names are in the renderer's and the host bridge's pointer vocabularies, and a wrong member is reported at the member (`/elements/3/fit/maxLines`, or in the override that states `fit`). - A bound number is printed as the schema says: plain decimal digits, no exponent, no fraction that is only zeros (`1.0E10` prints `10000000000`, `2.50` prints `2.5`). A number that is not finite is VALUE_MISSING. Tests - The widest-advance check covers every character a face draws, inked or not (a no-break space advances the pen), so a font update cannot break the proof unseen. - Property: `fit` equals the search of every size, block for block, over 3600 generated texts, boxes, sizes, floors and line counts. - The goldens are unchanged. A README beside them records the JDK builds and the FreeType each ships. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> |
||
|
|
f5acf93e3d
|
feat(renderer): scene 2 text: bundled faces, measurement before drawing, stated fit rules (P2)
A version 2 `text` element is now drawn. It replaces the placeholder that parsed and placed a text and refused it as RENDERER_TOO_OLD. What a scene can say - `font` names one of the five bundled faces (or comes from `defaults`); any other name is FONT_UNKNOWN. There is no printer font and no installed font. - `size` is the em as a physical length (1 pt to 1000 pt), not rounded to dots. - `value` or `valuePath`; `align`/`valign` place the block in the box and each line in the block; `lineHeight`, `rotation`, `reverse`, `threshold`. - `fit`: one line by default, shrunk in quarter-point steps to 70% of the size (not below 5 pt), then refused. Opt in to more lines (`maxLines`) and to cutting with an ellipsis (`overflow: truncate`). - `maxLength`: validation proves the longest value fits, so a length refusal happens when a scene is published; printing refuses a longer value first. How it is measured and drawn - Measurement is integer arithmetic on the face's own numbers (thousandths of the em, read once at a reference size), so it is the same on every machine. Every character sits at a whole-dot pen position and is drawn there by the Java rasteriser that version 1 raster text uses, then reduced to ink at the threshold. A line's box is its advance widened to its ink plus two dots a side; a render whose ink would leave it fails instead of clipping. - At each size a line is broken between words while that fits and at any character when it does not, which is also what makes a length provable. - One glyph for each character, left to right: no kerning, ligatures, mark placement or bidirectional text. A character that cannot be drawn that way is FONT_MISSING_GLYPH with a count. Diagnostics (ids and numbers only; no value ever enters one) TEXT_DOES_NOT_FIT, VALUE_MISSING, VALUE_TOO_LONG, FONT_UNKNOWN, FONT_MISSING_GLYPH, RESERVED_FONT_NAME; TEXT_TRUNCATED, TEXT_SMALL_FOR_DPI, TEXT_LIGHT_FACE, FIT_NOT_PROVEN; TEXT_SHRUNK, TEXT_WRAPPED. Core seams added: `ElementSource.inherit` gives an element the scene's `defaults` for the properties its type reads, and `ElementSource.refuse` raises a coded refusal at a property (FONT_UNKNOWN at the place that names the face, in the element, the defaults or an override). Tests: goldens for the five faces and for fit, rotation and reverse at 203, 300 and 600 dpi, generated in the CI image (Linux, Temurin 21.0.9) and the same bytes on Windows (21.0.12) and on Linux Temurin 21.0.13; every character of every face drawn inside its measured line; seeded property tests of fit (never shrunk when it fits, the largest size that fits, never below the floor, never more lines than allowed, cut only when asked and always with the mark, a proven length never refused); licence, provenance and hash of every face; the version 2 mutation campaign extended to text. The version 1 corpus is untouched. 100% line coverage of the scene 2 packages. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> |
||
|
|
d3378825d7
|
feat(renderer): bundle five OFL fonts for version 2 label text
Version 2 label scenes draw text in faces shipped inside the renderer, so a label needs no printer font, no installed font and no object storage: qqq-sans, qqq-sans-bold IBM Plex Sans Regular and Bold (@ibm/plex-sans@1.1.0) qqq-condensed-bold IBM Plex Sans Condensed Bold (@ibm/plex-sans-condensed@2.0.0) qqq-mono, qqq-mono-bold JetBrains Mono Regular and Bold (v2.304) The files are unmodified, taken from each project's official release archive. Each licence was confirmed as the SIL Open Font License 1.1 from the archive's own licence file, which is shipped byte for byte beside the fonts with a provenance note (release, archive URL and hash, path in the archive, font version). /fonts/SHA256SUMS lists the hashes. git is told never to rewrite anything under /fonts. THIRD-PARTY.md and NOTICE record the fonts. The OFL is not on the K22-DEP-02 allowlist; the maintainer approved one recorded exception for unmodified OFL fonts shipped as data, and the register entry is proposed with the pull request. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> |