Neuer Asset Type "font" #74

Closed
opened 2026-09-13 12:59:03 +02:00 by admin · 1 comment

wie beim texture upload: drag and drop bzw. wenn "+" => Font vorher noch dialog mit vorhandenen fonts die schon hochgeladen wurden. das font asset soll man dann bei einem text asset auswählen können. wählt man beim text kein font aus, dann einfach default verwenden (das was momentan verwendet wird)

wie beim texture upload: drag and drop bzw. wenn "+" => Font vorher noch dialog mit vorhandenen fonts die schon hochgeladen wurden. das font asset soll man dann bei einem text asset auswählen können. wählt man beim text kein font aus, dann einfach default verwenden (das was momentan verwendet wird)
admin added this to the Development project 2026-09-13 13:29:08 +02:00
admin moved this to Next in Development on 2026-09-13 16:28:30 +02:00
admin moved this to Queue in Development on 2026-09-13 16:28:54 +02:00
admin moved this to Next in Development on 2026-09-13 18:52:31 +02:00
Owner

Built and closed with commit 86b5363, wiki eea795f (new page Fonts).

What it is

A font asset holds a file and nothing else — the twin of texture, not placeable. A text object wears one through the ordinary property font (row between Text and Font size); empty means the editor's own typeface. Being a property, it gets override/reset/apply, undo, loading of older projects and the Properties node for free.

Way in Behaviour
Drop / paste a .ttf/.otf/.woff/.woff2 uploads and makes the asset, named after the file ("DejaVuSansMono")
+ → Font a dialog listing everything the server holds, one row per face, and the row is the preview — it renders a sample line in that very face. No separate tab
Inspector of a font asset a preview line in the real face (there is nothing else to show)
Text object Font dropdown with None

Server: api/lib/FontStore.php plus GET/POST/DELETE ?r=fonts, content-addressed data/fonts/{sha256}.{ext}. The format comes from the first four bytes — finfo is useless for fonts (font/sfnt, application/vnd.ms-opentype or plain octet-stream depending on libmagic). .ttc and SVG fonts are refused: a collection holds several faces and @font-face cannot say which, and SVG can carry script. Size is not capped — an upload that is too large fails with a plain message rather than a silently changed nginx config.

Drawing: one place, WorldViewport.syncText — scene, game preview and play at once. FontLibrary loads through FontFace, answers '' while it is not there and triggers the redraw when it arrives. The family is gm-font-<hash16>, never the name inside the file, so two faces both called "Regular" cannot collide.

Proof

./check.sh === CHECK EXIT 0 ===, 882 tests (+13: 7 model, 6 import)
full e2e locally ALL PASSED – 775 checks (763 + 1 guard + 11 in the new step 30)
the three games 24 / 19 / 21, === SMOKES EXIT 0 ===
looked at three screenshots: the picker (two rows in visibly different faces), the scene ("Handgloves" in serif), the asset inspector with its preview

Three counter-tests, each biting in its own place: ignore the font in syncText → exactly the "redraws in that face" check fails; drop the Font schema row → exactly the Font row check fails; put the shader sprite back on the camera → the new guard fails and the Pow check fails with the CI pixel.

The side finding: CI was genuinely red, and not from load

Runs 24 and 25 both failed at the same Pow check with rootFar: #102df2. My load-collision reading was wrong. Measured: step 28 puts its test sprite on the camera, and in a chained run the container symbols of earlier steps sit there — symbols draw above objects. #102df2 is the container colour 0x3fb8c9 at ~25 % over blue. Alone the step was green, chained it was not.

Fixed by moving the sprite 180/100 away from the camera and adding a guard that samples a grid over the sprite and names every foreign pixel. Counter-test: sprite back on the camera → the guard lists the intruders and the Pow check fails with exactly the #102df2 from CI.

Two mistakes of mine that only the screenshot showed

  • textStyle('body') hands out a shared instance. I wrote the family into it — the whole interface turned serif and overlapped. Now an own TextStyle.
  • Children on a Graphics instead of a Container → the Pixi v8 deprecation in the console. Fixed, and step 30 now guards it (clear the console, open the picker, expect no warning line).

Limits worth knowing

The selection box and hit area of a text are still estimated from the character count (CHAR_WIDTH), not measured — a very narrow or wide face sits in a box that does not quite fit. No bold/italic pairing: one font asset is one file. The face arrives asynchronously, so a text stands in the default typeface for a moment and then reflows.

