A text object is drawn in the editor's own typeface until it is given one of its own. That
typeface is a font asset, and the text points at it through the ordinary property font —
the same design as a sprite and its texture: because it is an ordinary property, instance
overrides, Apply to container, Reset to asset, undo and Properties work on it without
a line written for them. One placed title can wear a different face than its siblings.
Getting a typeface in. Dropping a .ttf, .otf, .woff or .woff2 file onto the
canvas uploads it and makes the asset in one go; several at once are imported one after
another, and a file that fails does not stop the rest. The asset is named after the file
("DejaVuSerif.ttf" becomes "DejaVuSerif"), which is worth more than the stored name — see below.
A dropped file is recognised by its extension, not by its MIME type: browsers report a
dropped font as font/ttf, application/x-font-ttf or as nothing at all.
+ → Font asks the other question and opens the picker: every typeface the server already
holds, one row each. There is no thumbnail to show for a font, so each row is the preview —
the sample line is drawn in that very face. Picking one and pressing Use font makes the
asset; Upload… is the way in for a face the server does not have yet. A row captioned with
an asset name is one this project already wears, and picking it uses that asset rather than
making a second one; any other row is captioned with the name the file was uploaded under, and
picking it names the asset after that file — DejaVuSerif.ttf becomes DejaVuSerif.
Where it goes. The bytes are uploaded to POST ?r=fonts and stored as
data/fonts/{folder}/{sha256}.{ext} — in the uploader's folder and named after their own
content, exactly like an image, and with the same four consequences (one file per typeface
however often it is uploaded, cacheable forever, a short string in the project document, an
undoable delete). Delete in the picker removes a file of your own nobody uses, as for
images. Unlike an image, a font
is never re-encoded on the way: what the server stores is the file that was picked. The
format is read from the file's own signature, so nothing a request claims can decide what is
written. Bitmap collections (.ttc) and SVG fonts are refused — a collection holds several
faces and @font-face cannot say which, and an SVG font can carry script.
Wearing it. A text object's inspector has a Font row between Text and Font size: a
dropdown of the project's font assets, with None for the editor's own typeface. That is
also what a reference falls back to when the asset behind it is gone, so a deleted font leaves
readable text rather than nothing. The row works in the scene view, in the game preview and
while playing — all three go through one place (WorldViewport.syncText, which hands the text to
TextPicture: glyphs rasterised into a picture of the text's own, drawn like a sprite so a shader
can be worn — see Shaders).
Letter spacing. Below Font size, in the same units: what is added between two characters, so a text of ten characters grows by nine times the value. Negative draws them closer, which is how a design's tracking in em carries over: -0.03em at a font size of 216 is -6.5. The spacing counts into the text's footprint, so selecting, picking and "Add collider" follow it.
Height. A text has no height row: it is as tall as the line box its face declares (the
browser's fontBoundingBoxAscent + fontBoundingBoxDescent, which is where the face says a
line begins and ends), each further line one Pixi line lower. Pixi on its own makes a line only
as tall as |ÉqÅM reaches and cuts off whatever reaches further: the tail of Manrope's "g" is
0.015em deeper than its "q" and was cut flat. Where Pixi's box is the taller one, it is kept. So
the height does not change with what is typed, and a glyph only loses ink if it reaches beyond
the face's own metrics. By Manrope's font file, a line is 1.366em instead of 1.25em.
Filter. A font asset has one row, Filter, the same as a texture's: Linear (the default) draws the glyphs smooth and renders them again, sharper, as the view zooms in. Nearest is for pixel fonts: the glyphs end up at one texel per world unit and are magnified without smoothing, so at a scale or zoom of 4 every font pixel is a block of four screen pixels. To get there they are rasterised eight times finer and brought down again, each 8x8 block becoming one texel that is either fully inked (half the block or more) or empty. No texel is left half covered, on any platform — Chrome on Windows smooths canvas text sideways whatever the filter, and without this step every glyph column there had a grey fringe. The texture stays as small as at 1:1. A pixel font looks cleanest at the size it was drawn for (8 or 16 for most); at any other size its pixels come out hard but unevenly wide, and a font that is no pixel font loses its thinnest strokes. Every text wearing the font follows its filter; the text itself has no filter row.
It arrives late. A face is fetched over the network, so a text is drawn in the fallback for the first frames and then reflows into the real one. That is the same "not yet, redraw when it lands" contract the image library has, and the reason the width of a text can change shortly after a project opens.
The family name is derived, not read. A face is registered with the browser as
gm-font-<first 16 characters of the hash>, never under the name inside the font: two uploads
of "Regular" cannot then fight over one family, and the same bytes always resolve to the same
face. The name inside the file is not read at all — the asset's name comes from the uploaded
file name and is yours to change.
The frame is what is drawn. The selection box, the click area in a running game and the
camera's framing take the size of the text's picture (setTextMeasure in bounds.ts, set by
textPicture.ts). Where nothing can measure — the unit tests in node — they fall back to an
estimate from the character count (CHAR_WIDTH, LINE_HEIGHT).
Known limits. Only text objects wear fonts; a sprite has no use for one. There is no bold/italic pair: a font asset is one file, so a bold face is a second asset.
Using the editor
- Concepts
- Accounts
- Agents (MCP)
- Scenes
- Scene editing
- Behaviors
- Structs
- Colliders
- Physics
- Textures
- Vector art
- Pixel art
- Fonts
- Videos
- Sounds
- Instruments
- Patterns
- Songs
- Animations
- Curves
- Tiles
- Tilemaps
- State machines
- Shaders
- Particles
- Containers
- Compositions
- Keyboard
Working on the code