Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

#### Changed

- **text:** `verticalAlign: 'middle'` centers the band from the first line's cap height to the last line's baseline, instead of the string's rendered glyphs. A label no longer moves when its text changes ("LEVEL 1" and "QUIT", or a lowercase label gaining a "g"), and labels centered at the same point share a baseline. `createButton`, `createDropdown` and `createTooltip` labels move with it. If you offset a `'middle'` label by hand to put its capitals in the middle, remove the offset
- **text:** The font atlas generator measures `metrics.capHeight` on the reference capital's ink, without the distance field padding around its quad, and the shipped default font is updated (`capHeight` goes from `0.881` to `0.690` em). `verticalAlign: 'capline'` now puts the top of a capital exactly on the anchor, so `'capline'` text sits about `0.19` em (times `size`) higher than before; adjust positions you tuned around the old value. Regenerate your own atlases with `forge-generate-font-atlas` to correct their `capHeight`
- **rendering:** `Color`'s red, green and blue are no longer clamped to `1`, so a tint can make a sprite brighter than its texture: a button can rest at `Color.white` and brighten on hover with a `hoverColor` such as `new Color(1.2, 1.2, 1.2)`, and on an `hdr` camera a sprite tinted `new Color(3, 3, 3)` blooms more than a white-tinted one. On the canvas or an 8-bit render target, each channel of the result still stops at full brightness. Negative channels are still clamped to `0` and alpha to `[0, 1]`, and `toRGBAString` writes channels above `1` as `255`. If you dimmed sprites below `1` at rest so they could brighten, tint them `Color.white` at rest and above `1` when brightened instead. If you relied on values above `1` being clamped (for example a color eased with an overshooting easing), clamp them yourself. `Color.fromHSLA` now throws for a saturation or lightness outside `0`-`100`
- **ecs:** Entities are now generational handles, so a reference to a removed entity can never point at an unrelated entity that took its place. A handle packs a slot index and a generation into the same `number`; a reused slot gets a new handle, so the removed entity's handle stops matching anything: `getComponent` returns `null` for it, the new `EcsWorld.isAlive(entity)` returns `false`, and `removeEntity` on it does nothing and returns `false` (it now returns `true` when it removes an entity). Removing the same entity twice in a tick no longer hands its id to two later entities, and the least recently freed slot is reused first. An entity now stays alive until `removeEntity`: `removeComponent` no longer removes an entity whose last component it removed, so call `removeEntity` yourself if you relied on that. `addComponent` and `addTag` now throw for a removed entity (or a handle the world didn't create) instead of silently writing to it. Entities of reused slots are no longer small numbers, so don't do arithmetic on handles or use them as array indices. New exports from `ecs`: `entityIndex`, `entityGeneration` and `formatEntity`, for debugging; error messages that name an entity now print its index and generation (e.g. `12v3`)
- **math:** Every angle and direction now follows one convention: radians, `0` along `+X`, positive turning towards `+Y` (counter-clockwise, since the world is Y-up). `Vec2.up` is now `(0, 1)` and `Vec2.down` is `(0, -1)`; if you used `Vec2.up` to mean "down the screen", use `Vec2.down`. `radiansToVector(angle)` now returns `(cos angle, sin angle)`, so `radiansToVector(0)` is `(1, 0)` and it's the inverse of `vectorToRadians`; drop any `+ Math.PI / 2` you added to round-trip between them, and add `- Math.PI / 2` when facing a direction with art drawn facing up. `applyExplosiveForce` and circle-circle collisions now push coincident bodies up rather than down
Expand Down
2 changes: 1 addition & 1 deletion assets/fonts/default/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"lineHeight": 1.0952380952380953,
"ascender": 0.9285714285714286,
"descender": -0.40476190476190477,
"capHeight": 0.8809523809523809
"capHeight": 0.6904761904761905
},
"glyphs": [
{
Expand Down
182 changes: 0 additions & 182 deletions design/text-cap-height-centering.md

This file was deleted.

6 changes: 6 additions & 0 deletions documentation-site/docs/docs/text/loading-a-font-atlas.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ const glyph = fontAtlas.data.glyphs.get('A'.codePointAt(0)!);
your desired render size to get world/screen units. `planeBounds` is
**Y-up**, relative to the glyph's baseline, matching the rest of Forge's
Y-up conventions.
- `planeBounds` include the distance field's padding around the glyph's
ink (half the `distanceRange`), because outlines and glows draw into
it. `ascender` and `descender` are measured from those padded bounds, so
they're safe outer bounds for everything a glyph renders. `capHeight` is
measured on the letter itself (a flat capital like "H"), without the
padding.
- `atlasBounds` is the glyph's texture rect, normalized `0` to `1`, with
`top` closer to the top of the atlas image than `bottom`.
- Both `planeBounds` and `atlasBounds` are `null` for glyphs with no visible
Expand Down
38 changes: 18 additions & 20 deletions documentation-site/docs/docs/text/rendering-text.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,26 +131,24 @@ addTextComponent(world, label, {
[UI: Labels and Text](../ui/labels-and-text.md), which sets this
automatically for its own stretch-anchored labels).
- `verticalAlign` (`'top'` | `'middle'` | `'bottom'` | `'baseline'` |
`'capline'`, default `'top'`) positions the shaped block's visible ink
relative to the entity's position, not its line-height box (which
typically doesn't match the ink's own extent).
- `'top'`, `'bottom'`, `'baseline'`, and `'capline'` all anchor to a
fixed reference that doesn't depend on this specific string's rendered
bounds, so a line's position stays stable as its text is edited:
`'top'` anchors the font's ascender, so text hangs _below_ the
entity's position; `'bottom'` anchors the font's descender, so text
sits _above_ it; `'capline'` is `'top'` but anchored to the font's cap
height (the top of a capital letter like "H") instead of its ascender
(the top of the font's _tallest_ glyphs, including ascenders like
"b"/"d"/"h" that reach higher than a flat capital) - useful for a
title or label set in caps, where anchoring to the taller ascender
would leave a visible gap above the text; `'baseline'` anchors the
first line's own baseline directly, most useful for single-line text.
- `'middle'` instead centers this exact string's _actual_ rendered ink: a
font's ascender is typically taller than its descender is deep (most
glyphs have no descender at all), so centering on the font's metrics
would bias every descender-less string (numbers, titles, most short UI
labels) above the true visual center of its box.
`'capline'`, default `'top'`) positions the shaped block relative to the
entity's position. Every value anchors to the font's metrics and the
number of lines, never to the glyphs the string happens to contain, so a
label doesn't move when its text changes, and two labels aligned to the
same point share a baseline.
- `'top'` anchors the first line's ascender (the top of the font's
tallest glyphs, such as "b"/"d"/"h"), so text hangs _below_ the
entity's position.
- `'bottom'` anchors the last line's descender, so text sits _above_ it.
- `'capline'` anchors the first line's cap height, the top of a capital
letter like "H", so the top of a title set in caps touches the
position.
- `'baseline'` anchors the first line's baseline.
- `'middle'` centers the band from the first line's cap height to the last
line's baseline on the position. That's where a designer centers a
label in a button: capitals and digits sit exactly in the middle, and
descenders like "g"/"y" hang below the band. `createButton`,
`createDropdown` and `createTooltip` center their labels this way.
- `lineHeight` (default `1`) multiplies the font atlas's own authored line
height to control the vertical distance between line baselines.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ const columns: {
* block of text against a shared, highlighted anchor line at the same
* entity position, so the difference between them is exactly what moves
* relative to that line - `top` hangs below it, `bottom` sits above it,
* `middle` straddles it on this exact text's own rendered ink, `baseline`
* puts the first line's baseline directly on it, and `capline` hangs below
* it like `top` but from the shorter cap height instead of the ascender.
* `middle` centers the band from the first line's cap height to the last
* line's baseline on it, `baseline` puts the first line's baseline
* directly on it, and `capline` hangs below it like `top` but from the
* shorter cap height instead of the ascender.
* @param world - The ECS world to add label entities to.
* @param fontAtlas - The font atlas every label draws from.
* @param whiteSprite - A plain white sprite template for the anchor lines.
Expand Down
Loading
Loading