The seeded typefaces come from the system (/usr/share/fonts/truetype/dejavu), not the repo; the CI image installs the same fonts-dejavu-core, and both files are byte-identical to the host's (sha256 compared inside game-maker-ci:latest) — the checks measure text widths, so a different set would move them.

Built and closed with commit `86b5363`, wiki `eea795f` (new page *Fonts*). ## What it is A **font asset** holds a `file` and nothing else — the twin of `texture`, not placeable. A **text object** wears one through the ordinary property `font` (row between *Text* and *Font size*); empty means the editor's own typeface. Being a property, it gets override/reset/apply, undo, loading of older projects and the `Properties` node for free. | Way in | Behaviour | |---|---| | Drop / paste a `.ttf/.otf/.woff/.woff2` | uploads and makes the asset, named after the file ("DejaVuSansMono") | | `+ → Font` | a dialog listing **everything the server holds**, one row per face, and the row *is* the preview — it renders a sample line in that very face. No separate tab | | Inspector of a font asset | a preview line in the real face (there is nothing else to show) | | Text object | `Font` dropdown with `None` | **Server:** `api/lib/FontStore.php` plus `GET/POST/DELETE ?r=fonts`, content-addressed `data/fonts/{sha256}.{ext}`. The format comes from the **first four bytes** — `finfo` is useless for fonts (`font/sfnt`, `application/vnd.ms-opentype` or plain `octet-stream` depending on libmagic). `.ttc` and SVG fonts are refused: a collection holds several faces and `@font-face` cannot say which, and SVG can carry script. Size is not capped — an upload that is too large fails with a plain message rather than a silently changed nginx config. **Drawing:** one place, `WorldViewport.syncText` — scene, game preview and play at once. `FontLibrary` loads through `FontFace`, answers `''` while it is not there and triggers the redraw when it arrives. The family is `gm-font-<hash16>`, **never** the name inside the file, so two faces both called "Regular" cannot collide. ## Proof | | | |---|---| | `./check.sh` | `=== CHECK EXIT 0 ===`, **882 tests** (+13: 7 model, 6 import) | | full e2e locally | **ALL PASSED – 775 checks** (763 + 1 guard + 11 in the new step 30) | | the three games | 24 / 19 / 21, `=== SMOKES EXIT 0 ===` | | looked at | three screenshots: the picker (two rows in visibly different faces), the scene ("Handgloves" in serif), the asset inspector with its preview | **Three counter-tests, each biting in its own place:** ignore the font in `syncText` → exactly the "redraws in that face" check fails; drop the `Font` schema row → exactly the Font row check fails; put the shader sprite back on the camera → the new guard fails *and* the Pow check fails with the CI pixel. ## The side finding: CI was genuinely red, and not from load Runs 24 and 25 both failed at the same Pow check with `rootFar: #102df2`. My load-collision reading was wrong. Measured: step 28 puts its test sprite on the camera, and **in a chained run** the container symbols of earlier steps sit there — symbols draw above objects. `#102df2` is the container colour `0x3fb8c9` at ~25 % over blue. Alone the step was green, chained it was not. Fixed by moving the sprite 180/100 away from the camera **and** adding a guard that samples a grid over the sprite and names every foreign pixel. Counter-test: sprite back on the camera → the guard lists the intruders and the Pow check fails with exactly the `#102df2` from CI. ## Two mistakes of mine that only the screenshot showed - **`textStyle('body')` hands out a shared instance.** I wrote the family into it — the whole interface turned serif and overlapped. Now an own `TextStyle`. - **Children on a `Graphics`** instead of a `Container` → the Pixi v8 deprecation in the console. Fixed, and step 30 now guards it (clear the console, open the picker, expect no warning line). ## Limits worth knowing The selection box and hit area of a text are still **estimated** from the character count (`CHAR_WIDTH`), not measured — a very narrow or wide face sits in a box that does not quite fit. No bold/italic pairing: one font asset is one file. The face arrives asynchronously, so a text stands in the default typeface for a moment and then reflows. The seeded typefaces come from the system (`/usr/share/fonts/truetype/dejavu`), not the repo; the CI image installs the same `fonts-dejavu-core`, and both files are **byte-identical** to the host's (`sha256` compared inside `game-maker-ci:latest`) — the checks measure text widths, so a different set would move them.
admin moved this to Done in Development on 2026-09-13 20:13:02 +02:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#74