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
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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)
Built and closed with commit
86b5363, wikieea795f(new page Fonts).What it is
A font asset holds a
fileand nothing else — the twin oftexture, not placeable. A text object wears one through the ordinary propertyfont(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 thePropertiesnode for free..ttf/.otf/.woff/.woff2+ → FontFontdropdown withNoneServer:
api/lib/FontStore.phpplusGET/POST/DELETE ?r=fonts, content-addresseddata/fonts/{sha256}.{ext}. The format comes from the first four bytes —finfois useless for fonts (font/sfnt,application/vnd.ms-opentypeor plainoctet-streamdepending on libmagic)..ttcand SVG fonts are refused: a collection holds several faces and@font-facecannot 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.FontLibraryloads throughFontFace, answers''while it is not there and triggers the redraw when it arrives. The family isgm-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)=== SMOKES EXIT 0 ===Three counter-tests, each biting in its own place: ignore the font in
syncText→ exactly the "redraws in that face" check fails; drop theFontschema 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.#102df2is the container colour0x3fb8c9at ~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
#102df2from 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 ownTextStyle.Graphicsinstead of aContainer→ 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 samefonts-dejavu-core, and both files are byte-identical to the host's (sha256compared insidegame-maker-ci:latest) — the checks measure text widths, so a different set would move them.