From ce4a173bba8f9c9ebc0d190a8d1b2f790b7d9dc0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 15:09:25 +0000 Subject: [PATCH 1/5] feat(text): add MSDF font atlas text rendering module Adds `@forge-game-engine/forge/text`: `loadFontAtlas`/`FontAtlas` (`/asset-loading`) parse an msdf-atlas-gen JSON metrics file and atlas PNG; `createMsdfTextRenderable` (`/rendering`) builds the GPU-side renderable, reusing the sprite vertex shader/instance data layout unchanged, paired with a new MSDF fragment shader (median-of-three-channels + screen-space- derivative antialiasing); `TextEcsComponent`/`addTextComponent` and `createTextShapingEcsSystem` (`/text`) shape a string - word-wrapping, kerning, alignment, line spacing - into glyph quads held on a `TextMeshEcsComponent`, dirty-tracked so re-shaping only happens when a shape-affecting field actually changes. `createRenderEcsSystem` draws text through an isolated expansion branch that turns each glyph into a `SpriteEcsComponent`-shaped render command, so text batches and depth-sorts with sprites in the same command buffer without touching the sprite/nine-slice hot path. Closes #584. Ships the MSDF rendering path from the issue; a default shipped font, a Canvas2D prototyping escape hatch, and text effects (outline/glow/shadow) are follow-up work. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq --- .cspell/project-words.txt | 7 + AGENTS.md | 2 + CHANGELOG.md | 1 + .../docs/docs/asset-loading/index.md | 10 +- .../docs/docs/rendering/index.md | 4 + .../docs/docs/text/_category_.json | 8 + documentation-site/docs/docs/text/index.md | 92 ++++++ .../docs/docs/text/msdf-text.md | 136 +++++++++ package.json | 6 + src/asset-loading/index.ts | 2 + src/asset-loading/load-font-atlas.test.ts | 151 ++++++++++ src/asset-loading/load-font-atlas.ts | 141 ++++++++++ src/asset-loading/types/font-atlas.ts | 122 ++++++++ src/index.ts | 1 + src/rendering/shaders/index.ts | 1 + src/rendering/shaders/msdf/index.ts | 3 + src/rendering/shaders/msdf/msdf.frag.glsl | 42 +++ src/rendering/systems/render-system.test.ts | 204 +++++++++++++- src/rendering/systems/render-system.ts | 124 +++++++++ .../create-msdf-text-renderable.test.ts | 154 +++++++++++ .../utilities/create-msdf-text-renderable.ts | 62 +++++ .../utilities/create-shader-cache.ts | 2 + src/rendering/utilities/index.ts | 1 + src/text/components/index.ts | 2 + src/text/components/text-component.test.ts | 86 ++++++ src/text/components/text-component.ts | 147 ++++++++++ .../components/text-mesh-component.test.ts | 57 ++++ src/text/components/text-mesh-component.ts | 95 +++++++ src/text/index.ts | 3 + src/text/systems/index.ts | 1 + src/text/systems/text-shaping-system.test.ts | 261 ++++++++++++++++++ src/text/systems/text-shaping-system.ts | 112 ++++++++ src/text/utilities/index.ts | 1 + src/text/utilities/shape-text.test.ts | 250 +++++++++++++++++ src/text/utilities/shape-text.ts | 251 +++++++++++++++++ 35 files changed, 2539 insertions(+), 3 deletions(-) create mode 100644 documentation-site/docs/docs/text/_category_.json create mode 100644 documentation-site/docs/docs/text/index.md create mode 100644 documentation-site/docs/docs/text/msdf-text.md create mode 100644 src/asset-loading/load-font-atlas.test.ts create mode 100644 src/asset-loading/load-font-atlas.ts create mode 100644 src/asset-loading/types/font-atlas.ts create mode 100644 src/rendering/shaders/msdf/index.ts create mode 100644 src/rendering/shaders/msdf/msdf.frag.glsl create mode 100644 src/rendering/utilities/create-msdf-text-renderable.test.ts create mode 100644 src/rendering/utilities/create-msdf-text-renderable.ts create mode 100644 src/text/components/index.ts create mode 100644 src/text/components/text-component.test.ts create mode 100644 src/text/components/text-component.ts create mode 100644 src/text/components/text-mesh-component.test.ts create mode 100644 src/text/components/text-mesh-component.ts create mode 100644 src/text/index.ts create mode 100644 src/text/systems/index.ts create mode 100644 src/text/systems/text-shaping-system.test.ts create mode 100644 src/text/systems/text-shaping-system.ts create mode 100644 src/text/utilities/index.ts create mode 100644 src/text/utilities/shape-text.test.ts create mode 100644 src/text/utilities/shape-text.ts diff --git a/.cspell/project-words.txt b/.cspell/project-words.txt index d4456708..f1814c3d 100644 --- a/.cspell/project-words.txt +++ b/.cspell/project-words.txt @@ -1,6 +1,7 @@ aabb aabbs afterrun +antialiases Aoboshi artboard attw @@ -28,6 +29,7 @@ Flaticon fphysics fract frontmatter +fwidth gamedev gamepadconnected Gamepads @@ -39,6 +41,7 @@ highp hitscan hotspot hoverable +imageout impluse Infima inigo @@ -55,6 +58,9 @@ Mertens mousedown mousemove mouseup +Msdf +msdf +MSDF Narkowicz ndot Nisbet @@ -65,6 +71,7 @@ pingpong Pixabay prebuild prestart +pxrange quilez rasterizer readback diff --git a/AGENTS.md b/AGENTS.md index 828be002..0dbd249d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,7 @@ Forge is a browser-based, code-only game engine built with TypeScript. It provid - **Input**: Keyboard, mouse, and gamepad input handling - **Particles**: Particle system - **Asset Loading**: Resource management +- **Text**: MSDF font atlas text rendering - **FSM**: Finite state machine implementation **Important**: The engine contains general-purpose game functionality. Game-specific or genre-specific code should be in separate packages. @@ -53,6 +54,7 @@ Forge is a browser-based, code-only game engine built with TypeScript. It provid /physics # Physics integration /pooling # Object pooling /rendering # Rendering system + /text # MSDF font atlas text rendering /timer # Timer utilities /utilities # General utilities index.ts # Main exports diff --git a/CHANGELOG.md b/CHANGELOG.md index 646298bf..be1d0103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **physics:** Add `raycast(world, start, end, sort?)`, casting a line segment against every entity in an `EcsWorld` with a `ColliderEcsComponent` (`CircleCollider`, `PolygonCollider`, and `TerrainCollider` alike) and returning every intersection as a `RaycastHit` (`entity`, `point`, `normal`, `distance`), ordered by distance from `start` by default - **physics:** Add `RigidBodyEcsComponent.type` (`'dynamic'` | `'kinematic'` | `'static'`, defaulting to `'dynamic'`), letting a body be moved directly by game code (`'kinematic'`) so it still pushes dynamic bodies on contact without itself being affected by gravity, forces, or impulses - previously only possible implicitly, by giving an entity no `RigidBodyEcsComponent` at all (still supported, and equivalent to `type: 'static'`) +- **text:** Add a new `@forge-game-engine/forge/text` module for MSDF (multi-channel signed distance field) text rendering: `loadFontAtlas`/`FontAtlas` (`/asset-loading`) parse an `msdf-atlas-gen` JSON metrics file and atlas PNG; `createMsdfTextRenderable` (`/rendering`) builds the GPU-side renderable from a font atlas; `TextEcsComponent`/`addTextComponent` and `createTextShapingEcsSystem` shape a string (with word-wrapping, kerning, alignment, and line spacing) into glyph quads a `TextMeshEcsComponent` holds, drawn by `createRenderEcsSystem` batched alongside sprites. Shaping is dirty-tracked, only re-running when a shape-affecting field actually changes. Does not yet include a default shipped font, a Canvas2D prototyping fallback, or text effects (outline/glow/shadow) - see the module's docs for current limitations #### Changed diff --git a/documentation-site/docs/docs/asset-loading/index.md b/documentation-site/docs/docs/asset-loading/index.md index 00079a93..8b8a6da7 100644 --- a/documentation-site/docs/docs/asset-loading/index.md +++ b/documentation-site/docs/docs/asset-loading/index.md @@ -13,12 +13,18 @@ plus two supporting building blocks: - [`AssetCache`](/Forge/docs/api/interfaces/AssetCache): the common `get` / `load` / `getOrLoad` contract that asset caches implement. `ImageCache` implements it for `HTMLImageElement`; if you add a cache for - another asset type (audio buffers, JSON data, fonts), implement this - interface so it behaves consistently with the rest of the engine. + another asset type (audio buffers, JSON data), implement this interface + so it behaves consistently with the rest of the engine. - [`AssetRegistry`](/Forge/docs/api/classes/AssetRegistry): maps human-readable string IDs to compact numeric IDs, so hot-path code (like a per-frame animation system) can look up an asset by index instead of by string. +- [`loadFontAtlas`](/Forge/docs/api/functions/loadFontAtlas): parses an + MSDF font atlas (an `msdf-atlas-gen` JSON metrics file plus its PNG + texture, loaded through `ImageCache`) into a + [`FontAtlas`](/Forge/docs/api/interfaces/FontAtlas) for + `@forge-game-engine/forge/text` to shape and draw. See + [Text](../text/index.md) for the full text rendering guide. Guides in this section: diff --git a/documentation-site/docs/docs/rendering/index.md b/documentation-site/docs/docs/rendering/index.md index 9a668c04..9e60be67 100644 --- a/documentation-site/docs/docs/rendering/index.md +++ b/documentation-site/docs/docs/rendering/index.md @@ -8,6 +8,10 @@ draws the sprites matching that camera's `cullingMask`, sorted by each sprite's `layer` (draw order, lower first) and then by depth (world Y position) within a layer, batching consecutive sprites that share a [`Renderable`](/Forge/docs/api/classes/Renderable) into a single draw call. +Shaped text (`@forge-game-engine/forge/text`) draws through this same path +- each glyph quad expands into a `SpriteEcsComponent`-shaped render command, +so a label sorts and batches with sprites exactly like a nine-sliced +panel's regions do; see [Text](../text/index.md). This section is a work in progress and currently covers the multipass rendering foundation and its first post-processing effect; a full guide to diff --git a/documentation-site/docs/docs/text/_category_.json b/documentation-site/docs/docs/text/_category_.json new file mode 100644 index 00000000..c5e2ce70 --- /dev/null +++ b/documentation-site/docs/docs/text/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Text", + "position": 11, + "link": { + "type": "doc", + "id": "docs/text/index" + } +} diff --git a/documentation-site/docs/docs/text/index.md b/documentation-site/docs/docs/text/index.md new file mode 100644 index 00000000..53b5c1ac --- /dev/null +++ b/documentation-site/docs/docs/text/index.md @@ -0,0 +1,92 @@ +# Text + +`@forge-game-engine/forge/text` renders text using +[MSDF](https://github.com/Chlumsky/msdf-atlas-gen) (multi-channel signed +distance field) font atlases: a pre-generated texture plus a JSON metrics +file, sampled by a small fragment shader that reconstructs crisp glyph +edges at any scale. Unlike drawing text to a `` and uploading it as +a texture, MSDF text batches into the same instanced draw call as sprites +and never re-uploads a texture when the string changes - the glyph +quads it draws are ordinary `SpriteEcsComponent`-shaped render commands +under the hood, so a label interleaves correctly with sprites in the same +depth-sorted draw order (see +[`createRenderEcsSystem`](/Forge/docs/api/functions/createRenderEcsSystem)). + +Three pieces work together: + +- [`loadFontAtlas`](/Forge/docs/api/functions/loadFontAtlas) + (`/asset-loading`) parses an `msdf-atlas-gen` JSON metrics file and loads + its atlas PNG into a [`FontAtlas`](/Forge/docs/api/interfaces/FontAtlas) - + glyph advances, kerning pairs, and line metrics, all normalized to em + units. +- [`createMsdfTextRenderable`](/Forge/docs/api/functions/createMsdfTextRenderable) + (`/rendering`) builds the GPU-side `Renderable` (the MSDF shader bound to + the atlas texture) for a `FontAtlas`. Call this once per font, not once + per label - every entity sharing the same `Renderable` batches together. +- [`addTextComponent`](/Forge/docs/api/functions/addTextComponent) and + [`createTextShapingEcsSystem`](/Forge/docs/api/functions/createTextShapingEcsSystem) + (`/text`) turn a string into glyph quads: attach a + [`TextEcsComponent`](/Forge/docs/api/interfaces/TextEcsComponent) to an + entity, and the shaping system dirty-tracks it into a + [`TextMeshEcsComponent`](/Forge/docs/api/interfaces/TextMeshEcsComponent) + the render system draws. + +Guides in this section: + +- [MSDF Text](./msdf-text.md): generating a font atlas, loading it, and + configuring wrapping, alignment, and line spacing. + +## Quick Start + +```ts +import { addPositionComponent } from '@forge-game-engine/forge/common'; +import { loadFontAtlas } from '@forge-game-engine/forge/asset-loading'; +import { SystemRegistrationOrder } from '@forge-game-engine/forge/ecs'; +import { createMsdfTextRenderable } from '@forge-game-engine/forge/rendering'; +import { + addTextComponent, + createTextShapingEcsSystem, +} from '@forge-game-engine/forge/text'; +import { createGame } from '@forge-game-engine/forge/utilities'; + +const { world, renderContext } = createGame('game-container'); + +const font = await loadFontAtlas( + 'fonts/roboto-msdf.json', + 'fonts/roboto-msdf.png', + renderContext.imageCache, +); +const renderable = createMsdfTextRenderable(font, renderContext); + +// Shape before render, so the render system always sees this frame's glyphs. +world.addSystem(createTextShapingEcsSystem(), SystemRegistrationOrder.late); + +const label = world.createEntity(); +addPositionComponent(world, label, { world: { x: 0, y: 0 } }); +addTextComponent(world, label, { + text: 'Hello, Forge!', + font, + renderable, + fontSize: 32, +}); +``` + +See [MSDF Text](./msdf-text.md#registration-order) for why shaping needs to +run before `createRenderEcsSystem`. + +## Current limitations + +This is the initial MSDF rendering path from +[issue #584](https://github.com/Forge-Game-Engine/Forge/issues/584). Not +yet implemented: + +- **No font shipped with the engine.** Every project currently generates + and hosts its own atlas (see [MSDF Text](./msdf-text.md#generating-an-atlas)). +- **No Canvas2D prototyping escape hatch.** `msdf-atlas-gen` is a real + toolchain step; a documented `fillText`-to-texture fallback for quick + prototyping is planned but not yet built. +- **No text effects** (outline, drop shadow, glow) - these fall out as + extra MSDF shader parameters later. +- **Word wrap only, not character wrap.** A single word wider than + `wrapWidth` is placed on its own line unbroken; see + [`shapeText`](/Forge/docs/api/functions/shapeText). diff --git a/documentation-site/docs/docs/text/msdf-text.md b/documentation-site/docs/docs/text/msdf-text.md new file mode 100644 index 00000000..f0c61097 --- /dev/null +++ b/documentation-site/docs/docs/text/msdf-text.md @@ -0,0 +1,136 @@ +--- +sidebar_position: 1 +--- + +# MSDF Text + +## Generating an atlas + +Forge doesn't ship a default font yet (see the +[current limitations](./index.md#current-limitations)), so every project +generates its own atlas with +[`msdf-atlas-gen`](https://github.com/Chlumsky/msdf-atlas-gen): + +```sh +msdf-atlas-gen -font Roboto-Regular.ttf -type msdf -format png \ + -imageout roboto-msdf.png -json roboto-msdf.json \ + -charset charset.txt -size 32 -pxrange 4 +``` + +Host the resulting `.json` and `.png` as static assets (e.g. next to your +other sprite sheets) and load them with +[`loadFontAtlas`](/Forge/docs/api/functions/loadFontAtlas): + +```ts +import { loadFontAtlas } from '@forge-game-engine/forge/asset-loading'; + +const font = await loadFontAtlas( + 'fonts/roboto-msdf.json', + 'fonts/roboto-msdf.png', + renderContext.imageCache, +); +``` + +`-charset` matters: a code point missing from the atlas is silently +skipped by `shapeText` rather than erroring, so leave out a needed +character and text using it will look wrong in a way that's easy to miss +until someone actually types that character. Generate the charset from +every string your game actually displays (including punctuation and any +non-English text) rather than guessing. + +Kerning pairs must be present in the JSON (`msdf-atlas-gen` includes them +by default) or text will look subtly wrong - slightly-too-wide gaps +between specific letter pairs like "AV" or "To" - in a way that's hard to +attribute back to "missing kerning data" later. + +## Drawing text + +[`createMsdfTextRenderable`](/Forge/docs/api/functions/createMsdfTextRenderable) +builds the GPU-side renderable once per `FontAtlas` - share it across every +entity using that font, the same way one `Renderable` is shared across +every sprite using the same spritesheet: + +```ts +import { createMsdfTextRenderable } from '@forge-game-engine/forge/rendering'; + +const renderable = createMsdfTextRenderable(font, renderContext); +``` + +Then attach a [`TextEcsComponent`](/Forge/docs/api/interfaces/TextEcsComponent) +with [`addTextComponent`](/Forge/docs/api/functions/addTextComponent): + +```ts +import { addPositionComponent } from '@forge-game-engine/forge/common'; +import { Color } from '@forge-game-engine/forge/rendering'; +import { addTextComponent } from '@forge-game-engine/forge/text'; + +const score = world.createEntity(); +addPositionComponent(world, score, { world: { x: -300, y: 260 } }); +addTextComponent(world, score, { + text: 'Score: 0', + font, + renderable, + fontSize: 24, + color: new Color(1, 0.9, 0.2, 1), +}); +``` + +Updating `text` (e.g. every time the score changes) is cheap to write - +just assign a new string to `textComponent.text` - and +[`createTextShapingEcsSystem`](/Forge/docs/api/functions/createTextShapingEcsSystem) +only re-shapes the entities whose text actually changed that frame; see +[Dirty tracking](#dirty-tracking). + +## Wrapping, alignment, and line spacing + +```ts +addTextComponent(world, dialogueEntity, { + text: 'A long line of dialogue that should wrap inside the text box.', + font, + renderable, + fontSize: 18, + wrapWidth: 280, + alignment: 'center', + lineSpacing: 1.2, +}); +``` + +- **`wrapWidth`** (world units) word-wraps `text` to fit, breaking between + words. Omit it for a single line however long `text` is. A single word + wider than `wrapWidth` is placed on its own line unbroken - there's no + character-level breaking yet. +- **`alignment`** (`'left'` | `'center'` | `'right'`, default `'left'`) + positions each line relative to the text block's own width (the widest + line, or `wrapWidth` when set). +- **`lineSpacing`** (default `1`) multiplies `font.lineHeight` for the + vertical gap between line baselines. +- **`pivot`** (default `(0.5, 0.5)`, same convention as + [`SpriteEcsComponent.pivot`](/Forge/docs/api/interfaces/SpriteEcsComponent)) + is the text block's origin, normalized to its own size: `(0, 0)` anchors + the block's top-left corner to the entity's position, `(1, 1)` its + bottom-right. +- Explicit `\n` characters in `text` always start a new line, wrapped or not. + +## Registration order + +Where `createTextShapingEcsSystem` sits in the tick matters: + +- **Before `createRenderEcsSystem`.** The render system reads a + `TextMeshEcsComponent`'s glyph quads directly - an entity with a + `TextEcsComponent` but no `TextMeshEcsComponent` yet (never shaped) is + simply skipped, so register shaping with a lower + `SystemRegistrationOrder` + than rendering (`SystemRegistrationOrder.late`, from `@forge-game-engine/forge/ecs`, is a safe default for both). +- **After anything that changes `wrapWidth` at runtime**, if you're + computing it dynamically (e.g. from a resizable container's width) + rather than passing a fixed number. + +## Dirty tracking + +Re-shaping a paragraph - word-wrapping, kerning lookups, one quad per +character - is real cost, unlike most systems in a typical ECS pipeline +that recompute unconditionally every frame. `createTextShapingEcsSystem` +only re-shapes an entity when `text`, `font`, `fontSize`, `wrapWidth`, +`lineSpacing`, `alignment`, or `pivot` changed since its last shape. +Changing `color`, `enabled`, or `layer` never triggers a re-shape - those +only affect how the already-shaped glyphs are drawn. diff --git a/package.json b/package.json index ac2cb09d..862fd90d 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,12 @@ "default": "./dist/rendering/index.js" } }, + "./text": { + "import": { + "types": "./dist/text/index.d.ts", + "default": "./dist/text/index.js" + } + }, "./timer": { "import": { "types": "./dist/timer/index.d.ts", diff --git a/src/asset-loading/index.ts b/src/asset-loading/index.ts index 24e29cd0..80722edc 100644 --- a/src/asset-loading/index.ts +++ b/src/asset-loading/index.ts @@ -1,3 +1,5 @@ export * from './asset-cache.js'; export * from './asset-caches/index.js'; export * from './asset-registry.js'; +export * from './load-font-atlas.js'; +export * from './types/font-atlas.js'; diff --git a/src/asset-loading/load-font-atlas.test.ts b/src/asset-loading/load-font-atlas.test.ts new file mode 100644 index 00000000..c82a2cc4 --- /dev/null +++ b/src/asset-loading/load-font-atlas.test.ts @@ -0,0 +1,151 @@ +import { describe, expect, it, vi } from 'vitest'; +import { ImageCache } from './asset-caches/index.js'; +import { loadFontAtlas } from './load-font-atlas.js'; + +const msdfAtlasGenJson = { + atlas: { + distanceRange: 4, + width: 512, + height: 256, + yOrigin: 'bottom' as const, + }, + metrics: { + emSize: 1, + lineHeight: 1.2, + ascender: 0.95, + descender: -0.25, + }, + glyphs: [ + { unicode: 32, advance: 0.25 }, + { + unicode: 65, + advance: 0.66, + planeBounds: { left: 0.01, bottom: 0, right: 0.65, top: 0.68 }, + atlasBounds: { left: 100, bottom: 50, right: 164, top: 114 }, + }, + ], + kerning: [{ unicode1: 65, unicode2: 86, advance: -0.06 }], +}; + +function mockJsonResponse(body: unknown, ok = true): Response { + return { + ok, + status: ok ? 200 : 404, + statusText: ok ? 'OK' : 'Not Found', + json: () => Promise.resolve(body), + } as Response; +} + +describe('loadFontAtlas', () => { + it('parses metrics and glyphs from the msdf-atlas-gen JSON', async () => { + const mockImage = new Image(); + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + mockJsonResponse(msdfAtlasGenJson), + ); + + const imageCache = new ImageCache(); + vi.spyOn(imageCache, 'getOrLoad').mockResolvedValue(mockImage); + + const fontAtlas = await loadFontAtlas('font.json', 'font.png', imageCache); + + expect(fontAtlas.image).toBe(mockImage); + expect(fontAtlas.atlasWidth).toBe(512); + expect(fontAtlas.atlasHeight).toBe(256); + expect(fontAtlas.distanceRange).toBe(4); + expect(fontAtlas.emSize).toBe(1); + expect(fontAtlas.lineHeight).toBeCloseTo(1.2); + expect(fontAtlas.ascender).toBeCloseTo(0.95); + expect(fontAtlas.descender).toBeCloseTo(-0.25); + expect(imageCache.getOrLoad).toHaveBeenCalledWith('font.png'); + }); + + it('omits uv/plane bounds for glyphs with no visible shape', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + mockJsonResponse(msdfAtlasGenJson), + ); + + const imageCache = new ImageCache(); + vi.spyOn(imageCache, 'getOrLoad').mockResolvedValue(new Image()); + + const fontAtlas = await loadFontAtlas('font.json', 'font.png', imageCache); + + const space = fontAtlas.glyphs.get(32); + + expect(space?.advance).toBe(0.25); + expect(space?.planeBounds).toBeUndefined(); + expect(space?.uvOffset).toBeUndefined(); + expect(space?.uvScale).toBeUndefined(); + }); + + it("converts a visible glyph's bottom-left-origin atlasBounds pixels into top-left-origin normalized uv rects", async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + mockJsonResponse(msdfAtlasGenJson), + ); + + const imageCache = new ImageCache(); + vi.spyOn(imageCache, 'getOrLoad').mockResolvedValue(new Image()); + + const fontAtlas = await loadFontAtlas('font.json', 'font.png', imageCache); + + const glyphA = fontAtlas.glyphs.get(65); + + expect(glyphA?.planeBounds).toEqual({ + left: 0.01, + bottom: 0, + right: 0.65, + top: 0.68, + }); + // atlasBounds: left 100/512, right 164/512; top 114 -> 1 - 114/256, bottom 50 -> 1 - 50/256 + expect(glyphA?.uvOffset?.x).toBeCloseTo(100 / 512); + expect(glyphA?.uvOffset?.y).toBeCloseTo(1 - 114 / 256); + expect(glyphA?.uvScale?.x).toBeCloseTo((164 - 100) / 512); + expect(glyphA?.uvScale?.y).toBeCloseTo(114 / 256 - 50 / 256); + }); + + it('parses kerning pairs keyed by "unicode1:unicode2"', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + mockJsonResponse(msdfAtlasGenJson), + ); + + const imageCache = new ImageCache(); + vi.spyOn(imageCache, 'getOrLoad').mockResolvedValue(new Image()); + + const fontAtlas = await loadFontAtlas('font.json', 'font.png', imageCache); + + expect(fontAtlas.kerning.get('65:86')).toBeCloseTo(-0.06); + expect(fontAtlas.kerning.get('65:65')).toBeUndefined(); + }); + + it('throws when the metrics JSON fetch fails', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + mockJsonResponse(undefined, false), + ); + + const imageCache = new ImageCache(); + + await expect( + loadFontAtlas('font.json', 'font.png', imageCache), + ).rejects.toThrow( + 'Failed to fetch font atlas metrics at "font.json": 404 Not Found', + ); + }); + + it('defaults to no kerning when the JSON omits the kerning field', async () => { + const jsonWithoutKerning = { + atlas: msdfAtlasGenJson.atlas, + metrics: msdfAtlasGenJson.metrics, + glyphs: msdfAtlasGenJson.glyphs, + }; + + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + mockJsonResponse(jsonWithoutKerning), + ); + + const imageCache = new ImageCache(); + vi.spyOn(imageCache, 'getOrLoad').mockResolvedValue(new Image()); + + const fontAtlas = await loadFontAtlas('font.json', 'font.png', imageCache); + + expect(fontAtlas.kerning.size).toBe(0); + }); +}); diff --git a/src/asset-loading/load-font-atlas.ts b/src/asset-loading/load-font-atlas.ts new file mode 100644 index 00000000..fe9fdf7b --- /dev/null +++ b/src/asset-loading/load-font-atlas.ts @@ -0,0 +1,141 @@ +import { Vector2 } from '../math/index.js'; +import { + FontAtlas, + FontAtlasGlyph, + getKerningKey, +} from './types/font-atlas.js'; +import { ImageCache } from './asset-caches/index.js'; + +/** + * The subset of `msdf-atlas-gen`'s JSON output this loader reads. Other + * fields the tool emits (e.g. per-glyph `index`, `atlas.type`) are ignored. + */ +interface MsdfAtlasGenJson { + atlas: { + distanceRange: number; + width: number; + height: number; + yOrigin?: 'bottom' | 'top'; + }; + metrics: { + emSize: number; + lineHeight: number; + ascender: number; + descender: number; + }; + glyphs: { + unicode: number; + advance: number; + planeBounds?: { left: number; bottom: number; right: number; top: number }; + atlasBounds?: { left: number; bottom: number; right: number; top: number }; + }[]; + kerning?: { unicode1: number; unicode2: number; advance: number }[]; +} + +/** + * Converts a glyph's `atlasBounds` (pixels, measured from the bottom-left + * per `msdf-atlas-gen`'s default `yOrigin: "bottom"`) into + * `uvOffset`/`uvScale` (0 to 1, top-left origin), matching + * `SpriteEcsComponent.uvOffset`'s convention. + */ +function toUvRect( + atlasBounds: { left: number; bottom: number; right: number; top: number }, + atlasWidth: number, + atlasHeight: number, + yOrigin: 'bottom' | 'top', +): { uvOffset: Vector2; uvScale: Vector2 } { + const top = + yOrigin === 'bottom' + ? 1 - atlasBounds.top / atlasHeight + : atlasBounds.top / atlasHeight; + const bottom = + yOrigin === 'bottom' + ? 1 - atlasBounds.bottom / atlasHeight + : atlasBounds.bottom / atlasHeight; + + return { + uvOffset: { x: atlasBounds.left / atlasWidth, y: top }, + uvScale: { + x: (atlasBounds.right - atlasBounds.left) / atlasWidth, + y: bottom - top, + }, + }; +} + +function parseGlyphs( + json: MsdfAtlasGenJson, +): ReadonlyMap { + const glyphs = new Map(); + const { + width: atlasWidth, + height: atlasHeight, + yOrigin = 'bottom', + } = json.atlas; + + for (const glyph of json.glyphs) { + const uvRect = glyph.atlasBounds + ? toUvRect(glyph.atlasBounds, atlasWidth, atlasHeight, yOrigin) + : undefined; + + glyphs.set(glyph.unicode, { + advance: glyph.advance, + planeBounds: glyph.planeBounds, + uvOffset: uvRect?.uvOffset, + uvScale: uvRect?.uvScale, + }); + } + + return glyphs; +} + +function parseKerning(json: MsdfAtlasGenJson): ReadonlyMap { + const kerning = new Map(); + + for (const pair of json.kerning ?? []) { + kerning.set(getKerningKey(pair.unicode1, pair.unicode2), pair.advance); + } + + return kerning; +} + +/** + * Loads an MSDF font atlas produced by + * [`msdf-atlas-gen`](https://github.com/Chlumsky/msdf-atlas-gen): its JSON + * metrics file and its atlas PNG. Data-only - build the GPU-side + * `Renderable` for drawing text with the loaded atlas via + * `createMsdfTextRenderable` (`/rendering`). + * @param jsonPath - The URL of the atlas's `msdf-atlas-gen` JSON metrics file. + * @param imagePath - The URL of the atlas's PNG texture. + * @param imageCache - The image cache to load `imagePath` through. + * @returns The parsed font atlas. + * @throws An error if the JSON metrics file can't be fetched or parsed. + */ +export async function loadFontAtlas( + jsonPath: string, + imagePath: string, + imageCache: ImageCache, +): Promise { + const response = await fetch(jsonPath); + + if (!response.ok) { + throw new Error( + `Failed to fetch font atlas metrics at "${jsonPath}": ${response.status} ${response.statusText}`, + ); + } + + const json = (await response.json()) as MsdfAtlasGenJson; + const image = await imageCache.getOrLoad(imagePath); + + return { + image, + atlasWidth: json.atlas.width, + atlasHeight: json.atlas.height, + distanceRange: json.atlas.distanceRange, + emSize: json.metrics.emSize, + lineHeight: json.metrics.lineHeight, + ascender: json.metrics.ascender, + descender: json.metrics.descender, + glyphs: parseGlyphs(json), + kerning: parseKerning(json), + }; +} diff --git a/src/asset-loading/types/font-atlas.ts b/src/asset-loading/types/font-atlas.ts new file mode 100644 index 00000000..542b2409 --- /dev/null +++ b/src/asset-loading/types/font-atlas.ts @@ -0,0 +1,122 @@ +import { Vector2 } from '../../math/index.js'; + +/** + * A single glyph's metrics, as produced by `msdf-atlas-gen`'s JSON output. + * + * `planeBounds`/`uvOffset`/`uvScale` are all omitted together for glyphs + * with no visible shape (e.g. the space character) - such a glyph still + * advances the cursor but contributes no glyph quad. + */ +export interface FontAtlasGlyph { + /** + * How far the cursor moves (in em units) after placing this glyph. + */ + advance: number; + + /** + * The glyph's visible quad, in em units relative to the glyph's baseline + * origin (`left`/`right` relative to the cursor, `bottom`/`top` relative + * to the baseline, `top` positive/upward). Omitted for glyphs with no + * visible shape. + */ + planeBounds?: { + left: number; + bottom: number; + right: number; + top: number; + }; + + /** + * The top-left corner of this glyph's region in the atlas texture, 0 to 1 + * - already normalized from `msdf-atlas-gen`'s bottom-left-origin pixel + * `atlasBounds` to match `SpriteEcsComponent.uvOffset`'s top-left, Y-down + * convention. Omitted for glyphs with no visible shape. + */ + uvOffset?: Vector2; + + /** + * The width/height of this glyph's region in the atlas texture, 0 to 1. + * Omitted for glyphs with no visible shape. + */ + uvScale?: Vector2; +} + +/** + * Parsed metrics and glyph metadata for an MSDF font atlas, as produced by + * [`msdf-atlas-gen`](https://github.com/Chlumsky/msdf-atlas-gen)'s JSON + * output alongside its atlas PNG. Data-only - pairs with + * `createMsdfTextRenderable` (`/rendering`) to build the GPU-side + * `Renderable` used to actually draw text with this font. + */ +export interface FontAtlas { + /** + * The loaded atlas texture image, ready to be uploaded to the GPU by + * `createMsdfTextRenderable`. + */ + image: HTMLImageElement; + + /** + * The atlas image's width, in pixels. + */ + atlasWidth: number; + + /** + * The atlas image's height, in pixels. + */ + atlasHeight: number; + + /** + * The signed distance field range, in atlas pixels, that the MSDF was + * generated with. Feeds the fragment shader's screen-space-derivative + * antialiasing so glyph edges stay crisp at any scale. + */ + distanceRange: number; + + /** + * The em size (in the same units as `metrics.lineHeight`/glyph `advance` + * values) that every other measurement in this atlas is normalized + * against. A consumer scale factor is `fontSize / emSize`. + */ + emSize: number; + + /** + * The vertical distance (in em units) between successive lines' baselines. + */ + lineHeight: number; + + /** + * The distance (in em units) from the baseline to the top of a typical + * ascending glyph (e.g. "h"). + */ + ascender: number; + + /** + * The distance (in em units) from the baseline to the bottom of a typical + * descending glyph (e.g. "g"). Negative. + */ + descender: number; + + /** + * Every glyph in the atlas, keyed by Unicode code point. + */ + glyphs: ReadonlyMap; + + /** + * Kerning adjustments (in em units, added to the first code point's + * `advance`) for specific consecutive code point pairs, keyed by + * `` `${unicode1}:${unicode2}` ``. A pair with no entry has no kerning + * adjustment. + */ + kerning: ReadonlyMap; +} + +/** + * Builds the `` `${unicode1}:${unicode2}` `` key `FontAtlas.kerning` is + * indexed by. + * @param unicode1 - The first code point. + * @param unicode2 - The second code point. + * @returns The kerning map key for this code point pair. + */ +export function getKerningKey(unicode1: number, unicode2: number): string { + return `${unicode1}:${unicode2}`; +} diff --git a/src/index.ts b/src/index.ts index 520d6fe4..a9caf667 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ export * from './input/index.js'; export * from './lifecycle/index.js'; export * from './math/index.js'; export * from './rendering/index.js'; +export * from './text/index.js'; export * from './timer/index.js'; export * from './utilities/index.js'; export * from './finite-state-machine/index.js'; diff --git a/src/rendering/shaders/index.ts b/src/rendering/shaders/index.ts index d0295024..4859d0da 100644 --- a/src/rendering/shaders/index.ts +++ b/src/rendering/shaders/index.ts @@ -1,4 +1,5 @@ export * from './utils/index.js'; +export * from './msdf/index.js'; export * from './sprite/index.js'; export * from './post-process/index.js'; export * from './gradients/index.js'; diff --git a/src/rendering/shaders/msdf/index.ts b/src/rendering/shaders/msdf/index.ts new file mode 100644 index 00000000..80176bfc --- /dev/null +++ b/src/rendering/shaders/msdf/index.ts @@ -0,0 +1,3 @@ +import msdfFragmentShaderSource from './msdf.frag.glsl?raw'; + +export const msdfFragmentShader = msdfFragmentShaderSource; diff --git a/src/rendering/shaders/msdf/msdf.frag.glsl b/src/rendering/shaders/msdf/msdf.frag.glsl new file mode 100644 index 00000000..e5ddde0c --- /dev/null +++ b/src/rendering/shaders/msdf/msdf.frag.glsl @@ -0,0 +1,42 @@ +#version 300 es + +#pragma forge name(msdf.frag) + +precision mediump float; + +uniform sampler2D u_texture; // The MSDF font atlas +uniform float u_distanceRange; // The atlas's signed distance field range, in atlas pixels + +in vec2 v_texCoord; // Input from vertex shader +in vec4 v_tint; // Tint color (text color) +out vec4 fragColor; // Output color + +// The median of the MSDF's three channels reconstructs the single-channel +// signed distance field (see msdf-atlas-gen's own recommended shader): +// wherever two of the three channels agree, their shared value is the "true" +// distance, since only one channel is ever allowed to diverge to preserve a +// sharp corner. +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +// Converts `u_distanceRange` (in atlas pixels) into screen pixels via the +// texture coordinate's screen-space derivative (`fwidth`), so the glyph +// edge's antialiasing band stays a constant width in screen pixels +// regardless of how much the text is scaled, instead of blurring when +// scaled up or aliasing when scaled down. +float screenPxRange() { + vec2 unitRange = vec2(u_distanceRange) / vec2(textureSize(u_texture, 0)); + vec2 screenTexSize = vec2(1.0) / fwidth(v_texCoord); + + return max(0.5 * dot(unitRange, screenTexSize), 1.0); +} + +void main() { + vec3 msd = texture(u_texture, v_texCoord).rgb; + float signedDistance = median(msd.r, msd.g, msd.b) - 0.5; + float screenPxDistance = screenPxRange() * signedDistance; + float alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0); + + fragColor = vec4(v_tint.rgb, v_tint.a * alpha); +} diff --git a/src/rendering/systems/render-system.test.ts b/src/rendering/systems/render-system.test.ts index caf23409..a7fc9df6 100644 --- a/src/rendering/systems/render-system.test.ts +++ b/src/rendering/systems/render-system.test.ts @@ -17,9 +17,15 @@ import { Color } from '../color'; import { Geometry } from '../geometry/geometry'; import { Material } from '../materials/material'; import { ShaderCache } from '../shaders'; -import { ImageCache } from '../../asset-loading'; +import { FontAtlas, ImageCache } from '../../asset-loading'; import { createProjectionMatrix } from '../shaders'; import { calculatePixelsPerUnit } from '../utilities/calculate-pixels-per-unit'; +import { + addTextComponent, + addTextMeshComponent, + TextEcsComponent, + TextMeshEcsComponent, +} from '../../text'; describe('createRenderEcsSystem', () => { let canvas: HTMLCanvasElement; @@ -606,4 +612,200 @@ describe('createRenderEcsSystem', () => { expect(bindInstanceData).toHaveBeenCalledTimes(1); }); }); + + describe('text', () => { + const createGlyph = (offset = Vec2.zero) => ({ + offset, + size: { x: 1, y: 1 }, + uvOffset: Vec2.zero, + uvScale: Vec2.one, + }); + + const addTextEntity = ( + renderable: Renderable, + worldY: number, + textOverrides: Partial = {}, + meshOverrides: Partial = {}, + ): number => { + const entity = world.createEntity(); + + addPositionComponent(world, entity, { + local: { x: 0, y: worldY }, + world: { x: 0, y: worldY }, + }); + const text = addTextComponent(world, entity, { + text: 'A', + font: {} as FontAtlas, + renderable, + fontSize: 1, + ...textOverrides, + }); + addTextMeshComponent(world, entity, { + glyphs: [createGlyph()], + bounds: Vec2.one, + sourceText: text.text, + sourceFont: text.font, + sourceFontSize: text.fontSize, + sourceWrapWidth: text.wrapWidth, + sourceLineSpacing: text.lineSpacing, + sourceAlignment: text.alignment, + sourcePivot: text.pivot, + ...meshOverrides, + }); + + return entity; + }; + + it('does not draw text with no TextMeshEcsComponent yet (not shaped)', () => { + addCameraEntity(); + const { renderable, bindInstanceData } = createRenderable(4); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + addTextComponent(world, entity, { + text: 'A', + font: {} as FontAtlas, + renderable, + fontSize: 1, + }); + + world.update(); + + expect(bindInstanceData).not.toHaveBeenCalled(); + expect(mockGl.drawArraysInstanced).not.toHaveBeenCalled(); + }); + + it('skips disabled text', () => { + addCameraEntity(); + const { renderable, bindInstanceData } = createRenderable(4); + + addTextEntity(renderable, 0, { enabled: false }); + + world.update(); + + expect(bindInstanceData).not.toHaveBeenCalled(); + }); + + it("skips text whose renderable category does not match the camera's culling mask", () => { + addCameraEntity(0b0010); + const { renderable, bindInstanceData } = createRenderable(4); + + renderable.category = 0b0001; + addTextEntity(renderable, 0); + + world.update(); + + expect(bindInstanceData).not.toHaveBeenCalled(); + }); + + it('draws one instance per glyph, batched into a single draw call', () => { + addCameraEntity(); + const { renderable, bindInstanceData } = createRenderable(4); + + addTextEntity(renderable, 0, undefined, { + glyphs: [createGlyph(), createGlyph(), createGlyph()], + }); + + world.update(); + + expect(bindInstanceData).toHaveBeenCalledTimes(3); + expect(mockGl.drawArraysInstanced).toHaveBeenCalledTimes(1); + expect(mockGl.drawArraysInstanced).toHaveBeenCalledWith( + undefined, + 0, + 6, + 3, + ); + }); + + it("positions each glyph at the entity's world position plus the glyph's offset", () => { + addCameraEntity(); + const { renderable, bindInstanceData } = createRenderable(4); + + const entity = world.createEntity(); + + addPositionComponent(world, entity, { + local: { x: 10, y: 20 }, + world: { x: 10, y: 20 }, + }); + const text = addTextComponent(world, entity, { + text: 'A', + font: {} as FontAtlas, + renderable, + fontSize: 1, + }); + addTextMeshComponent(world, entity, { + glyphs: [createGlyph({ x: 5, y: 3 })], + bounds: Vec2.one, + sourceText: text.text, + sourceFont: text.font, + sourceFontSize: text.fontSize, + sourceWrapWidth: text.wrapWidth, + sourceLineSpacing: text.lineSpacing, + sourceAlignment: text.alignment, + sourcePivot: text.pivot, + }); + + world.update(); + + const [{ position }] = bindInstanceData.mock.calls[0] as [ + { position: PositionEcsComponent }, + ]; + + expect(position.world).toEqual({ x: 15, y: 23 }); + }); + + it('sets each glyph instance from the shaped uv rect and the text color', () => { + addCameraEntity(); + const { renderable, bindInstanceData } = createRenderable(4); + const color = new Color(1, 0, 0, 1); + + addTextEntity( + renderable, + 0, + { color }, + { + glyphs: [ + { + offset: Vec2.zero, + size: { x: 2, y: 3 }, + uvOffset: { x: 0.25, y: 0.5 }, + uvScale: { x: 0.1, y: 0.2 }, + }, + ], + }, + ); + + world.update(); + + const [{ sprite }] = bindInstanceData.mock.calls[0] as [ + { sprite: SpriteEcsComponent }, + ]; + + expect(sprite.width).toBe(2); + expect(sprite.height).toBe(3); + expect(sprite.uvOffset).toEqual({ x: 0.25, y: 0.5 }); + expect(sprite.uvScale).toEqual({ x: 0.1, y: 0.2 }); + expect(sprite.tintColor).toBe(color); + }); + + it('interleaves with sprites in the same sorted, depth-ordered command buffer', () => { + addCameraEntity(); + const { renderable, bindInstanceData } = createRenderable(4); + + addSpriteEntity(renderable, 10); + addTextEntity(renderable, -5); + addSpriteEntity(renderable, 2); + + world.update(); + + const drawnDepths = bindInstanceData.mock.calls.map( + (call) => + (call[0] as { position: PositionEcsComponent }).position.world.y, + ); + + expect(drawnDepths).toEqual([-5, 2, 10]); + }); + }); }); diff --git a/src/rendering/systems/render-system.ts b/src/rendering/systems/render-system.ts index 974b6f54..56fc1921 100644 --- a/src/rendering/systems/render-system.ts +++ b/src/rendering/systems/render-system.ts @@ -11,6 +11,12 @@ import { import { Matrix3x3, Vec2 } from '../../math/index.js'; import { EcsSystem } from '../../ecs/ecs-system.js'; import { matchesMask } from '../../utilities/matches-mask.js'; +import { + TextEcsComponent, + textId, + TextMeshEcsComponent, + textMeshId, +} from '../../text/components/index.js'; import { CameraEcsComponent, cameraId, @@ -170,6 +176,107 @@ const pushSpriteRenderCommands = ( } }; +// Text is expanded into per-glyph, `SpriteEcsComponent`-shaped render +// commands right here, isolated to these two functions, rather than +// generalizing nine-slice's expansion into a shared `SubQuad[]` path (see +// issue #584): every glyph quad needs exactly the same instance data a +// sprite already carries (position, rotation, scale, size, pivot, uv, +// tint), so a glyph can reuse the sprite vertex shader/instancing pipeline +// unchanged by simply constructing a `SpriteEcsComponent` for it, the same +// trick nine-slice regions already use. +function pushTextRenderCommands( + commands: RenderCommand[], + textComponent: TextEcsComponent, + textMeshComponent: TextMeshEcsComponent, + entityPosition: PositionEcsComponent, + rotationComponent: RotationEcsComponent | null, + scaleComponent: ScaleEcsComponent | null, + flipComponent: FlipEcsComponent | null, +): void { + const { renderable, layer, color } = textComponent; + const depth = entityPosition.world.y; + + const rotationRadians = rotationComponent?.world ?? 0; + const scaleX = + (scaleComponent?.world.x ?? 1) * (flipComponent?.flipX ? -1 : 1); + const scaleY = + (scaleComponent?.world.y ?? 1) * (flipComponent?.flipY ? -1 : 1); + + for (const glyph of textMeshComponent.glyphs) { + const glyphOffset = Vec2.rotate( + { x: glyph.offset.x * scaleX, y: glyph.offset.y * scaleY }, + rotationRadians, + ); + + const glyphPosition: PositionEcsComponent = { + local: entityPosition.local, + // Clone before adding, matching `pushSpriteRenderCommands`: must not + // mutate the entity's live world position. + world: Vec2.add(Vec2.clone(entityPosition.world), glyphOffset), + }; + + const glyphSprite: SpriteEcsComponent = { + enabled: true, + width: glyph.size.x, + height: glyph.size.y, + pivot: { x: 0.5, y: 0.5 }, + tintColor: color, + renderable, + uvOffset: glyph.uvOffset, + uvScale: glyph.uvScale, + layer, + }; + + commands.push({ + layer, + depth, + renderable, + components: { + position: glyphPosition, + rotation: rotationComponent, + scale: scaleComponent, + sprite: glyphSprite, + flip: flipComponent, + }, + }); + } +} + +function buildTextCameraCommands( + world: EcsWorld, + texts: TextEcsComponent[], + textMeshes: TextMeshEcsComponent[], + textPositions: PositionEcsComponent[], + textEntities: readonly number[], + cullingMask: number, + commands: RenderCommand[], +): void { + for (let t = 0; t < textEntities.length; t++) { + const textComponent = texts[t]; + + if (!textComponent.enabled) { + continue; + } + + if (!matchesMask(textComponent.renderable.category, cullingMask)) { + continue; + } + + const textEntity = textEntities[t]; + const entityPosition = textPositions[t]; + + pushTextRenderCommands( + commands, + textComponent, + textMeshes[t], + entityPosition, + world.getComponent(textEntity, rotationId), + world.getComponent(textEntity, scaleId), + world.getComponent(textEntity, flipId), + ); + } +} + function buildCameraCommands( world: EcsWorld, sprites: SpriteEcsComponent[], @@ -246,6 +353,13 @@ export const createRenderEcsSystem = ( positionId, ]); + const { + entities: textEntities, + components: [texts, textMeshes, textPositions], + } = world.query< + [TextEcsComponent, TextMeshEcsComponent, PositionEcsComponent] + >([textId, textMeshId, positionId]); + for (let c = 0; c < cameras.length; c++) { const cameraComponent = cameras[c]; const cameraPositionComponent = cameraPositions[c]; @@ -281,6 +395,16 @@ export const createRenderEcsSystem = ( commands, ); + buildTextCameraCommands( + world, + texts, + textMeshes, + textPositions, + textEntities, + cameraComponent.cullingMask, + commands, + ); + const target = cameraComponent.renderTarget ?? null; renderContext.bindRenderTarget(target); diff --git a/src/rendering/utilities/create-msdf-text-renderable.test.ts b/src/rendering/utilities/create-msdf-text-renderable.test.ts new file mode 100644 index 00000000..df9b137b --- /dev/null +++ b/src/rendering/utilities/create-msdf-text-renderable.test.ts @@ -0,0 +1,154 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import { createMsdfTextRenderable } from './create-msdf-text-renderable.js'; +import { FontAtlas, ImageCache } from '../../asset-loading/index.js'; +import { RenderContext } from '../render-context.js'; +import { + ForgeShaderSource, + msdfFragmentShader, + ShaderCache, + spriteVertexShader, +} from '../shaders/index.js'; + +// Mock WebGLTexture constructor for instanceof checks in Material.bind +globalThis.WebGLTexture = class WebGLTexture {}; + +describe('createMsdfTextRenderable', () => { + let canvas: HTMLCanvasElement; + let mockGl: WebGL2RenderingContext; + let renderContext: RenderContext; + let fontAtlas: FontAtlas; + let distanceRangeLocation: WebGLUniformLocation; + + beforeEach(() => { + canvas = document.createElement('canvas'); + canvas.width = 800; + canvas.height = 600; + + distanceRangeLocation = {}; + + fontAtlas = { + image: { width: 512, height: 512 } as HTMLImageElement, + atlasWidth: 512, + atlasHeight: 512, + distanceRange: 4, + emSize: 1, + lineHeight: 1.2, + ascender: 0.95, + descender: -0.25, + glyphs: new Map(), + kerning: new Map(), + }; + + mockGl = { + VERTEX_SHADER: 'VERTEX_SHADER', + FRAGMENT_SHADER: 'FRAGMENT_SHADER', + COMPILE_STATUS: 'COMPILE_STATUS', + LINK_STATUS: 'LINK_STATUS', + ACTIVE_UNIFORMS: 'ACTIVE_UNIFORMS', + TEXTURE0: 0, + TEXTURE_2D: 'TEXTURE_2D', + ARRAY_BUFFER: 'ARRAY_BUFFER', + STATIC_DRAW: 'STATIC_DRAW', + CLAMP_TO_EDGE: 'CLAMP_TO_EDGE', + TEXTURE_WRAP_S: 'TEXTURE_WRAP_S', + TEXTURE_WRAP_T: 'TEXTURE_WRAP_T', + TEXTURE_MIN_FILTER: 'TEXTURE_MIN_FILTER', + TEXTURE_MAG_FILTER: 'TEXTURE_MAG_FILTER', + LINEAR: 'LINEAR', + RGBA: 'RGBA', + UNSIGNED_BYTE: 'UNSIGNED_BYTE', + + createBuffer: vi.fn().mockReturnValue({}), + bindBuffer: vi.fn(), + bufferData: vi.fn(), + + createTexture: vi.fn().mockImplementation(() => new WebGLTexture()), + bindTexture: vi.fn(), + texParameteri: vi.fn(), + texImage2D: vi.fn(), + + createShader: vi.fn().mockReturnValue({}), + shaderSource: vi.fn(), + compileShader: vi.fn(), + getShaderParameter: vi.fn().mockReturnValue(true), + getShaderInfoLog: vi.fn().mockReturnValue(''), + + createProgram: vi.fn().mockReturnValue({}), + attachShader: vi.fn(), + linkProgram: vi.fn(), + getProgramParameter: vi + .fn() + .mockImplementation((_program: unknown, pname: unknown) => + pname === 'ACTIVE_UNIFORMS' ? 2 : true, + ), + getProgramInfoLog: vi.fn().mockReturnValue(''), + + getActiveUniform: vi.fn().mockImplementation( + (_program, index: number) => + [ + { name: 'u_texture', type: 0, size: 1 }, + { name: 'u_distanceRange', type: 0, size: 1 }, + ][index] ?? null, + ), + getUniformLocation: vi + .fn() + .mockImplementation((_program, name: string) => { + if (name === 'u_distanceRange') { + return distanceRangeLocation; + } + + return {} as WebGLUniformLocation; + }), + useProgram: vi.fn(), + uniform1i: vi.fn(), + uniform1f: vi.fn(), + activeTexture: vi.fn(), + } as unknown as WebGL2RenderingContext; + + vi.spyOn(canvas, 'getContext').mockReturnValue(mockGl); + + const shaderCache = new ShaderCache([]) + .addShader(new ForgeShaderSource(spriteVertexShader)) + .addShader(new ForgeShaderSource(msdfFragmentShader)); + + renderContext = new RenderContext(shaderCache, new ImageCache(), canvas); + }); + + it('does not throw when creating a renderable', () => { + expect(() => + createMsdfTextRenderable(fontAtlas, renderContext), + ).not.toThrow(); + }); + + it('sets u_distanceRange from the font atlas', () => { + const renderable = createMsdfTextRenderable(fontAtlas, renderContext); + + renderable.material.bind(mockGl); + + const distanceRangeCalls = (mockGl.uniform1f as Mock).mock.calls.filter( + ([location]) => location === distanceRangeLocation, + ); + + expect(distanceRangeCalls).toHaveLength(1); + expect(distanceRangeCalls[0][1]).toBe(4); + }); + + it('assigns the given layer as the renderable category', () => { + const renderable = createMsdfTextRenderable(fontAtlas, renderContext, 3); + + expect(renderable.category).toBe(3); + }); + + it('defaults the layer to 0', () => { + const renderable = createMsdfTextRenderable(fontAtlas, renderContext); + + expect(renderable.category).toBe(0); + }); + + it('uses the standard sprite instance data layout so glyphs batch with sprites', () => { + const renderable = createMsdfTextRenderable(fontAtlas, renderContext); + + expect(renderable.floatsPerInstance).toBe(17); + }); +}); diff --git a/src/rendering/utilities/create-msdf-text-renderable.ts b/src/rendering/utilities/create-msdf-text-renderable.ts new file mode 100644 index 00000000..c669e922 --- /dev/null +++ b/src/rendering/utilities/create-msdf-text-renderable.ts @@ -0,0 +1,62 @@ +import { FontAtlas } from '../../asset-loading/index.js'; +import { createQuadGeometry } from '../geometry/index.js'; +import { Material } from '../materials/index.js'; +import { RenderContext } from '../render-context.js'; +import { Renderable } from '../renderable.js'; +import { createTextureFromImage } from '../shaders/index.js'; +import { combineInstanceDataSegments } from './instance-data-segment.js'; +import { spriteInstanceDataSegment } from './sprite-instance-data-segment.js'; + +/** + * Creates the `Renderable` used to draw text set in a given `FontAtlas`. + * + * Reuses the sprite vertex shader (`sprite.vert`) and its instance data + * layout unchanged - a glyph quad's position/rotation/scale/size/pivot/uv/ + * tint are exactly the data a sprite instance already carries - paired with + * an MSDF fragment shader that reconstructs a signed distance field from the + * atlas texture's three channels and antialiases the glyph edge against the + * texture coordinate's screen-space derivative, so text stays crisp at any + * scale. Share one `Renderable` (call this once per `FontAtlas`, not per + * `TextEcsComponent`) so every entity drawing with the same font batches + * into a single instanced draw call. + * @param fontAtlas - The font atlas to draw text from. + * @param renderContext - The render context to build GPU resources with. + * @param layer - The rendering category, matched against each camera's + * culling mask. Defaults to `0`. + * @param pixelated - Samples the atlas texture with nearest-neighbor + * filtering instead of linear. An MSDF atlas is reconstructed by the + * fragment shader's own antialiasing rather than by texture filtering, so + * this should almost always stay `false` (the default). + * @returns The created renderable. + */ +export function createMsdfTextRenderable( + fontAtlas: FontAtlas, + renderContext: RenderContext, + layer: number = 0, + pixelated: boolean = false, +): Renderable { + const { shaderCache, gl } = renderContext; + + const vertexShader = shaderCache.getShader('sprite.vert'); + const fragmentShader = shaderCache.getShader('msdf.frag'); + + const material = new Material(vertexShader, fragmentShader, gl); + + material.setUniform( + 'u_texture', + createTextureFromImage(gl, fontAtlas.image, pixelated), + ); + material.setUniform('u_distanceRange', fontAtlas.distanceRange); + + const { floatsPerInstance, bindInstanceData, setupInstanceAttributes } = + combineInstanceDataSegments(spriteInstanceDataSegment); + + return new Renderable( + createQuadGeometry(gl), + material, + floatsPerInstance, + layer, + bindInstanceData, + setupInstanceAttributes, + ); +} diff --git a/src/rendering/utilities/create-shader-cache.ts b/src/rendering/utilities/create-shader-cache.ts index 041bc4d4..07b1e8b3 100644 --- a/src/rendering/utilities/create-shader-cache.ts +++ b/src/rendering/utilities/create-shader-cache.ts @@ -5,6 +5,7 @@ import { cubicShaderInclude, ForgeShaderSource, gaussianBlurFragmentShader, + msdfFragmentShader, passthroughFragmentShader, passthroughVertexShader, perlinNoiseFragmentShader, @@ -69,6 +70,7 @@ export function createShaderCache(): ShaderCache { .addShader(new ForgeShaderSource(perlinNoiseFragmentShader)) .addShader(new ForgeShaderSource(spriteFragmentShader)) .addShader(new ForgeShaderSource(spriteVertexShader)) + .addShader(new ForgeShaderSource(msdfFragmentShader)) .addShader(new ForgeShaderSource(passthroughFragmentShader)) .addShader(new ForgeShaderSource(passthroughVertexShader)) .addShader(new ForgeShaderSource(gaussianBlurFragmentShader)) diff --git a/src/rendering/utilities/index.ts b/src/rendering/utilities/index.ts index 9e687219..5c39100f 100644 --- a/src/rendering/utilities/index.ts +++ b/src/rendering/utilities/index.ts @@ -4,6 +4,7 @@ export * from './compute-nine-slice-regions.js'; export * from './create-camera.js'; export * from './create-canvas.js'; export * from './create-image-sprite.js'; +export * from './create-msdf-text-renderable.js'; export * from './create-shader-cache.js'; export * from './create-sprite.js'; export * from './instance-data-segment.js'; diff --git a/src/text/components/index.ts b/src/text/components/index.ts new file mode 100644 index 00000000..9670950b --- /dev/null +++ b/src/text/components/index.ts @@ -0,0 +1,2 @@ +export * from './text-component.js'; +export * from './text-mesh-component.js'; diff --git a/src/text/components/text-component.test.ts b/src/text/components/text-component.test.ts new file mode 100644 index 00000000..44716025 --- /dev/null +++ b/src/text/components/text-component.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest'; +import { addTextComponent, textId } from './text-component.js'; +import { EcsWorld } from '../../ecs/index.js'; +import { Color } from '../../rendering/color.js'; +import type { Renderable } from '../../rendering/renderable.js'; +import type { FontAtlas } from '../../asset-loading/index.js'; + +const renderable = {} as Renderable; +const font = {} as FontAtlas; + +describe('addTextComponent', () => { + it('attaches a component with default values for unspecified options', () => { + const world = new EcsWorld(); + const entity = world.createEntity(); + + addTextComponent(world, entity, { + text: 'Hello', + font, + renderable, + fontSize: 16, + }); + + expect(world.getComponent(entity, textId)).toEqual({ + text: 'Hello', + font, + renderable, + fontSize: 16, + color: Color.white, + alignment: 'left', + lineSpacing: 1, + pivot: { x: 0.5, y: 0.5 }, + enabled: true, + layer: 0, + }); + }); + + it('overrides only the provided options', () => { + const world = new EcsWorld(); + const entity = world.createEntity(); + + addTextComponent(world, entity, { + text: 'Hello', + font, + renderable, + fontSize: 16, + alignment: 'center', + wrapWidth: 100, + enabled: false, + }); + + expect(world.getComponent(entity, textId)).toMatchObject({ + alignment: 'center', + wrapWidth: 100, + enabled: false, + lineSpacing: 1, + }); + }); + + it('returns the attached component', () => { + const world = new EcsWorld(); + const entity = world.createEntity(); + + const component = addTextComponent(world, entity, { + text: 'Hello', + font, + renderable, + fontSize: 16, + }); + + expect(world.getComponent(entity, textId)).toBe(component); + }); + + it('gives each entity its own pivot vector instance', () => { + const world = new EcsWorld(); + const first = world.createEntity(); + const second = world.createEntity(); + const options = { text: 'Hello', font, renderable, fontSize: 16 }; + + addTextComponent(world, first, options); + addTextComponent(world, second, options); + + expect(world.getComponent(first, textId)?.pivot).not.toBe( + world.getComponent(second, textId)?.pivot, + ); + }); +}); diff --git a/src/text/components/text-component.ts b/src/text/components/text-component.ts new file mode 100644 index 00000000..0fefb8bd --- /dev/null +++ b/src/text/components/text-component.ts @@ -0,0 +1,147 @@ +import { FontAtlas } from '../../asset-loading/index.js'; +import { createComponentId } from '../../ecs/ecs-component.js'; +import { EcsWorld } from '../../ecs/ecs-world.js'; +import { Vector2 } from '../../math/index.js'; +// Imported from their own leaf files (not `../../rendering/index.js`) so +// this file never has a runtime dependency on `render-system.ts`, which +// itself imports `textId`/`TextMeshEcsComponent` from this module - a +// `Renderable`/`Color` value import routed through the rendering barrel +// would create a genuine circular module load between `/rendering` and +// `/text`. `Renderable` is only ever used here as a field type, so it's +// imported as a type-only import to guarantee it's erased entirely. +import { Color } from '../../rendering/color.js'; +import type { Renderable } from '../../rendering/renderable.js'; + +/** + * How a text block's lines are horizontally positioned relative to the + * block's own width (the widest line, or `wrapWidth` when set). + */ +export type TextAlignment = 'left' | 'center' | 'right'; + +/** + * Fields of {@link TextEcsComponent} with no sensible default; callers must + * always provide these. + */ +export interface TextRequiredOptions { + /** + * The string to render. Explicit `\n` characters start a new line; + * `wrapWidth` (if set) additionally breaks lines between words. + */ + text: string; + + /** + * The font atlas metrics to shape `text` against. + */ + font: FontAtlas; + + /** + * The renderable used to draw this text's glyph quads, built once per + * `FontAtlas` via `createMsdfTextRenderable` (`/rendering`) and shared + * across every entity using that font, so they batch into a single + * instanced draw call. + */ + renderable: Renderable; + + /** + * The font size, in world units, that `font`'s em-normalized metrics are + * scaled by. + */ + fontSize: number; +} + +/** + * Fields of {@link TextEcsComponent} with a sensible default; callers may + * omit these. + */ +export interface TextDefaultedOptions { + /** + * The text's color. Multiplied against the MSDF atlas's reconstructed + * coverage, so unlike `SpriteEcsComponent.tintColor` this is the text's + * actual, only color rather than a tint over a sampled texture color. + * Defaults to `Color.white`. + */ + color: Color; + + /** + * How each line is horizontally positioned relative to the text block's + * own width. Defaults to `'left'`. + */ + alignment: TextAlignment; + + /** + * The width, in world units, that lines wrap to fit within, breaking + * between words (not mid-word). Omit for a single unwrapped line per `\n` + * in `text`, however long. + */ + wrapWidth?: number; + + /** + * A multiplier over `font.lineHeight` for the vertical distance between + * successive lines' baselines. Defaults to `1`. + */ + lineSpacing: number; + + /** + * The text block's origin, normalized to the block's own size: `(0, 0)` + * is the top-left corner, `(0.5, 0.5)` (the default) is the center, and + * `(1, 1)` is the bottom-right corner - matching + * `SpriteEcsComponent.pivot`'s convention exactly. + */ + pivot: Vector2; + + /** + * Whether this text is drawn. When `false`, the render system skips this + * entity entirely, before any culling-mask check. + */ + enabled: boolean; + + /** + * The draw-order layer for this text, relative to other sprites/text + * drawn by the same camera - see `SpriteEcsComponent.layer`. + */ + layer: number; +} + +export interface TextEcsComponent + extends TextRequiredOptions, TextDefaultedOptions {} + +export const textId = createComponentId('text'); + +/** + * Attaches a {@link TextEcsComponent} to `entity`. `createTextShapingEcsSystem` + * shapes `text` into glyph quads (a `TextMeshEcsComponent`, added + * automatically) whenever `text`, `font`, `fontSize`, `wrapWidth`, + * `lineSpacing`, `alignment`, or `pivot` change. + * @param world - The ECS world `entity` belongs to. + * @param entity - The entity to attach the component to. + * @param options - Options for configuring the text. `text`, `font`, + * `renderable`, and `fontSize` have no sensible default and must always be + * provided. + * @returns The attached component, for further tuning or runtime changes. + */ +export function addTextComponent( + world: EcsWorld, + entity: number, + options: TextRequiredOptions & Partial, +): TextEcsComponent { + // Built inside the function body (rather than as a shared module-level + // default), matching `addSpriteComponent`: `Color.white` can't be safely + // read at module-init time in a codebase with circular import cycles + // through barrel files, and `pivot` is a `Vector2` callers/systems may + // mutate in place, so each entity needs its own instance. + const defaultTextOptions: TextDefaultedOptions = { + color: Color.white, + alignment: 'left', + lineSpacing: 1, + pivot: { x: 0.5, y: 0.5 }, + enabled: true, + layer: 0, + }; + + const component: TextEcsComponent = { + ...defaultTextOptions, + ...options, + }; + + return world.addComponent(entity, textId, component); +} diff --git a/src/text/components/text-mesh-component.test.ts b/src/text/components/text-mesh-component.test.ts new file mode 100644 index 00000000..1b67ba2e --- /dev/null +++ b/src/text/components/text-mesh-component.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest'; +import { addTextMeshComponent, textMeshId } from './text-mesh-component.js'; +import { EcsWorld } from '../../ecs/index.js'; +import type { FontAtlas } from '../../asset-loading/index.js'; + +const font = {} as FontAtlas; + +describe('addTextMeshComponent', () => { + it('attaches the given shaped text data as-is', () => { + const world = new EcsWorld(); + const entity = world.createEntity(); + + const options = { + glyphs: [ + { + offset: { x: 0, y: 0 }, + size: { x: 1, y: 1 }, + uvOffset: { x: 0, y: 0 }, + uvScale: { x: 1, y: 1 }, + }, + ], + bounds: { x: 1, y: 1 }, + sourceText: 'A', + sourceFont: font, + sourceFontSize: 16, + sourceWrapWidth: undefined, + sourceLineSpacing: 1, + sourceAlignment: 'left' as const, + sourcePivot: { x: 0.5, y: 0.5 }, + }; + + addTextMeshComponent(world, entity, options); + + expect(world.getComponent(entity, textMeshId)).toBe(options); + }); + + it('returns the attached component', () => { + const world = new EcsWorld(); + const entity = world.createEntity(); + + const options = { + glyphs: [], + bounds: { x: 0, y: 0 }, + sourceText: '', + sourceFont: font, + sourceFontSize: 16, + sourceWrapWidth: undefined, + sourceLineSpacing: 1, + sourceAlignment: 'left' as const, + sourcePivot: { x: 0.5, y: 0.5 }, + }; + + const component = addTextMeshComponent(world, entity, options); + + expect(world.getComponent(entity, textMeshId)).toBe(component); + }); +}); diff --git a/src/text/components/text-mesh-component.ts b/src/text/components/text-mesh-component.ts new file mode 100644 index 00000000..7bd93373 --- /dev/null +++ b/src/text/components/text-mesh-component.ts @@ -0,0 +1,95 @@ +import { FontAtlas } from '../../asset-loading/index.js'; +import { createComponentId } from '../../ecs/ecs-component.js'; +import { EcsWorld } from '../../ecs/ecs-world.js'; +import { Vector2 } from '../../math/index.js'; +import { TextAlignment } from './text-component.js'; + +/** + * A single glyph's quad, in a text block's own local text-space, that the + * render system draws for each character `createTextShapingEcsSystem` + * placed. Mirrors `NineSliceRegion` (`/rendering`) - the same "offset + + * size + uv rect" shape the render system already knows how to expand into + * a `SpriteEcsComponent`-shaped render command. + */ +export interface GlyphQuad { + /** + * This glyph's center, as an offset from the text block's pivot-adjusted + * anchor point, in unscaled local units (before rotation and + * `ScaleEcsComponent` are applied). + */ + offset: Vector2; + + /** This glyph's rendered width/height, in the same unscaled units. */ + size: Vector2; + + /** The top-left corner of this glyph's region in the font atlas, 0 to 1. */ + uvOffset: Vector2; + + /** The width/height of this glyph's region in the font atlas, 0 to 1. */ + uvScale: Vector2; +} + +/** + * The shaped glyph quads produced by `createTextShapingEcsSystem` from a + * `TextEcsComponent`. Added and kept up to date automatically - shaping a + * paragraph is real cost, so this only happens when `TextEcsComponent`'s + * shape-affecting fields actually change (dirty-tracked via the `source*` + * fields below), not every frame. + */ +export interface TextMeshEcsComponent { + /** + * This text block's shaped glyph quads, one per visible (non-whitespace) + * character. + */ + glyphs: GlyphQuad[]; + + /** + * This text block's overall size (the widest line's width, and the full + * stack of lines' height), in world units. Reflects `pivot`'s effect on + * `glyphs`' offsets, not just the raw shaped size. + */ + bounds: Vector2; + + /** + * The `TextEcsComponent.text` this mesh was last shaped from, used by + * `createTextShapingEcsSystem` to detect when re-shaping is needed. + */ + sourceText: string; + + /** The `TextEcsComponent.font` this mesh was last shaped from. */ + sourceFont: FontAtlas; + + /** The `TextEcsComponent.fontSize` this mesh was last shaped from. */ + sourceFontSize: number; + + /** The `TextEcsComponent.wrapWidth` this mesh was last shaped from. */ + sourceWrapWidth: number | undefined; + + /** The `TextEcsComponent.lineSpacing` this mesh was last shaped from. */ + sourceLineSpacing: number; + + /** The `TextEcsComponent.alignment` this mesh was last shaped from. */ + sourceAlignment: TextAlignment; + + /** The `TextEcsComponent.pivot` this mesh was last shaped from. */ + sourcePivot: Vector2; +} + +export const textMeshId = createComponentId('textMesh'); + +/** + * Attaches a {@link TextMeshEcsComponent} to `entity`. Normally managed + * automatically by `createTextShapingEcsSystem` - call this directly only + * when hand-authoring shaped glyph data outside of that system. + * @param world - The ECS world `entity` belongs to. + * @param entity - The entity to attach the component to. + * @param options - The shaped text mesh data. + * @returns The attached component, for further tuning or runtime changes. + */ +export function addTextMeshComponent( + world: EcsWorld, + entity: number, + options: TextMeshEcsComponent, +): TextMeshEcsComponent { + return world.addComponent(entity, textMeshId, options); +} diff --git a/src/text/index.ts b/src/text/index.ts new file mode 100644 index 00000000..9a9fe1fc --- /dev/null +++ b/src/text/index.ts @@ -0,0 +1,3 @@ +export * from './components/index.js'; +export * from './systems/index.js'; +export * from './utilities/index.js'; diff --git a/src/text/systems/index.ts b/src/text/systems/index.ts new file mode 100644 index 00000000..11b8d26f --- /dev/null +++ b/src/text/systems/index.ts @@ -0,0 +1 @@ +export * from './text-shaping-system.js'; diff --git a/src/text/systems/text-shaping-system.test.ts b/src/text/systems/text-shaping-system.test.ts new file mode 100644 index 00000000..7fec33a3 --- /dev/null +++ b/src/text/systems/text-shaping-system.test.ts @@ -0,0 +1,261 @@ +import { describe, expect, it } from 'vitest'; +import { createTextShapingEcsSystem } from './text-shaping-system.js'; +import { EcsWorld } from '../../ecs/index.js'; +import { addPositionComponent } from '../../common/index.js'; +import type { FontAtlas } from '../../asset-loading/index.js'; +import type { Renderable } from '../../rendering/renderable.js'; +import { addTextComponent, textId } from '../components/text-component.js'; +import { textMeshId } from '../components/text-mesh-component.js'; + +const renderable = {} as Renderable; + +function createFont(): FontAtlas { + return { + image: {} as HTMLImageElement, + atlasWidth: 512, + atlasHeight: 512, + distanceRange: 4, + emSize: 1, + lineHeight: 1.2, + ascender: 0.9, + descender: -0.2, + glyphs: new Map([ + [ + 65, + { + advance: 0.6, + planeBounds: { left: 0, bottom: 0, right: 0.6, top: 0.7 }, + uvOffset: { x: 0, y: 0 }, + uvScale: { x: 0.1, y: 0.1 }, + }, + ], + ]), + kerning: new Map(), + }; +} + +describe('createTextShapingEcsSystem', () => { + it('shapes an entity with a TextEcsComponent but no TextMeshEcsComponent yet', () => { + const world = new EcsWorld(); + const system = createTextShapingEcsSystem(); + world.addSystem(system); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + addTextComponent(world, entity, { + text: 'A', + font: createFont(), + renderable, + fontSize: 10, + }); + + world.update(); + + const mesh = world.getComponent(entity, textMeshId); + + expect(mesh?.glyphs).toHaveLength(1); + }); + + it('skips disabled text entities', () => { + const world = new EcsWorld(); + const system = createTextShapingEcsSystem(); + world.addSystem(system); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + addTextComponent(world, entity, { + text: 'A', + font: createFont(), + renderable, + fontSize: 10, + enabled: false, + }); + + world.update(); + + expect(world.getComponent(entity, textMeshId)).toBeNull(); + }); + + it('does not re-shape when nothing shape-affecting changed', () => { + const world = new EcsWorld(); + const system = createTextShapingEcsSystem(); + world.addSystem(system); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + addTextComponent(world, entity, { + text: 'A', + font: createFont(), + renderable, + fontSize: 10, + }); + + world.update(); + + const meshAfterFirstUpdate = world.getComponent(entity, textMeshId); + const glyphsAfterFirstUpdate = meshAfterFirstUpdate?.glyphs; + + world.update(); + + const meshAfterSecondUpdate = world.getComponent(entity, textMeshId); + + // Same component instance (mutated in place, not replaced) and the + // same `glyphs` array reference - proof `shapeText` wasn't called + // again, since a reshape always allocates a fresh glyphs array. + expect(meshAfterSecondUpdate).toBe(meshAfterFirstUpdate); + expect(meshAfterSecondUpdate?.glyphs).toBe(glyphsAfterFirstUpdate); + }); + + it('re-shapes when the text changes', () => { + const world = new EcsWorld(); + const system = createTextShapingEcsSystem(); + world.addSystem(system); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + const text = addTextComponent(world, entity, { + text: 'A', + font: createFont(), + renderable, + fontSize: 10, + }); + + world.update(); + + const glyphsAfterFirstUpdate = world.getComponent( + entity, + textMeshId, + )?.glyphs; + + text.text = 'AA'; + world.update(); + + const glyphsAfterSecondUpdate = world.getComponent( + entity, + textMeshId, + )?.glyphs; + + expect(glyphsAfterSecondUpdate).not.toBe(glyphsAfterFirstUpdate); + expect(glyphsAfterSecondUpdate).toHaveLength(2); + }); + + it('re-shapes when fontSize changes', () => { + const world = new EcsWorld(); + const system = createTextShapingEcsSystem(); + world.addSystem(system); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + const text = addTextComponent(world, entity, { + text: 'A', + font: createFont(), + renderable, + fontSize: 10, + }); + + world.update(); + + const boundsAfterFirstUpdate = world.getComponent(entity, textMeshId) + ?.bounds.x; + + text.fontSize = 20; + world.update(); + + const boundsAfterSecondUpdate = world.getComponent(entity, textMeshId) + ?.bounds.x; + + expect(boundsAfterSecondUpdate).toBeGreaterThan(boundsAfterFirstUpdate!); + }); + + it('re-shapes when wrapWidth, lineSpacing, alignment, or pivot change', () => { + const world = new EcsWorld(); + const system = createTextShapingEcsSystem(); + world.addSystem(system); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + const text = addTextComponent(world, entity, { + text: 'A A', + font: createFont(), + renderable, + fontSize: 10, + }); + + world.update(); + + const firstGlyphs = world.getComponent(entity, textMeshId)?.glyphs; + + text.wrapWidth = 1; + world.update(); + + const secondGlyphs = world.getComponent(entity, textMeshId)?.glyphs; + + expect(secondGlyphs).not.toBe(firstGlyphs); + + text.lineSpacing = 2; + world.update(); + + const thirdGlyphs = world.getComponent(entity, textMeshId)?.glyphs; + + expect(thirdGlyphs).not.toBe(secondGlyphs); + + text.alignment = 'center'; + world.update(); + + const fourthGlyphs = world.getComponent(entity, textMeshId)?.glyphs; + + expect(fourthGlyphs).not.toBe(thirdGlyphs); + + text.pivot = { x: 0, y: 0 }; + world.update(); + + const fifthGlyphs = world.getComponent(entity, textMeshId)?.glyphs; + + expect(fifthGlyphs).not.toBe(fourthGlyphs); + }); + + it('does not re-shape when only non-shape-affecting fields change', () => { + const world = new EcsWorld(); + const system = createTextShapingEcsSystem(); + world.addSystem(system); + + const entity = world.createEntity(); + + addPositionComponent(world, entity); + const text = addTextComponent(world, entity, { + text: 'A', + font: createFont(), + renderable, + fontSize: 10, + }); + + world.update(); + + const glyphsAfterFirstUpdate = world.getComponent( + entity, + textMeshId, + )?.glyphs; + + text.layer = 5; + world.update(); + + const glyphsAfterSecondUpdate = world.getComponent( + entity, + textMeshId, + )?.glyphs; + + expect(glyphsAfterSecondUpdate).toBe(glyphsAfterFirstUpdate); + }); + + it('queries only entities with a TextEcsComponent', () => { + const system = createTextShapingEcsSystem(); + + expect(system.query).toEqual([textId]); + }); +}); diff --git a/src/text/systems/text-shaping-system.ts b/src/text/systems/text-shaping-system.ts new file mode 100644 index 00000000..b6b80c9d --- /dev/null +++ b/src/text/systems/text-shaping-system.ts @@ -0,0 +1,112 @@ +import { EcsSystem } from '../../ecs/ecs-system.js'; +import { EcsWorld } from '../../ecs/ecs-world.js'; +import { + addTextMeshComponent, + TextEcsComponent, + textId, + TextMeshEcsComponent, + textMeshId, +} from '../components/index.js'; +import { shapeText } from '../utilities/shape-text.js'; + +/** + * Whether `mesh` was shaped from the shape-affecting fields `text` currently + * has. `TextEcsComponent.color`/`enabled`/`layer`/`renderable` don't affect + * shaping and are deliberately excluded, so changing them never triggers a + * re-shape. + */ +function isMeshCurrent( + mesh: TextMeshEcsComponent, + text: TextEcsComponent, +): boolean { + return ( + mesh.sourceText === text.text && + mesh.sourceFont === text.font && + mesh.sourceFontSize === text.fontSize && + mesh.sourceWrapWidth === text.wrapWidth && + mesh.sourceLineSpacing === text.lineSpacing && + mesh.sourceAlignment === text.alignment && + mesh.sourcePivot.x === text.pivot.x && + mesh.sourcePivot.y === text.pivot.y + ); +} + +function reshape( + world: EcsWorld, + entity: number, + text: TextEcsComponent, + existingMesh: TextMeshEcsComponent | null, +): void { + const shaped = shapeText(text.text, text.font, text.fontSize, { + alignment: text.alignment, + wrapWidth: text.wrapWidth, + lineSpacing: text.lineSpacing, + pivot: text.pivot, + }); + + const mesh: TextMeshEcsComponent = { + glyphs: shaped.glyphs, + bounds: shaped.bounds, + sourceText: text.text, + sourceFont: text.font, + sourceFontSize: text.fontSize, + sourceWrapWidth: text.wrapWidth, + sourceLineSpacing: text.lineSpacing, + sourceAlignment: text.alignment, + sourcePivot: { x: text.pivot.x, y: text.pivot.y }, + }; + + if (existingMesh) { + Object.assign(existingMesh, mesh); + + return; + } + + addTextMeshComponent(world, entity, mesh); +} + +/** + * Creates a system that shapes every entity's `TextEcsComponent` into a + * `TextMeshEcsComponent` of glyph quads for the render system to draw. + * + * Re-shaping a paragraph is real cost (word-wrapping, kerning lookups, one + * quad per character), so this only happens when `text`, `font`, + * `fontSize`, `wrapWidth`, `lineSpacing`, `alignment`, or `pivot` actually + * changed since the last shape - every other system in a typical pipeline + * recomputes unconditionally per frame, but text shaping is the deliberate + * exception. + * + * Register this with a `SystemRegistrationOrder` after any system that can + * change an entity's `wrapWidth` (e.g. a UI layout pass resolving a + * container's rect) and before `createRenderEcsSystem`, so the render pass + * always consumes this frame's glyph quads rather than last frame's. + * Standalone text with no layout dependency (damage numbers, floating + * names, debug overlays) has no ordering constraint beyond "before render". + * @returns The ECS system. + */ +export const createTextShapingEcsSystem = (): EcsSystem< + [TextEcsComponent] +> => ({ + query: [textId], + update: (world, { entities, components: [texts] }) => { + for (let i = 0; i < entities.length; i++) { + const text = texts[i]; + + if (!text.enabled) { + continue; + } + + const entity = entities[i]; + const existingMesh = world.getComponent( + entity, + textMeshId, + ); + + if (existingMesh && isMeshCurrent(existingMesh, text)) { + continue; + } + + reshape(world, entity, text, existingMesh); + } + }, +}); diff --git a/src/text/utilities/index.ts b/src/text/utilities/index.ts new file mode 100644 index 00000000..5807b5a7 --- /dev/null +++ b/src/text/utilities/index.ts @@ -0,0 +1 @@ +export * from './shape-text.js'; diff --git a/src/text/utilities/shape-text.test.ts b/src/text/utilities/shape-text.test.ts new file mode 100644 index 00000000..a3318654 --- /dev/null +++ b/src/text/utilities/shape-text.test.ts @@ -0,0 +1,250 @@ +import { describe, expect, it } from 'vitest'; +import { shapeText } from './shape-text.js'; +import type { FontAtlas } from '../../asset-loading/index.js'; + +/** + * A tiny synthetic font: "A" and "V" are 1 em wide/tall monospace-ish + * glyphs, "V" following "A" kerns -0.2em tighter, and space just advances. + */ +function createFont(overrides: Partial = {}): FontAtlas { + return { + image: {} as HTMLImageElement, + atlasWidth: 100, + atlasHeight: 100, + distanceRange: 4, + emSize: 1, + lineHeight: 1, + ascender: 0.8, + descender: -0.2, + glyphs: new Map([ + [ + 65, // 'A' + { + advance: 1, + planeBounds: { left: 0, bottom: 0, right: 1, top: 1 }, + uvOffset: { x: 0, y: 0 }, + uvScale: { x: 0.1, y: 0.1 }, + }, + ], + [ + 86, // 'V' + { + advance: 1, + planeBounds: { left: 0, bottom: 0, right: 1, top: 1 }, + uvOffset: { x: 0.1, y: 0 }, + uvScale: { x: 0.1, y: 0.1 }, + }, + ], + [ + 32, // ' ' + { advance: 0.5 }, + ], + ]), + kerning: new Map([['65:86', -0.2]]), + ...overrides, + }; +} + +describe('shapeText', () => { + it('produces one glyph per visible character', () => { + const shaped = shapeText('AA', createFont(), 10); + + expect(shaped.glyphs).toHaveLength(2); + }); + + it('scales glyph size by fontSize / emSize', () => { + const font = createFont({ emSize: 2 }); + const shaped = shapeText('A', font, 10); + + // planeBounds spans 0..1 em; scale = fontSize / emSize = 5. + expect(shaped.glyphs[0].size).toEqual({ x: 5, y: 5 }); + }); + + it('produces no glyph quad for whitespace, but still advances the cursor', () => { + const shaped = shapeText('A A', createFont(), 10); + + expect(shaped.glyphs).toHaveLength(2); + // Second 'A' advance: 1 (first A) + 0.5 (space) = 1.5em * 10 = 15. + expect(shaped.glyphs[1].offset.x).toBeGreaterThan( + shaped.glyphs[0].offset.x, + ); + }); + + it('skips characters missing from the font atlas without throwing', () => { + expect(() => shapeText('A?A', createFont(), 10)).not.toThrow(); + + const shaped = shapeText('A?A', createFont(), 10); + + expect(shaped.glyphs).toHaveLength(2); + }); + + it('applies kerning between consecutive glyphs', () => { + const withKerning = shapeText('AV', createFont(), 10); + const withoutKerningFont = createFont({ kerning: new Map() }); + const withoutKerning = shapeText('AV', withoutKerningFont, 10); + + // Kerning is -0.2em * fontSize(10) = -2 tighter, so the second glyph's + // offset should be 2 units further left with kerning applied. + expect(withKerning.glyphs[1].offset.x).toBeLessThan( + withoutKerning.glyphs[1].offset.x, + ); + }); + + it('does not apply kerning across a line break', () => { + const font = createFont(); + const shapedTwoLines = shapeText('A\nV', font, 10); + + expect(shapedTwoLines.glyphs).toHaveLength(2); + // The 'V' on line 2 starts a fresh cursor (x relative to its own line), + // so it isn't pulled left by the A-V kerning pair the way it would be + // on a single line. + const shapedOneLine = shapeText('AV', font, 10); + const kernedOffset = shapedOneLine.glyphs[1].offset.x; + + expect(shapedTwoLines.glyphs[1].offset.x).not.toBe(kernedOffset); + }); + + describe('bounds', () => { + it('computes width as the widest line when no wrapWidth is given', () => { + const shaped = shapeText('AA\nA', createFont(), 10); + + // 'AA' is 2em wide * 10 = 20; 'A' alone is 10. + expect(shaped.bounds.x).toBe(20); + }); + + it('computes height as lines * lineHeight * lineSpacing * fontSize', () => { + const shaped = shapeText('A\nA\nA', createFont(), 10, { + lineSpacing: 2, + }); + + // font.lineHeight = 1, so height = 3 lines * 1 * 2 * 10 = 60. + expect(shaped.bounds.y).toBe(60); + }); + + it('uses wrapWidth as the block width when given', () => { + const shaped = shapeText('A', createFont(), 10, { wrapWidth: 50 }); + + expect(shaped.bounds.x).toBe(50); + }); + }); + + describe('word wrapping', () => { + it('does not wrap when wrapWidth is omitted, however long the line', () => { + const shaped = shapeText('A A A A A A A A A A', createFont(), 10); + + // 19 characters (10 'A's + 9 spaces) all placed as visible or + // whitespace on a single unwrapped line: 10 visible glyphs. + expect(shaped.glyphs).toHaveLength(10); + expect(shaped.bounds.y).toBe(10); // one line only + }); + + it('wraps between words to fit wrapWidth', () => { + // Each "A A" pair is 1.5em wide; with fontSize 10 that's 15 units. + // A wrapWidth of 20 allows one word (10 units) but not two per line. + const shaped = shapeText('A A A', createFont(), 10, { wrapWidth: 20 }); + + // 3 lines of one 'A' each -> height = 3 * 1 * 1 * 10 = 30. + expect(shaped.bounds.y).toBe(30); + }); + + it('places a single word wider than wrapWidth on its own line, unbroken', () => { + const shaped = shapeText('AA', createFont(), 10, { wrapWidth: 5 }); + + expect(shaped.glyphs).toHaveLength(2); + expect(shaped.bounds.y).toBe(10); // still a single line + }); + + it('preserves explicit line breaks alongside wrapping', () => { + const shaped = shapeText('A A\nA', createFont(), 10, { wrapWidth: 30 }); + + // "A A" is A(1em) + space(0.5em) + A(1em) = 2.5em wide, 25 units at + // fontSize 10, which fits within a 30-unit wrapWidth as one line; + // "A" from the explicit break is a second line -> 2 lines total. + expect(shaped.bounds.y).toBe(20); + }); + }); + + describe('alignment', () => { + it('left-aligns by default, starting every line at x = 0 (before pivot)', () => { + const shaped = shapeText('AA\nA', createFont(), 10, { + wrapWidth: 20, + pivot: { x: 0, y: 0 }, + }); + + const firstGlyphOfEachLine = [shaped.glyphs[0], shaped.glyphs[2]]; + + for (const glyph of firstGlyphOfEachLine) { + expect(glyph.offset.x).toBeCloseTo(glyph.size.x / 2); + } + }); + + it('center-aligns shorter lines within the block width', () => { + const shaped = shapeText('AA\nA', createFont(), 10, { + alignment: 'center', + pivot: { x: 0, y: 0 }, + }); + + // Block width is 20 (the "AA" line). The lone "A" line (10 wide) + // should be centered, starting at x = 5 instead of x = 0. + const secondLineGlyph = shaped.glyphs[2]; + + expect(secondLineGlyph.offset.x).toBeCloseTo( + 5 + secondLineGlyph.size.x / 2, + ); + }); + + it('right-aligns shorter lines within the block width', () => { + const shaped = shapeText('AA\nA', createFont(), 10, { + alignment: 'right', + pivot: { x: 0, y: 0 }, + }); + + const secondLineGlyph = shaped.glyphs[2]; + + expect(secondLineGlyph.offset.x).toBeCloseTo( + 10 + secondLineGlyph.size.x / 2, + ); + }); + }); + + describe('pivot', () => { + it('defaults to the block center (0.5, 0.5)', () => { + const centered = shapeText('A', createFont(), 10); + const topLeft = shapeText('A', createFont(), 10, { + pivot: { x: 0, y: 0 }, + }); + + expect(centered.glyphs[0].offset.x).toBeLessThan( + topLeft.glyphs[0].offset.x, + ); + }); + + it('offsets every glyph by pivot * bounds', () => { + const topLeft = shapeText('AA', createFont(), 10, { + pivot: { x: 0, y: 0 }, + }); + const bottomRight = shapeText('AA', createFont(), 10, { + pivot: { x: 1, y: 1 }, + }); + + const { bounds } = topLeft; + + expect(bottomRight.glyphs[0].offset.x).toBeCloseTo( + topLeft.glyphs[0].offset.x - bounds.x, + ); + expect(bottomRight.glyphs[0].offset.y).toBeCloseTo( + topLeft.glyphs[0].offset.y + bounds.y, + ); + }); + }); + + describe('uv rects', () => { + it("passes through each glyph's uvOffset/uvScale from the font atlas unchanged", () => { + const shaped = shapeText('AV', createFont(), 10); + + expect(shaped.glyphs[0].uvOffset).toEqual({ x: 0, y: 0 }); + expect(shaped.glyphs[0].uvScale).toEqual({ x: 0.1, y: 0.1 }); + expect(shaped.glyphs[1].uvOffset).toEqual({ x: 0.1, y: 0 }); + }); + }); +}); diff --git a/src/text/utilities/shape-text.ts b/src/text/utilities/shape-text.ts new file mode 100644 index 00000000..dfbc15fb --- /dev/null +++ b/src/text/utilities/shape-text.ts @@ -0,0 +1,251 @@ +import { + FontAtlas, + FontAtlasGlyph, + getKerningKey, +} from '../../asset-loading/index.js'; +import { Vector2 } from '../../math/index.js'; +import { GlyphQuad } from '../components/text-mesh-component.js'; +import { TextAlignment } from '../components/text-component.js'; + +/** + * Options for {@link shapeText}, mirroring `TextEcsComponent`'s + * shape-affecting fields. + */ +export interface ShapeTextOptions { + /** How each line is horizontally positioned. Defaults to `'left'`. */ + alignment?: TextAlignment; + + /** + * The width, in world units, that lines wrap to fit within, breaking + * between words (not mid-word - a single word wider than `wrapWidth` is + * still placed on its own line, unbroken). Omit for a single unwrapped + * line per `\n` in `text`. + */ + wrapWidth?: number; + + /** A multiplier over `font.lineHeight`. Defaults to `1`. */ + lineSpacing?: number; + + /** The text block's origin, normalized to its own size. Defaults to `(0.5, 0.5)`. */ + pivot?: Vector2; +} + +/** The result of {@link shapeText}. */ +export interface ShapedText { + /** One quad per visible (non-whitespace, in-atlas) character. */ + glyphs: GlyphQuad[]; + + /** + * The text block's overall size, in world units: the widest line's width + * (or `wrapWidth`, if given) and the full line-height stack's height. + */ + bounds: Vector2; +} + +const defaultShapeTextOptions = { + alignment: 'left' as TextAlignment, + lineSpacing: 1, +}; + +/** + * Walks `line`'s characters left to right, applying each glyph's kerning + * (against the previous placed glyph) and advance, in em-to-world-unit + * `scale`. Characters with no matching glyph in `font` are skipped (no + * advance) - a font missing a character has no way to represent it. + * @param line - The line to lay out (must not contain `\n`). + * @param font - The font atlas to look up glyphs and kerning in. + * @param scale - The em-to-world-unit scale factor (`fontSize / font.emSize`). + * @param onGlyph - Invoked for each placed glyph, with its code point, its + * atlas metrics, and its cursor position (the pen position immediately + * before this glyph, in world units, before its own advance is applied). + * @returns The line's total advance width, in world units. + */ +function layoutLine( + line: string, + font: FontAtlas, + scale: number, + onGlyph?: (codePoint: number, glyph: FontAtlasGlyph, cursorX: number) => void, +): number { + let cursorX = 0; + let previousCodePoint: number | null = null; + + for (const character of line) { + // `codePointAt` (not charCodeAt/index access) so a surrogate-pair + // character (e.g. most emoji) resolves to one code point, matching how + // `for...of` already iterated by code point rather than UTF-16 unit. + const codePoint = character.codePointAt(0)!; + const glyph = font.glyphs.get(codePoint); + + if (!glyph) { + previousCodePoint = null; + + continue; + } + + if (previousCodePoint !== null) { + const kerning = + font.kerning.get(getKerningKey(previousCodePoint, codePoint)) ?? 0; + + cursorX += kerning * scale; + } + + onGlyph?.(codePoint, glyph, cursorX); + + cursorX += glyph.advance * scale; + previousCodePoint = codePoint; + } + + return cursorX; +} + +/** + * Greedily word-wraps `paragraph` (a single line's worth of source text, + * already split on `\n`) to fit within `wrapWidth`, breaking between words. + * A word wider than `wrapWidth` on its own is still placed on its own line, + * unbroken. Collapses runs of spaces to a single space between words - + * word-wrapping re-flows the text anyway, so original whitespace runs + * aren't preserved (unlike the no-`wrapWidth` case, which uses `paragraph` + * verbatim). + * @param paragraph - The paragraph to wrap. + * @param font - The font atlas to measure candidate lines against. + * @param scale - The em-to-world-unit scale factor. + * @param wrapWidth - The width, in world units, to wrap within. + * @returns The wrapped lines. + */ +function wrapParagraph( + paragraph: string, + font: FontAtlas, + scale: number, + wrapWidth: number, +): string[] { + const words = paragraph.split(' '); + const lines: string[] = []; + let currentLine = ''; + + for (const word of words) { + const candidate = currentLine ? `${currentLine} ${word}` : word; + + if (currentLine !== '' && layoutLine(candidate, font, scale) > wrapWidth) { + lines.push(currentLine); + currentLine = word; + + continue; + } + + currentLine = candidate; + } + + lines.push(currentLine); + + return lines; +} + +function computeLines( + text: string, + font: FontAtlas, + scale: number, + wrapWidth: number | undefined, +): string[] { + const paragraphs = text.split('\n'); + + if (wrapWidth === undefined) { + return paragraphs; + } + + return paragraphs.flatMap((paragraph) => + wrapParagraph(paragraph, font, scale, wrapWidth), + ); +} + +function alignmentOffset( + alignment: TextAlignment, + blockWidth: number, + lineWidth: number, +): number { + if (alignment === 'center') { + return (blockWidth - lineWidth) / 2; + } + + if (alignment === 'right') { + return blockWidth - lineWidth; + } + + return 0; +} + +/** + * Shapes `text` into glyph quads ready for the render system: word-wrapping + * (if `wrapWidth` is given), applying kerning, and laying out lines + * according to `alignment`, `lineSpacing`, and `pivot`. + * + * Coordinates follow the same convention as `computeNineSliceRegions` + * (`/rendering`): each glyph's `offset` is relative to the text block's + * pivot-adjusted anchor, in the engine's Y-up world-unit space, ready to be + * rotated/scaled and added to an entity's world position. + * @param text - The string to shape. Explicit `\n` characters start a new + * line. + * @param font - The font atlas to shape against. + * @param fontSize - The font size, in world units, that `font`'s + * em-normalized metrics are scaled by. + * @param options - Shaping options. + * @returns The shaped glyph quads and the text block's overall bounds. + */ +export function shapeText( + text: string, + font: FontAtlas, + fontSize: number, + options: ShapeTextOptions = {}, +): ShapedText { + const { alignment, wrapWidth, lineSpacing, pivot } = { + ...defaultShapeTextOptions, + pivot: { x: 0.5, y: 0.5 }, + ...options, + }; + + const scale = fontSize / (font.emSize || 1); + const lines = computeLines(text, font, scale, wrapWidth); + const lineWidths = lines.map((line) => layoutLine(line, font, scale)); + const blockWidth = wrapWidth ?? Math.max(0, ...lineWidths); + const rowHeight = font.lineHeight * lineSpacing * scale; + const blockHeight = lines.length * rowHeight; + + const glyphs: GlyphQuad[] = []; + + lines.forEach((line, lineIndex) => { + const lineOffsetX = alignmentOffset( + alignment, + blockWidth, + lineWidths[lineIndex], + ); + // Y-down, row 0 at the block's top edge - matching `NineSliceRegion`'s + // "start" convention before the pivot subtraction below converts it to + // the engine's Y-up world space. + const baselineDown = lineIndex * rowHeight + font.ascender * scale; + + layoutLine(line, font, scale, (_codePoint, glyph, cursorX) => { + if (!glyph.planeBounds || !glyph.uvOffset || !glyph.uvScale) { + return; + } + + const left = lineOffsetX + cursorX + glyph.planeBounds.left * scale; + const right = lineOffsetX + cursorX + glyph.planeBounds.right * scale; + const topDown = baselineDown - glyph.planeBounds.top * scale; + const bottomDown = baselineDown - glyph.planeBounds.bottom * scale; + + glyphs.push({ + offset: { + x: (left + right) / 2 - pivot.x * blockWidth, + y: pivot.y * blockHeight - (topDown + bottomDown) / 2, + }, + size: { x: right - left, y: bottomDown - topDown }, + uvOffset: glyph.uvOffset, + uvScale: glyph.uvScale, + }); + }); + }); + + return { + glyphs, + bounds: { x: blockWidth, y: blockHeight }, + }; +} From b98f9c332852ba5a3565520d375584f5dbcf8e92 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 21:11:58 +0000 Subject: [PATCH 2/5] docs(text): add an interactive Text demo with a real MSDF font atlas Per the add-demo skill: a new top-level /src module (text) unambiguously needs a demo. Generates a real MSDF atlas from Liberation Sans (SIL OFL 1.1, bundled) via the actual Chlumsky msdf-atlas-gen toolchain (built from source and vendored as static assets, see the README alongside them), rather than skipping the demo for lack of a font asset. The demo shows word-wrap/kerning/alignment side by side (left/center/ right, same paragraph) plus a live per-frame-updated counter label to demonstrate createTextShapingEcsSystem's dirty tracking in action. Also updates the text module's docs to link to the new demo. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq --- .cspell/project-words.txt | 1 + documentation-site/docs/docs/text/index.md | 5 + .../docs/docs/text/msdf-text.md | 5 + documentation-site/docusaurus.config.ts | 4 + .../src/pages/demos/text/_create-game.ts | 66 ++++++++++++ .../src/pages/demos/text/_create-labels.ts | 91 +++++++++++++++++ .../demos/text/_elapsed-counter.component.ts | 10 ++ .../demos/text/_elapsed-counter.system.ts | 22 ++++ .../src/pages/demos/text/index.tsx | 43 ++++++++ .../static/fonts/liberation-sans/OFL.txt | 96 ++++++++++++++++++ .../static/fonts/liberation-sans/README.md | 19 ++++ .../liberation-sans/liberation-sans-msdf.json | 1 + .../liberation-sans/liberation-sans-msdf.png | Bin 0 -> 79558 bytes 13 files changed, 363 insertions(+) create mode 100644 documentation-site/src/pages/demos/text/_create-game.ts create mode 100644 documentation-site/src/pages/demos/text/_create-labels.ts create mode 100644 documentation-site/src/pages/demos/text/_elapsed-counter.component.ts create mode 100644 documentation-site/src/pages/demos/text/_elapsed-counter.system.ts create mode 100644 documentation-site/src/pages/demos/text/index.tsx create mode 100644 documentation-site/static/fonts/liberation-sans/OFL.txt create mode 100644 documentation-site/static/fonts/liberation-sans/README.md create mode 100644 documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json create mode 100644 documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.png diff --git a/.cspell/project-words.txt b/.cspell/project-words.txt index f1814c3d..04cfea00 100644 --- a/.cspell/project-words.txt +++ b/.cspell/project-words.txt @@ -125,4 +125,5 @@ WASD webgl Wenrexa yoavbls +yorigin Zoltan diff --git a/documentation-site/docs/docs/text/index.md b/documentation-site/docs/docs/text/index.md index 53b5c1ac..931a36f6 100644 --- a/documentation-site/docs/docs/text/index.md +++ b/documentation-site/docs/docs/text/index.md @@ -31,6 +31,11 @@ Three pieces work together: [`TextMeshEcsComponent`](/Forge/docs/api/interfaces/TextMeshEcsComponent) the render system draws. +Try it in the [Text demo](/Forge/demos/text), which renders word-wrapped, +aligned paragraphs from a real generated Liberation Sans MSDF atlas, +including a live per-frame-updated label showing the shaping system's dirty +tracking in action. + Guides in this section: - [MSDF Text](./msdf-text.md): generating a font atlas, loading it, and diff --git a/documentation-site/docs/docs/text/msdf-text.md b/documentation-site/docs/docs/text/msdf-text.md index f0c61097..9d9da23e 100644 --- a/documentation-site/docs/docs/text/msdf-text.md +++ b/documentation-site/docs/docs/text/msdf-text.md @@ -17,6 +17,11 @@ msdf-atlas-gen -font Roboto-Regular.ttf -type msdf -format png \ -charset charset.txt -size 32 -pxrange 4 ``` +(The [Text demo](/Forge/demos/text)'s own atlas - +`documentation-site/static/fonts/liberation-sans/` - was generated the same +way, from Liberation Sans; see the `README.md` alongside it for the exact +command used.) + Host the resulting `.json` and `.png` as static assets (e.g. next to your other sprite sheets) and load them with [`loadFontAtlas`](/Forge/docs/api/functions/loadFontAtlas): diff --git a/documentation-site/docusaurus.config.ts b/documentation-site/docusaurus.config.ts index 1d88a1db..8a36b6e1 100644 --- a/documentation-site/docusaurus.config.ts +++ b/documentation-site/docusaurus.config.ts @@ -195,6 +195,10 @@ const config: Config = { to: 'demos/texture-filtering', label: 'Texture Filtering', }, + { + to: 'demos/text', + label: 'Text', + }, ], }, { diff --git a/documentation-site/src/pages/demos/text/_create-game.ts b/documentation-site/src/pages/demos/text/_create-game.ts new file mode 100644 index 00000000..4bd847bb --- /dev/null +++ b/documentation-site/src/pages/demos/text/_create-game.ts @@ -0,0 +1,66 @@ +import { loadFontAtlas } from '@forge-game-engine/forge/asset-loading'; +import { SystemRegistrationOrder } from '@forge-game-engine/forge/ecs'; +import { + createCamera, + createCameraEcsSystem, + createMsdfTextRenderable, + createRenderEcsSystem, +} from '@forge-game-engine/forge/rendering'; +import { createTextShapingEcsSystem } from '@forge-game-engine/forge/text'; +import { createGame, Game } from '@forge-game-engine/forge/utilities'; +import { DEMO_VERTICAL_WORLD_UNITS } from '@site/src/utils/demo-camera'; +import { createElapsedCounterEcsSystem } from './_elapsed-counter.system'; +import { createLabels } from './_create-labels'; + +const renderLayers = { + foreground: 1 << 0, +}; + +/** + * Builds the Text demo: loads a Liberation Sans MSDF font atlas, builds its + * shared renderable, and lays out a few showcase labels (see + * `_create-labels.ts`) - a title/byline, three word-wrapped paragraphs + * (one per alignment), and a live elapsed-time counter. + * @param fontJsonUrl - The URL of the atlas's `msdf-atlas-gen` JSON metrics + * file. + * @param fontPngUrl - The URL of the atlas's PNG texture. + * @returns The created game. + */ +export const createTextGame = async ( + fontJsonUrl: string, + fontPngUrl: string, +): Promise => { + const { game, world, renderContext, time } = createGame('demo-game'); + + createCamera(world, { + isStatic: true, + cullingMask: renderLayers.foreground, + verticalWorldUnits: DEMO_VERTICAL_WORLD_UNITS, + }); + + const font = await loadFontAtlas( + fontJsonUrl, + fontPngUrl, + renderContext.imageCache, + ); + const renderable = createMsdfTextRenderable( + font, + renderContext, + renderLayers.foreground, + ); + + createLabels(world, font, renderable); + + world.addSystem(createCameraEcsSystem(time)); + // Both registered `early`, before `createRenderEcsSystem`'s default + // (`normal`) order: the counter must update `text` before shaping reads + // it, and shaping must run before render consumes this frame's glyphs. + world.addSystem( + createElapsedCounterEcsSystem(time), + SystemRegistrationOrder.early, + ); + world.addSystem(createTextShapingEcsSystem(), SystemRegistrationOrder.early); + world.addSystem(createRenderEcsSystem(renderContext)); + + return game; +}; diff --git a/documentation-site/src/pages/demos/text/_create-labels.ts b/documentation-site/src/pages/demos/text/_create-labels.ts new file mode 100644 index 00000000..0911dc6c --- /dev/null +++ b/documentation-site/src/pages/demos/text/_create-labels.ts @@ -0,0 +1,91 @@ +import { EcsWorld } from '@forge-game-engine/forge/ecs'; +import { addPositionComponent } from '@forge-game-engine/forge/common'; +import { Color, Renderable } from '@forge-game-engine/forge/rendering'; +import { FontAtlas } from '@forge-game-engine/forge/asset-loading'; +import { + addTextComponent, + TextAlignment, +} from '@forge-game-engine/forge/text'; +import { elapsedCounterTag } from './_elapsed-counter.component'; + +const paragraph = + 'Word-wrapped MSDF text with kerning and configurable alignment.'; + +interface LabelOptions { + text: string; + fontSize: number; + color?: Color; + alignment?: TextAlignment; + wrapWidth?: number; +} + +function createLabel( + world: EcsWorld, + font: FontAtlas, + renderable: Renderable, + y: number, + options: LabelOptions, +): void { + const entity = world.createEntity(); + + addPositionComponent(world, entity, { world: { x: 0, y } }); + addTextComponent(world, entity, { + font, + renderable, + ...options, + }); +} + +/** + * Builds the demo's showcase text entities: a title/byline, three + * word-wrapped paragraphs (one per `TextAlignment`) stacked so their + * layout can be compared directly, and a live elapsed-time counter tagged + * for `createElapsedCounterEcsSystem` to update every frame. + * @param world - The ECS world to add the text entities to. + * @param font - The loaded font atlas every label shares. + * @param renderable - The MSDF renderable every label shares, built once + * for this font via `createMsdfTextRenderable`. + */ +export function createLabels( + world: EcsWorld, + font: FontAtlas, + renderable: Renderable, +): void { + createLabel(world, font, renderable, 250, { + text: 'Forge Text', + fontSize: 44, + }); + + createLabel(world, font, renderable, 205, { + text: 'MSDF font atlas rendering, batched with sprites.', + fontSize: 16, + color: new Color(0.7, 0.72, 0.78, 1), + }); + + const alignments: { alignment: TextAlignment; y: number }[] = [ + { alignment: 'left', y: 130 }, + { alignment: 'center', y: 10 }, + { alignment: 'right', y: -110 }, + ]; + + for (const { alignment, y } of alignments) { + createLabel(world, font, renderable, y, { + text: `${alignment}: ${paragraph}`, + fontSize: 16, + wrapWidth: 260, + alignment, + }); + } + + const counterEntity = world.createEntity(); + + addPositionComponent(world, counterEntity, { world: { x: 0, y: -240 } }); + addTextComponent(world, counterEntity, { + text: 'Elapsed: 0.0s', + font, + renderable, + fontSize: 26, + color: new Color(0.4, 0.85, 1, 1), + }); + world.addTag(counterEntity, elapsedCounterTag); +} diff --git a/documentation-site/src/pages/demos/text/_elapsed-counter.component.ts b/documentation-site/src/pages/demos/text/_elapsed-counter.component.ts new file mode 100644 index 00000000..f09fb4b1 --- /dev/null +++ b/documentation-site/src/pages/demos/text/_elapsed-counter.component.ts @@ -0,0 +1,10 @@ +import { createTagId } from '@forge-game-engine/forge/ecs'; + +/** + * Marks a text entity whose `text` `createElapsedCounterEcsSystem` + * (`_elapsed-counter.system.ts`) updates every frame with the demo's + * elapsed time - showcasing that updating `TextEcsComponent.text` is cheap, + * since `createTextShapingEcsSystem` only re-shapes glyphs for entities + * whose text (or another shape-affecting field) actually changed. + */ +export const elapsedCounterTag = createTagId('elapsedCounter'); diff --git a/documentation-site/src/pages/demos/text/_elapsed-counter.system.ts b/documentation-site/src/pages/demos/text/_elapsed-counter.system.ts new file mode 100644 index 00000000..4e2a06fc --- /dev/null +++ b/documentation-site/src/pages/demos/text/_elapsed-counter.system.ts @@ -0,0 +1,22 @@ +import { EcsSystem } from '@forge-game-engine/forge/ecs'; +import { Time } from '@forge-game-engine/forge/common'; +import { TextEcsComponent, textId } from '@forge-game-engine/forge/text'; +import { elapsedCounterTag } from './_elapsed-counter.component'; + +/** + * Creates a system that updates every entity tagged `elapsedCounterTag`'s + * text with the demo's elapsed time, once per frame. + * @param time - The time instance used to read elapsed seconds. + * @returns The ECS system. + */ +export const createElapsedCounterEcsSystem = ( + time: Time, +): EcsSystem<[TextEcsComponent]> => ({ + query: [textId], + tags: [elapsedCounterTag], + update: (_world, { components: [texts] }) => { + for (const text of texts) { + text.text = `Elapsed: ${time.timeInSeconds.toFixed(1)}s`; + } + }, +}); diff --git a/documentation-site/src/pages/demos/text/index.tsx b/documentation-site/src/pages/demos/text/index.tsx new file mode 100644 index 00000000..2202057e --- /dev/null +++ b/documentation-site/src/pages/demos/text/index.tsx @@ -0,0 +1,43 @@ +import React, { JSX } from 'react'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import { createTextGame } from './_create-game'; +import gameCode from '!!raw-loader!./_create-game'; +import createLabelsCode from '!!raw-loader!./_create-labels'; +import elapsedCounterComponentCode from '!!raw-loader!./_elapsed-counter.component'; +import elapsedCounterSystemCode from '!!raw-loader!./_elapsed-counter.system'; + +import { Demo } from '@site/src/components/Demo'; + +export default function Text(): JSX.Element { + const fontJsonUrl = useBaseUrl( + 'fonts/liberation-sans/liberation-sans-msdf.json', + ); + const fontPngUrl = useBaseUrl( + 'fonts/liberation-sans/liberation-sans-msdf.png', + ); + + return ( + createTextGame(fontJsonUrl, fontPngUrl)} + codeFiles={[ + { name: 'game.ts', content: gameCode }, + { name: 'create-labels.ts', content: createLabelsCode }, + { + name: 'elapsed-counter.component.ts', + content: elapsedCounterComponentCode, + }, + { + name: 'elapsed-counter.system.ts', + content: elapsedCounterSystemCode, + }, + ]} + /> + ); +} diff --git a/documentation-site/static/fonts/liberation-sans/OFL.txt b/documentation-site/static/fonts/liberation-sans/OFL.txt new file mode 100644 index 00000000..874204f6 --- /dev/null +++ b/documentation-site/static/fonts/liberation-sans/OFL.txt @@ -0,0 +1,96 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE The goals of the Open Font License (OFL) are to stimulate +worldwide development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to provide +a free and open framework in which fonts may be shared and improved in +partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. +The fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + + + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. +This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components +as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting ? in part or in whole ? +any of the components of the Original Version, by changing formats or +by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer +or other person who contributed to the Font Software. + + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components,in + Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or + in the appropriate machine-readable metadata fields within text or + binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font + Name(s) unless explicit written permission is granted by the + corresponding Copyright Holder. This restriction only applies to the + primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any + Modified Version, except to acknowledge the contribution(s) of the + Copyright Holder(s) and the Author(s) or with their explicit written + permission. + +5) The Font Software, modified or unmodified, in part or in whole, must + be distributed entirely under this license, and must not be distributed + under any other license. The requirement for fonts to remain under + this license does not apply to any document created using the Font + Software. + + + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + + + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. diff --git a/documentation-site/static/fonts/liberation-sans/README.md b/documentation-site/static/fonts/liberation-sans/README.md new file mode 100644 index 00000000..558acd7f --- /dev/null +++ b/documentation-site/static/fonts/liberation-sans/README.md @@ -0,0 +1,19 @@ +# Liberation Sans MSDF atlas + +`liberation-sans-msdf.png` + `liberation-sans-msdf.json` were generated from +`LiberationSans-Regular.ttf` (SIL Open Font License 1.1, see `OFL.txt`) +using [`msdf-atlas-gen`](https://github.com/Chlumsky/msdf-atlas-gen) v1.4.0: + +```sh +msdf-atlas-gen \ + -font LiberationSans-Regular.ttf \ + -type msdf -format png \ + -imageout liberation-sans-msdf.png \ + -json liberation-sans-msdf.json \ + -size 48 -pxrange 4 -yorigin bottom +``` + +Used by the [Text demo](../../src/pages/demos/text) as the sample font for +`@forge-game-engine/forge/text`. Regenerate with the same command (pointed +at an updated font file, or with a different `-charset` if the demo needs +characters outside the default ASCII set) to refresh these assets. diff --git a/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json b/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json new file mode 100644 index 00000000..1261ef42 --- /dev/null +++ b/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json @@ -0,0 +1 @@ +{"atlas":{"type":"msdf","distanceRange":4,"distanceRangeMiddle":0,"size":48,"width":316,"height":316,"yOrigin":"bottom"},"metrics":{"emSize":1,"lineHeight":1.14990234375,"ascender":0.9052734375,"descender":-0.2119140625,"underlineY":-0.0693359375,"underlineThickness":0.0732421875},"glyphs":[{"unicode":32,"advance":0.27783203125},{"unicode":33,"advance":0.27783203125,"planeBounds":{"left":0.044921875,"bottom":-0.052083333333333336,"right":0.232421875,"top":0.73958333333333326},"atlasBounds":{"left":33.5,"bottom":144.5,"right":42.5,"top":182.5}},{"unicode":34,"advance":0.35498046875,"planeBounds":{"left":0.00040690104166666782,"bottom":0.42708333333333337,"right":0.35457356770833331,"top":0.73958333333333326},"atlasBounds":{"left":169.5,"bottom":17.5,"right":186.5,"top":32.5}},{"unicode":35,"advance":0.55615234375,"planeBounds":{"left":-0.045084635416666706,"bottom":-0.052083333333333336,"right":0.60074869791666663,"top":0.73958333333333326},"atlasBounds":{"left":230.5,"bottom":144.5,"right":261.5,"top":182.5}},{"unicode":36,"advance":0.55615234375,"planeBounds":{"left":-0.037109375000000035,"bottom":-0.11458333333333333,"right":0.58789062499999989,"top":0.80208333333333326},"atlasBounds":{"left":213.5,"bottom":271.5,"right":243.5,"top":315.5}},{"unicode":37,"advance":0.88916015625,"planeBounds":{"left":-0.01375325520833333,"bottom":-0.052083333333333336,"right":0.90291341145833326,"top":0.73958333333333326},"atlasBounds":{"left":271.5,"bottom":277.5,"right":315.5,"top":315.5}},{"unicode":38,"advance":0.6669921875,"planeBounds":{"left":-0.010904947916666663,"bottom":-0.052083333333333336,"right":0.69742838541666663,"top":0.73958333333333326},"atlasBounds":{"left":0.5,"bottom":65.5,"right":34.5,"top":103.5}},{"unicode":39,"advance":0.19091796875,"planeBounds":{"left":0.0019531250000000017,"bottom":0.42708333333333337,"right":0.189453125,"top":0.73958333333333326},"atlasBounds":{"left":303.5,"bottom":226.5,"right":312.5,"top":241.5}},{"unicode":40,"advance":0.3330078125,"planeBounds":{"left":0.017496744791666668,"bottom":-0.26041666666666663,"right":0.37166341145833331,"top":0.78125},"atlasBounds":{"left":0.5,"bottom":265.5,"right":17.5,"top":315.5}},{"unicode":41,"advance":0.3330078125,"planeBounds":{"left":-0.038655598958333336,"bottom":-0.26041666666666663,"right":0.31551106770833331,"top":0.78125},"atlasBounds":{"left":18.5,"bottom":265.5,"right":35.5,"top":315.5}},{"unicode":42,"advance":0.38916015625,"planeBounds":{"left":-0.034342447916666664,"bottom":0.28125,"right":0.42399088541666669,"top":0.73958333333333326},"atlasBounds":{"left":293.5,"bottom":242.5,"right":315.5,"top":264.5}},{"unicode":43,"advance":0.583984375,"planeBounds":{"left":8.1380208333299217e-05,"bottom":0.03125,"right":0.58341471354166663,"top":0.63541666666666663},"atlasBounds":{"left":54.5,"bottom":3.5,"right":82.5,"top":32.5}},{"unicode":44,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.17708333333333334,"right":0.232666015625,"top":0.15625},"atlasBounds":{"left":293.5,"bottom":225.5,"right":302.5,"top":241.5}},{"unicode":45,"advance":0.3330078125,"planeBounds":{"left":-0.00016276041666664701,"bottom":0.17708333333333331,"right":0.33317057291666669,"top":0.36458333333333331},"atlasBounds":{"left":241.5,"bottom":23.5,"right":257.5,"top":32.5}},{"unicode":46,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.052083333333333336,"right":0.232666015625,"top":0.15625},"atlasBounds":{"left":231.5,"bottom":22.5,"right":240.5,"top":32.5}},{"unicode":47,"advance":0.27783203125,"planeBounds":{"left":-0.048583984375,"bottom":-0.052083333333333336,"right":0.32641601562499994,"top":0.78125},"atlasBounds":{"left":54.5,"bottom":224.5,"right":72.5,"top":264.5}},{"unicode":48,"advance":0.55615234375,"planeBounds":{"left":-0.0031738281250000347,"bottom":-0.052083333333333336,"right":0.55932617187499989,"top":0.76041666666666663},"atlasBounds":{"left":265.5,"bottom":225.5,"right":292.5,"top":264.5}},{"unicode":49,"advance":0.55615234375,"planeBounds":{"left":0.031331380208333294,"bottom":-0.052083333333333336,"right":0.55216471354166663,"top":0.73958333333333326},"atlasBounds":{"left":103.5,"bottom":65.5,"right":128.5,"top":103.5}},{"unicode":50,"advance":0.55615234375,"planeBounds":{"left":0.0072428385416666314,"bottom":-0.052083333333333336,"right":0.54890950520833326,"top":0.76041666666666663},"atlasBounds":{"left":0.5,"bottom":183.5,"right":26.5,"top":222.5}},{"unicode":51,"advance":0.55615234375,"planeBounds":{"left":-0.006103515625000033,"bottom":-0.052083333333333336,"right":0.55639648437499989,"top":0.76041666666666663},"atlasBounds":{"left":27.5,"bottom":183.5,"right":54.5,"top":222.5}},{"unicode":52,"advance":0.55615234375,"planeBounds":{"left":-0.02718098958333337,"bottom":-0.052083333333333336,"right":0.57698567708333326,"top":0.73958333333333326},"atlasBounds":{"left":129.5,"bottom":65.5,"right":158.5,"top":103.5}},{"unicode":53,"advance":0.55615234375,"planeBounds":{"left":-0.0041503906250000347,"bottom":-0.052083333333333336,"right":0.55834960937499989,"top":0.73958333333333326},"atlasBounds":{"left":159.5,"bottom":65.5,"right":186.5,"top":103.5}},{"unicode":54,"advance":0.55615234375,"planeBounds":{"left":0.00024414062499996357,"bottom":-0.052083333333333336,"right":0.56274414062499989,"top":0.76041666666666663},"atlasBounds":{"left":55.5,"bottom":183.5,"right":82.5,"top":222.5}},{"unicode":55,"advance":0.55615234375,"planeBounds":{"left":0.0077311197916666314,"bottom":-0.052083333333333336,"right":0.54939778645833326,"top":0.73958333333333326},"atlasBounds":{"left":187.5,"bottom":65.5,"right":213.5,"top":103.5}},{"unicode":56,"advance":0.55615234375,"planeBounds":{"left":-0.0031738281250000347,"bottom":-0.052083333333333336,"right":0.55932617187499989,"top":0.76041666666666663},"atlasBounds":{"left":83.5,"bottom":183.5,"right":110.5,"top":222.5}},{"unicode":57,"advance":0.55615234375,"planeBounds":{"left":-0.0034179687500000364,"bottom":-0.052083333333333336,"right":0.55908203124999989,"top":0.76041666666666663},"atlasBounds":{"left":111.5,"bottom":183.5,"right":138.5,"top":222.5}},{"unicode":58,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.052083333333333336,"right":0.232666015625,"top":0.57291666666666663},"atlasBounds":{"left":234.5,"bottom":34.5,"right":243.5,"top":64.5}},{"unicode":59,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.17708333333333334,"right":0.232666015625,"top":0.57291666666666663},"atlasBounds":{"left":303.5,"bottom":146.5,"right":312.5,"top":182.5}},{"unicode":60,"advance":0.583984375,"planeBounds":{"left":0.00056966145833329922,"bottom":0.03125,"right":0.58390299479166663,"top":0.63541666666666663},"atlasBounds":{"left":83.5,"bottom":3.5,"right":111.5,"top":32.5}},{"unicode":61,"advance":0.583984375,"planeBounds":{"left":8.1380208333299217e-05,"bottom":0.11458333333333334,"right":0.58341471354166663,"top":0.55208333333333326},"atlasBounds":{"left":140.5,"bottom":11.5,"right":168.5,"top":32.5}},{"unicode":62,"advance":0.583984375,"planeBounds":{"left":0.00056966145833329922,"bottom":0.03125,"right":0.58390299479166663,"top":0.63541666666666663},"atlasBounds":{"left":25.5,"bottom":3.5,"right":53.5,"top":32.5}},{"unicode":63,"advance":0.55615234375,"planeBounds":{"left":-0.0012207031250000347,"bottom":-0.052083333333333336,"right":0.56127929687499989,"top":0.76041666666666663},"atlasBounds":{"left":139.5,"bottom":183.5,"right":166.5,"top":222.5}},{"unicode":64,"advance":1.01513671875,"planeBounds":{"left":0.034912109375,"bottom":-0.19791666666666669,"right":0.972412109375,"top":0.78124999999999989},"atlasBounds":{"left":167.5,"bottom":268.5,"right":212.5,"top":315.5}},{"unicode":65,"advance":0.6669921875,"planeBounds":{"left":-0.04150390625,"bottom":-0.052083333333333336,"right":0.70849609375,"top":0.73958333333333326},"atlasBounds":{"left":66.5,"bottom":65.5,"right":102.5,"top":103.5}},{"unicode":66,"advance":0.6669921875,"planeBounds":{"left":0.035644531249999965,"bottom":-0.052083333333333336,"right":0.66064453124999989,"top":0.73958333333333326},"atlasBounds":{"left":35.5,"bottom":65.5,"right":65.5,"top":103.5}},{"unicode":67,"advance":0.72216796875,"planeBounds":{"left":0.0028483072916666696,"bottom":-0.052083333333333336,"right":0.73201497395833326,"top":0.76041666666666663},"atlasBounds":{"left":167.5,"bottom":183.5,"right":202.5,"top":222.5}},{"unicode":68,"advance":0.72216796875,"planeBounds":{"left":0.034423828125,"bottom":-0.052083333333333336,"right":0.721923828125,"top":0.73958333333333326},"atlasBounds":{"left":277.5,"bottom":104.5,"right":310.5,"top":142.5}},{"unicode":69,"advance":0.6669921875,"planeBounds":{"left":0.030110677083333301,"bottom":-0.052083333333333336,"right":0.67594401041666663,"top":0.73958333333333326},"atlasBounds":{"left":245.5,"bottom":104.5,"right":276.5,"top":142.5}},{"unicode":70,"advance":0.61083984375,"planeBounds":{"left":0.034749348958333301,"bottom":-0.052083333333333336,"right":0.61808268229166663,"top":0.73958333333333326},"atlasBounds":{"left":216.5,"bottom":104.5,"right":244.5,"top":142.5}},{"unicode":71,"advance":0.77783203125,"planeBounds":{"left":0.0017089843750000017,"bottom":-0.052083333333333336,"right":0.751708984375,"top":0.76041666666666663},"atlasBounds":{"left":203.5,"bottom":183.5,"right":239.5,"top":222.5}},{"unicode":72,"advance":0.72216796875,"planeBounds":{"left":0.038411458333333294,"bottom":-0.052083333333333336,"right":0.68424479166666663,"top":0.73958333333333326},"atlasBounds":{"left":152.5,"bottom":104.5,"right":183.5,"top":142.5}},{"unicode":73,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.052083333333333336,"right":0.232666015625,"top":0.73958333333333326},"atlasBounds":{"left":293.5,"bottom":144.5,"right":302.5,"top":182.5}},{"unicode":74,"advance":0.5,"planeBounds":{"left":-0.029296875,"bottom":-0.052083333333333336,"right":0.47070312499999994,"top":0.73958333333333326},"atlasBounds":{"left":95.5,"bottom":104.5,"right":119.5,"top":142.5}},{"unicode":75,"advance":0.6669921875,"planeBounds":{"left":0.03556315104166663,"bottom":-0.052083333333333336,"right":0.70222981770833326,"top":0.73958333333333326},"atlasBounds":{"left":62.5,"bottom":104.5,"right":94.5,"top":142.5}},{"unicode":76,"advance":0.55615234375,"planeBounds":{"left":0.03165690104166663,"bottom":-0.052083333333333336,"right":0.57332356770833326,"top":0.73958333333333326},"atlasBounds":{"left":35.5,"bottom":104.5,"right":61.5,"top":142.5}},{"unicode":77,"advance":0.8330078125,"planeBounds":{"left":0.031087239583333336,"bottom":-0.052083333333333336,"right":0.80192057291666663,"top":0.73958333333333326},"atlasBounds":{"left":278.5,"bottom":184.5,"right":315.5,"top":222.5}},{"unicode":78,"advance":0.72216796875,"planeBounds":{"left":0.038411458333333294,"bottom":-0.052083333333333336,"right":0.68424479166666663,"top":0.73958333333333326},"atlasBounds":{"left":120.5,"bottom":104.5,"right":151.5,"top":142.5}},{"unicode":79,"advance":0.77783203125,"planeBounds":{"left":0.0032552083333333356,"bottom":-0.052083333333333336,"right":0.77408854166666663,"top":0.76041666666666663},"atlasBounds":{"left":240.5,"bottom":183.5,"right":277.5,"top":222.5}},{"unicode":80,"advance":0.6669921875,"planeBounds":{"left":0.035644531249999965,"bottom":-0.052083333333333336,"right":0.66064453124999989,"top":0.73958333333333326},"atlasBounds":{"left":262.5,"bottom":144.5,"right":292.5,"top":182.5}},{"unicode":81,"advance":0.77783203125,"planeBounds":{"left":0.0032552083333333356,"bottom":-0.23958333333333334,"right":0.77408854166666663,"top":0.76041666666666652},"atlasBounds":{"left":129.5,"bottom":267.5,"right":166.5,"top":315.5}},{"unicode":82,"advance":0.72216796875,"planeBounds":{"left":0.03515625,"bottom":-0.052083333333333336,"right":0.72265625,"top":0.73958333333333326},"atlasBounds":{"left":196.5,"bottom":144.5,"right":229.5,"top":182.5}},{"unicode":83,"advance":0.6669921875,"planeBounds":{"left":-8.1380208333368606e-05,"bottom":-0.052083333333333336,"right":0.66658528645833326,"top":0.76041666666666663},"atlasBounds":{"left":0.5,"bottom":143.5,"right":32.5,"top":182.5}},{"unicode":84,"advance":0.61083984375,"planeBounds":{"left":-0.02815755208333337,"bottom":-0.052083333333333336,"right":0.63850911458333326,"top":0.73958333333333326},"atlasBounds":{"left":113.5,"bottom":144.5,"right":145.5,"top":182.5}},{"unicode":85,"advance":0.72216796875,"planeBounds":{"left":0.02775065104166663,"bottom":-0.052083333333333336,"right":0.69441731770833326,"top":0.73958333333333326},"atlasBounds":{"left":80.5,"bottom":144.5,"right":112.5,"top":182.5}},{"unicode":86,"advance":0.6669921875,"planeBounds":{"left":-0.04150390625,"bottom":-0.052083333333333336,"right":0.70849609375,"top":0.73958333333333326},"atlasBounds":{"left":43.5,"bottom":144.5,"right":79.5,"top":182.5}},{"unicode":87,"advance":0.94384765625,"planeBounds":{"left":-0.038004557291666664,"bottom":-0.052083333333333336,"right":0.98282877604166663,"top":0.73958333333333326},"atlasBounds":{"left":146.5,"bottom":144.5,"right":195.5,"top":182.5}},{"unicode":88,"advance":0.6669921875,"planeBounds":{"left":-0.019938151041666664,"bottom":-0.052083333333333336,"right":0.68839518229166663,"top":0.73958333333333326},"atlasBounds":{"left":214.5,"bottom":65.5,"right":248.5,"top":103.5}},{"unicode":89,"advance":0.6669921875,"planeBounds":{"left":-0.020670572916666664,"bottom":-0.052083333333333336,"right":0.68766276041666663,"top":0.73958333333333326},"atlasBounds":{"left":0.5,"bottom":104.5,"right":34.5,"top":142.5}},{"unicode":90,"advance":0.61083984375,"planeBounds":{"left":-0.017252604166666699,"bottom":-0.052083333333333336,"right":0.62858072916666663,"top":0.73958333333333326},"atlasBounds":{"left":184.5,"bottom":104.5,"right":215.5,"top":142.5}},{"unicode":91,"advance":0.27783203125,"planeBounds":{"left":0.024820963541666685,"bottom":-0.26041666666666663,"right":0.31648763020833331,"top":0.78125},"atlasBounds":{"left":85.5,"bottom":265.5,"right":99.5,"top":315.5}},{"unicode":92,"advance":0.27783203125,"planeBounds":{"left":-0.048583984375,"bottom":-0.052083333333333336,"right":0.32641601562499994,"top":0.78125},"atlasBounds":{"left":102.5,"bottom":224.5,"right":120.5,"top":264.5}},{"unicode":93,"advance":0.27783203125,"planeBounds":{"left":-0.038655598958333336,"bottom":-0.26041666666666663,"right":0.25301106770833331,"top":0.78125},"atlasBounds":{"left":100.5,"bottom":265.5,"right":114.5,"top":315.5}},{"unicode":94,"advance":0.46923828125,"planeBounds":{"left":-0.046630859375000035,"bottom":0.28125,"right":0.51586914062499989,"top":0.73958333333333326},"atlasBounds":{"left":112.5,"bottom":10.5,"right":139.5,"top":32.5}},{"unicode":95,"advance":0.55615234375,"planeBounds":{"left":-0.057210286458333363,"bottom":-0.26041666666666663,"right":0.60945638020833326,"top":-0.072916666666666657},"atlasBounds":{"left":258.5,"bottom":23.5,"right":290.5,"top":32.5}},{"unicode":96,"advance":0.3330078125,"planeBounds":{"left":0.0094401041666666869,"bottom":0.53125,"right":0.30110677083333331,"top":0.78125},"atlasBounds":{"left":187.5,"bottom":20.5,"right":201.5,"top":32.5}},{"unicode":97,"advance":0.55615234375,"planeBounds":{"left":-0.0027669270833333686,"bottom":-0.052083333333333336,"right":0.60139973958333326,"top":0.59375},"atlasBounds":{"left":147.5,"bottom":33.5,"right":176.5,"top":64.5}},{"unicode":98,"advance":0.55615234375,"planeBounds":{"left":0.01847330729166663,"bottom":-0.052083333333333336,"right":0.56013997395833326,"top":0.78125},"atlasBounds":{"left":121.5,"bottom":224.5,"right":147.5,"top":264.5}},{"unicode":99,"advance":0.5,"planeBounds":{"left":-0.0023600260416667025,"bottom":-0.052083333333333336,"right":0.51847330729166663,"top":0.59375},"atlasBounds":{"left":26.5,"bottom":33.5,"right":51.5,"top":64.5}},{"unicode":100,"advance":0.55615234375,"planeBounds":{"left":-0.0039876302083333686,"bottom":-0.052083333333333336,"right":0.53767903645833326,"top":0.78125},"atlasBounds":{"left":184.5,"bottom":224.5,"right":210.5,"top":264.5}},{"unicode":101,"advance":0.55615234375,"planeBounds":{"left":-0.0041503906250000347,"bottom":-0.052083333333333336,"right":0.55834960937499989,"top":0.59375},"atlasBounds":{"left":80.5,"bottom":33.5,"right":107.5,"top":64.5}},{"unicode":102,"advance":0.27783203125,"planeBounds":{"left":-0.030354817708333336,"bottom":-0.052083333333333336,"right":0.32381184895833331,"top":0.78125},"atlasBounds":{"left":247.5,"bottom":224.5,"right":264.5,"top":264.5}},{"unicode":103,"advance":0.55615234375,"planeBounds":{"left":-0.0039876302083333686,"bottom":-0.26041666666666663,"right":0.53767903645833326,"top":0.59375},"atlasBounds":{"left":244.5,"bottom":274.5,"right":270.5,"top":315.5}},{"unicode":104,"advance":0.55615234375,"planeBounds":{"left":0.019856770833333301,"bottom":-0.052083333333333336,"right":0.54069010416666663,"top":0.78125},"atlasBounds":{"left":221.5,"bottom":224.5,"right":246.5,"top":264.5}},{"unicode":105,"advance":0.22216796875,"planeBounds":{"left":0.01708984375,"bottom":-0.052083333333333336,"right":0.20458984375,"top":0.78125},"atlasBounds":{"left":211.5,"bottom":224.5,"right":220.5,"top":264.5}},{"unicode":106,"advance":0.22216796875,"planeBounds":{"left":-0.070231119791666657,"bottom":-0.26041666666666663,"right":0.20060221354166669,"top":0.78125},"atlasBounds":{"left":115.5,"bottom":265.5,"right":128.5,"top":315.5}},{"unicode":107,"advance":0.5,"planeBounds":{"left":0.024007161458333297,"bottom":-0.052083333333333336,"right":0.54484049479166663,"top":0.78125},"atlasBounds":{"left":158.5,"bottom":224.5,"right":183.5,"top":264.5}},{"unicode":108,"advance":0.22216796875,"planeBounds":{"left":0.017578125,"bottom":-0.052083333333333336,"right":0.205078125,"top":0.78125},"atlasBounds":{"left":148.5,"bottom":224.5,"right":157.5,"top":264.5}},{"unicode":109,"advance":0.8330078125,"planeBounds":{"left":0.020914713541666668,"bottom":-0.052083333333333336,"right":0.81258138020833326,"top":0.59375},"atlasBounds":{"left":108.5,"bottom":33.5,"right":146.5,"top":64.5}},{"unicode":110,"advance":0.55615234375,"planeBounds":{"left":0.018391927083333301,"bottom":-0.052083333333333336,"right":0.53922526041666663,"top":0.59375},"atlasBounds":{"left":267.5,"bottom":72.5,"right":292.5,"top":103.5}},{"unicode":111,"advance":0.55615234375,"planeBounds":{"left":-0.0031738281250000347,"bottom":-0.052083333333333336,"right":0.55932617187499989,"top":0.59375},"atlasBounds":{"left":52.5,"bottom":33.5,"right":79.5,"top":64.5}},{"unicode":112,"advance":0.55615234375,"planeBounds":{"left":0.01847330729166663,"bottom":-0.26041666666666663,"right":0.56013997395833326,"top":0.59375},"atlasBounds":{"left":0.5,"bottom":223.5,"right":26.5,"top":264.5}},{"unicode":113,"advance":0.55615234375,"planeBounds":{"left":-0.0037434895833333686,"bottom":-0.26041666666666663,"right":0.53792317708333326,"top":0.59375},"atlasBounds":{"left":27.5,"bottom":223.5,"right":53.5,"top":264.5}},{"unicode":114,"advance":0.3330078125,"planeBounds":{"left":0.024739583333333336,"bottom":-0.052083333333333336,"right":0.35807291666666669,"top":0.59375},"atlasBounds":{"left":293.5,"bottom":72.5,"right":309.5,"top":103.5}},{"unicode":115,"advance":0.5,"planeBounds":{"left":-0.014567057291666704,"bottom":-0.052083333333333336,"right":0.50626627604166663,"top":0.59375},"atlasBounds":{"left":0.5,"bottom":33.5,"right":25.5,"top":64.5}},{"unicode":116,"advance":0.27783203125,"planeBounds":{"left":-0.034261067708333336,"bottom":-0.052083333333333336,"right":0.31990559895833331,"top":0.69791666666666663},"atlasBounds":{"left":249.5,"bottom":67.5,"right":266.5,"top":103.5}},{"unicode":117,"advance":0.55615234375,"planeBounds":{"left":0.016927083333333301,"bottom":-0.052083333333333336,"right":0.53776041666666663,"top":0.57291666666666663},"atlasBounds":{"left":244.5,"bottom":34.5,"right":269.5,"top":64.5}},{"unicode":118,"advance":0.5,"planeBounds":{"left":-0.041666666666666706,"bottom":-0.052083333333333336,"right":0.54166666666666663,"top":0.57291666666666663},"atlasBounds":{"left":177.5,"bottom":34.5,"right":205.5,"top":64.5}},{"unicode":119,"advance":0.72216796875,"planeBounds":{"left":-0.044677734375,"bottom":-0.052083333333333336,"right":0.767822265625,"top":0.57291666666666663},"atlasBounds":{"left":270.5,"bottom":34.5,"right":309.5,"top":64.5}},{"unicode":120,"advance":0.5,"planeBounds":{"left":-0.031005859375000035,"bottom":-0.052083333333333336,"right":0.53149414062499989,"top":0.57291666666666663},"atlasBounds":{"left":206.5,"bottom":34.5,"right":233.5,"top":64.5}},{"unicode":121,"advance":0.5,"planeBounds":{"left":-0.041422526041666706,"bottom":-0.26041666666666663,"right":0.54191080729166663,"top":0.57291666666666674},"atlasBounds":{"left":73.5,"bottom":224.5,"right":101.5,"top":264.5}},{"unicode":122,"advance":0.5,"planeBounds":{"left":-0.0046386718749999983,"bottom":-0.052083333333333336,"right":0.49536132812499994,"top":0.57291666666666663},"atlasBounds":{"left":0.5,"bottom":2.5,"right":24.5,"top":32.5}},{"unicode":123,"advance":0.333984375,"planeBounds":{"left":-0.031412760416666664,"bottom":-0.26041666666666663,"right":0.36442057291666669,"top":0.78125},"atlasBounds":{"left":65.5,"bottom":265.5,"right":84.5,"top":315.5}},{"unicode":124,"advance":0.259765625,"planeBounds":{"left":0.046549479166666657,"bottom":-0.26041666666666663,"right":0.21321614583333331,"top":0.78125},"atlasBounds":{"left":56.5,"bottom":265.5,"right":64.5,"top":315.5}},{"unicode":125,"advance":0.333984375,"planeBounds":{"left":-0.031656901041666664,"bottom":-0.26041666666666663,"right":0.36417643229166669,"top":0.78125},"atlasBounds":{"left":36.5,"bottom":265.5,"right":55.5,"top":315.5}},{"unicode":126,"advance":0.583984375,"planeBounds":{"left":0.00032552083333329748,"bottom":0.21875,"right":0.58365885416666663,"top":0.44791666666666663},"atlasBounds":{"left":202.5,"bottom":21.5,"right":230.5,"top":32.5}}],"kerning":[{"unicode1":32,"unicode2":65,"advance":-0.05517578125},{"unicode1":32,"unicode2":84,"advance":-0.01806640625},{"unicode1":32,"unicode2":89,"advance":-0.01806640625},{"unicode1":49,"unicode2":49,"advance":-0.07421875},{"unicode1":65,"unicode2":32,"advance":-0.05517578125},{"unicode1":65,"unicode2":84,"advance":-0.07421875},{"unicode1":65,"unicode2":86,"advance":-0.07421875},{"unicode1":65,"unicode2":87,"advance":-0.037109375},{"unicode1":65,"unicode2":89,"advance":-0.07421875},{"unicode1":65,"unicode2":118,"advance":-0.01806640625},{"unicode1":65,"unicode2":119,"advance":-0.01806640625},{"unicode1":65,"unicode2":121,"advance":-0.01806640625},{"unicode1":70,"unicode2":44,"advance":-0.11083984375},{"unicode1":70,"unicode2":46,"advance":-0.11083984375},{"unicode1":70,"unicode2":65,"advance":-0.05517578125},{"unicode1":76,"unicode2":32,"advance":-0.037109375},{"unicode1":76,"unicode2":84,"advance":-0.07421875},{"unicode1":76,"unicode2":86,"advance":-0.07421875},{"unicode1":76,"unicode2":87,"advance":-0.07421875},{"unicode1":76,"unicode2":89,"advance":-0.07421875},{"unicode1":76,"unicode2":121,"advance":-0.037109375},{"unicode1":80,"unicode2":32,"advance":-0.01806640625},{"unicode1":80,"unicode2":44,"advance":-0.12890625},{"unicode1":80,"unicode2":46,"advance":-0.12890625},{"unicode1":80,"unicode2":65,"advance":-0.07421875},{"unicode1":82,"unicode2":84,"advance":-0.01806640625},{"unicode1":82,"unicode2":86,"advance":-0.01806640625},{"unicode1":82,"unicode2":87,"advance":-0.01806640625},{"unicode1":82,"unicode2":89,"advance":-0.01806640625},{"unicode1":84,"unicode2":32,"advance":-0.01806640625},{"unicode1":84,"unicode2":44,"advance":-0.11083984375},{"unicode1":84,"unicode2":45,"advance":-0.05517578125},{"unicode1":84,"unicode2":46,"advance":-0.11083984375},{"unicode1":84,"unicode2":58,"advance":-0.11083984375},{"unicode1":84,"unicode2":59,"advance":-0.11083984375},{"unicode1":84,"unicode2":65,"advance":-0.07421875},{"unicode1":84,"unicode2":79,"advance":-0.01806640625},{"unicode1":84,"unicode2":97,"advance":-0.11083984375},{"unicode1":84,"unicode2":99,"advance":-0.11083984375},{"unicode1":84,"unicode2":101,"advance":-0.11083984375},{"unicode1":84,"unicode2":105,"advance":-0.037109375},{"unicode1":84,"unicode2":111,"advance":-0.11083984375},{"unicode1":84,"unicode2":114,"advance":-0.037109375},{"unicode1":84,"unicode2":115,"advance":-0.11083984375},{"unicode1":84,"unicode2":117,"advance":-0.037109375},{"unicode1":84,"unicode2":119,"advance":-0.05517578125},{"unicode1":84,"unicode2":121,"advance":-0.05517578125},{"unicode1":86,"unicode2":44,"advance":-0.091796875},{"unicode1":86,"unicode2":45,"advance":-0.05517578125},{"unicode1":86,"unicode2":46,"advance":-0.091796875},{"unicode1":86,"unicode2":58,"advance":-0.037109375},{"unicode1":86,"unicode2":59,"advance":-0.037109375},{"unicode1":86,"unicode2":65,"advance":-0.07421875},{"unicode1":86,"unicode2":97,"advance":-0.07421875},{"unicode1":86,"unicode2":101,"advance":-0.05517578125},{"unicode1":86,"unicode2":105,"advance":-0.01806640625},{"unicode1":86,"unicode2":111,"advance":-0.05517578125},{"unicode1":86,"unicode2":114,"advance":-0.037109375},{"unicode1":86,"unicode2":117,"advance":-0.037109375},{"unicode1":86,"unicode2":121,"advance":-0.037109375},{"unicode1":87,"unicode2":44,"advance":-0.05517578125},{"unicode1":87,"unicode2":45,"advance":-0.01806640625},{"unicode1":87,"unicode2":46,"advance":-0.05517578125},{"unicode1":87,"unicode2":58,"advance":-0.01806640625},{"unicode1":87,"unicode2":59,"advance":-0.01806640625},{"unicode1":87,"unicode2":65,"advance":-0.037109375},{"unicode1":87,"unicode2":97,"advance":-0.037109375},{"unicode1":87,"unicode2":101,"advance":-0.01806640625},{"unicode1":87,"unicode2":111,"advance":-0.01806640625},{"unicode1":87,"unicode2":114,"advance":-0.01806640625},{"unicode1":87,"unicode2":117,"advance":-0.01806640625},{"unicode1":87,"unicode2":121,"advance":-0.0087890625},{"unicode1":89,"unicode2":32,"advance":-0.01806640625},{"unicode1":89,"unicode2":44,"advance":-0.12890625},{"unicode1":89,"unicode2":45,"advance":-0.091796875},{"unicode1":89,"unicode2":46,"advance":-0.12890625},{"unicode1":89,"unicode2":58,"advance":-0.05517578125},{"unicode1":89,"unicode2":59,"advance":-0.06494140625},{"unicode1":89,"unicode2":65,"advance":-0.07421875},{"unicode1":89,"unicode2":97,"advance":-0.07421875},{"unicode1":89,"unicode2":101,"advance":-0.091796875},{"unicode1":89,"unicode2":105,"advance":-0.037109375},{"unicode1":89,"unicode2":111,"advance":-0.091796875},{"unicode1":89,"unicode2":112,"advance":-0.07421875},{"unicode1":89,"unicode2":113,"advance":-0.091796875},{"unicode1":89,"unicode2":117,"advance":-0.05517578125},{"unicode1":89,"unicode2":118,"advance":-0.05517578125},{"unicode1":102,"unicode2":102,"advance":-0.01806640625},{"unicode1":114,"unicode2":44,"advance":-0.05517578125},{"unicode1":114,"unicode2":46,"advance":-0.05517578125},{"unicode1":118,"unicode2":44,"advance":-0.07421875},{"unicode1":118,"unicode2":46,"advance":-0.07421875},{"unicode1":119,"unicode2":44,"advance":-0.05517578125},{"unicode1":119,"unicode2":46,"advance":-0.05517578125},{"unicode1":121,"unicode2":44,"advance":-0.07421875},{"unicode1":121,"unicode2":46,"advance":-0.07421875}]} diff --git a/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.png b/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.png new file mode 100644 index 0000000000000000000000000000000000000000..86cdf043bca6f39ada3159cddb05291e7c107a49 GIT binary patch literal 79558 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�rucop`$<3959s&9CNEd0+#%ab5G8eLYR<|!+ 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 Date: Fri, 7 Aug 2026 22:46:48 +0000 Subject: [PATCH 3/5] docs(text): swap the demo's bundled font from Liberation Sans to Open Sans Liberation Sans is SIL OFL 1.1, which raised concerns about how the modification/redistribution terms read even though OFL is designed to be embed-friendly. Open Sans (Apache License 2.0, full text bundled) has no font-specific license terms at all - just the same permissive terms as any other Apache-2.0 software - so it sidesteps the question entirely. Regenerated the MSDF atlas from the real msdf-atlas-gen toolchain against OpenSans-Regular.ttf and re-verified end-to-end (build, typecheck, and a real browser load confirming crisp rendering and the live counter still ticking). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq --- documentation-site/docs/docs/text/index.md | 2 +- .../docs/docs/text/msdf-text.md | 4 +- .../src/pages/demos/text/_create-game.ts | 2 +- .../src/pages/demos/text/index.tsx | 10 +- .../static/fonts/liberation-sans/OFL.txt | 96 --------- .../liberation-sans/liberation-sans-msdf.json | 1 - .../liberation-sans/liberation-sans-msdf.png | Bin 79558 -> 0 bytes .../static/fonts/open-sans/LICENSE.txt | 202 ++++++++++++++++++ .../{liberation-sans => open-sans}/README.md | 14 +- .../fonts/open-sans/open-sans-msdf.json | 1 + .../static/fonts/open-sans/open-sans-msdf.png | Bin 0 -> 79506 bytes 11 files changed, 217 insertions(+), 115 deletions(-) delete mode 100644 documentation-site/static/fonts/liberation-sans/OFL.txt delete mode 100644 documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json delete mode 100644 documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.png create mode 100644 documentation-site/static/fonts/open-sans/LICENSE.txt rename documentation-site/static/fonts/{liberation-sans => open-sans}/README.md (51%) create mode 100644 documentation-site/static/fonts/open-sans/open-sans-msdf.json create mode 100644 documentation-site/static/fonts/open-sans/open-sans-msdf.png diff --git a/documentation-site/docs/docs/text/index.md b/documentation-site/docs/docs/text/index.md index 931a36f6..08538d32 100644 --- a/documentation-site/docs/docs/text/index.md +++ b/documentation-site/docs/docs/text/index.md @@ -32,7 +32,7 @@ Three pieces work together: the render system draws. Try it in the [Text demo](/Forge/demos/text), which renders word-wrapped, -aligned paragraphs from a real generated Liberation Sans MSDF atlas, +aligned paragraphs from a real generated Open Sans MSDF atlas, including a live per-frame-updated label showing the shaping system's dirty tracking in action. diff --git a/documentation-site/docs/docs/text/msdf-text.md b/documentation-site/docs/docs/text/msdf-text.md index 9d9da23e..49c19968 100644 --- a/documentation-site/docs/docs/text/msdf-text.md +++ b/documentation-site/docs/docs/text/msdf-text.md @@ -18,8 +18,8 @@ msdf-atlas-gen -font Roboto-Regular.ttf -type msdf -format png \ ``` (The [Text demo](/Forge/demos/text)'s own atlas - -`documentation-site/static/fonts/liberation-sans/` - was generated the same -way, from Liberation Sans; see the `README.md` alongside it for the exact +`documentation-site/static/fonts/open-sans/` - was generated the same +way, from Open Sans; see the `README.md` alongside it for the exact command used.) Host the resulting `.json` and `.png` as static assets (e.g. next to your diff --git a/documentation-site/src/pages/demos/text/_create-game.ts b/documentation-site/src/pages/demos/text/_create-game.ts index 4bd847bb..8f7e4c8f 100644 --- a/documentation-site/src/pages/demos/text/_create-game.ts +++ b/documentation-site/src/pages/demos/text/_create-game.ts @@ -17,7 +17,7 @@ const renderLayers = { }; /** - * Builds the Text demo: loads a Liberation Sans MSDF font atlas, builds its + * Builds the Text demo: loads an Open Sans MSDF font atlas, builds its * shared renderable, and lays out a few showcase labels (see * `_create-labels.ts`) - a title/byline, three word-wrapped paragraphs * (one per alignment), and a live elapsed-time counter. diff --git a/documentation-site/src/pages/demos/text/index.tsx b/documentation-site/src/pages/demos/text/index.tsx index 2202057e..b08f67cc 100644 --- a/documentation-site/src/pages/demos/text/index.tsx +++ b/documentation-site/src/pages/demos/text/index.tsx @@ -9,12 +9,8 @@ import elapsedCounterSystemCode from '!!raw-loader!./_elapsed-counter.system'; import { Demo } from '@site/src/components/Demo'; export default function Text(): JSX.Element { - const fontJsonUrl = useBaseUrl( - 'fonts/liberation-sans/liberation-sans-msdf.json', - ); - const fontPngUrl = useBaseUrl( - 'fonts/liberation-sans/liberation-sans-msdf.png', - ); + const fontJsonUrl = useBaseUrl('fonts/open-sans/open-sans-msdf.json'); + const fontPngUrl = useBaseUrl('fonts/open-sans/open-sans-msdf.png'); return ( createTextGame(fontJsonUrl, fontPngUrl)} codeFiles={[ { name: 'game.ts', content: gameCode }, diff --git a/documentation-site/static/fonts/liberation-sans/OFL.txt b/documentation-site/static/fonts/liberation-sans/OFL.txt deleted file mode 100644 index 874204f6..00000000 --- a/documentation-site/static/fonts/liberation-sans/OFL.txt +++ /dev/null @@ -1,96 +0,0 @@ -This Font Software is licensed under the SIL Open Font License, -Version 1.1. - -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 - -PREAMBLE The goals of the Open Font License (OFL) are to stimulate -worldwide development of collaborative font projects, to support the font -creation efforts of academic and linguistic communities, and to provide -a free and open framework in which fonts may be shared and improved in -partnership with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. -The fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply to -any document created using the fonts or their derivatives. - - - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. -This may include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components -as distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting ? in part or in whole ? -any of the components of the Original Version, by changing formats or -by porting the Font Software to a new environment. - -"Author" refers to any designer, engineer, programmer, technical writer -or other person who contributed to the Font Software. - - -PERMISSION & CONDITIONS - -Permission is hereby granted, free of charge, to any person obtaining a -copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components,in - Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, - redistributed and/or sold with any software, provided that each copy - contains the above copyright notice and this license. These can be - included either as stand-alone text files, human-readable headers or - in the appropriate machine-readable metadata fields within text or - binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font - Name(s) unless explicit written permission is granted by the - corresponding Copyright Holder. This restriction only applies to the - primary font name as presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font - Software shall not be used to promote, endorse or advertise any - Modified Version, except to acknowledge the contribution(s) of the - Copyright Holder(s) and the Author(s) or with their explicit written - permission. - -5) The Font Software, modified or unmodified, in part or in whole, must - be distributed entirely under this license, and must not be distributed - under any other license. The requirement for fonts to remain under - this license does not apply to any document created using the Font - Software. - - - -TERMINATION -This license becomes null and void if any of the above conditions are not met. - - - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER -DEALINGS IN THE FONT SOFTWARE. diff --git a/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json b/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json deleted file mode 100644 index 1261ef42..00000000 --- a/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.json +++ /dev/null @@ -1 +0,0 @@ -{"atlas":{"type":"msdf","distanceRange":4,"distanceRangeMiddle":0,"size":48,"width":316,"height":316,"yOrigin":"bottom"},"metrics":{"emSize":1,"lineHeight":1.14990234375,"ascender":0.9052734375,"descender":-0.2119140625,"underlineY":-0.0693359375,"underlineThickness":0.0732421875},"glyphs":[{"unicode":32,"advance":0.27783203125},{"unicode":33,"advance":0.27783203125,"planeBounds":{"left":0.044921875,"bottom":-0.052083333333333336,"right":0.232421875,"top":0.73958333333333326},"atlasBounds":{"left":33.5,"bottom":144.5,"right":42.5,"top":182.5}},{"unicode":34,"advance":0.35498046875,"planeBounds":{"left":0.00040690104166666782,"bottom":0.42708333333333337,"right":0.35457356770833331,"top":0.73958333333333326},"atlasBounds":{"left":169.5,"bottom":17.5,"right":186.5,"top":32.5}},{"unicode":35,"advance":0.55615234375,"planeBounds":{"left":-0.045084635416666706,"bottom":-0.052083333333333336,"right":0.60074869791666663,"top":0.73958333333333326},"atlasBounds":{"left":230.5,"bottom":144.5,"right":261.5,"top":182.5}},{"unicode":36,"advance":0.55615234375,"planeBounds":{"left":-0.037109375000000035,"bottom":-0.11458333333333333,"right":0.58789062499999989,"top":0.80208333333333326},"atlasBounds":{"left":213.5,"bottom":271.5,"right":243.5,"top":315.5}},{"unicode":37,"advance":0.88916015625,"planeBounds":{"left":-0.01375325520833333,"bottom":-0.052083333333333336,"right":0.90291341145833326,"top":0.73958333333333326},"atlasBounds":{"left":271.5,"bottom":277.5,"right":315.5,"top":315.5}},{"unicode":38,"advance":0.6669921875,"planeBounds":{"left":-0.010904947916666663,"bottom":-0.052083333333333336,"right":0.69742838541666663,"top":0.73958333333333326},"atlasBounds":{"left":0.5,"bottom":65.5,"right":34.5,"top":103.5}},{"unicode":39,"advance":0.19091796875,"planeBounds":{"left":0.0019531250000000017,"bottom":0.42708333333333337,"right":0.189453125,"top":0.73958333333333326},"atlasBounds":{"left":303.5,"bottom":226.5,"right":312.5,"top":241.5}},{"unicode":40,"advance":0.3330078125,"planeBounds":{"left":0.017496744791666668,"bottom":-0.26041666666666663,"right":0.37166341145833331,"top":0.78125},"atlasBounds":{"left":0.5,"bottom":265.5,"right":17.5,"top":315.5}},{"unicode":41,"advance":0.3330078125,"planeBounds":{"left":-0.038655598958333336,"bottom":-0.26041666666666663,"right":0.31551106770833331,"top":0.78125},"atlasBounds":{"left":18.5,"bottom":265.5,"right":35.5,"top":315.5}},{"unicode":42,"advance":0.38916015625,"planeBounds":{"left":-0.034342447916666664,"bottom":0.28125,"right":0.42399088541666669,"top":0.73958333333333326},"atlasBounds":{"left":293.5,"bottom":242.5,"right":315.5,"top":264.5}},{"unicode":43,"advance":0.583984375,"planeBounds":{"left":8.1380208333299217e-05,"bottom":0.03125,"right":0.58341471354166663,"top":0.63541666666666663},"atlasBounds":{"left":54.5,"bottom":3.5,"right":82.5,"top":32.5}},{"unicode":44,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.17708333333333334,"right":0.232666015625,"top":0.15625},"atlasBounds":{"left":293.5,"bottom":225.5,"right":302.5,"top":241.5}},{"unicode":45,"advance":0.3330078125,"planeBounds":{"left":-0.00016276041666664701,"bottom":0.17708333333333331,"right":0.33317057291666669,"top":0.36458333333333331},"atlasBounds":{"left":241.5,"bottom":23.5,"right":257.5,"top":32.5}},{"unicode":46,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.052083333333333336,"right":0.232666015625,"top":0.15625},"atlasBounds":{"left":231.5,"bottom":22.5,"right":240.5,"top":32.5}},{"unicode":47,"advance":0.27783203125,"planeBounds":{"left":-0.048583984375,"bottom":-0.052083333333333336,"right":0.32641601562499994,"top":0.78125},"atlasBounds":{"left":54.5,"bottom":224.5,"right":72.5,"top":264.5}},{"unicode":48,"advance":0.55615234375,"planeBounds":{"left":-0.0031738281250000347,"bottom":-0.052083333333333336,"right":0.55932617187499989,"top":0.76041666666666663},"atlasBounds":{"left":265.5,"bottom":225.5,"right":292.5,"top":264.5}},{"unicode":49,"advance":0.55615234375,"planeBounds":{"left":0.031331380208333294,"bottom":-0.052083333333333336,"right":0.55216471354166663,"top":0.73958333333333326},"atlasBounds":{"left":103.5,"bottom":65.5,"right":128.5,"top":103.5}},{"unicode":50,"advance":0.55615234375,"planeBounds":{"left":0.0072428385416666314,"bottom":-0.052083333333333336,"right":0.54890950520833326,"top":0.76041666666666663},"atlasBounds":{"left":0.5,"bottom":183.5,"right":26.5,"top":222.5}},{"unicode":51,"advance":0.55615234375,"planeBounds":{"left":-0.006103515625000033,"bottom":-0.052083333333333336,"right":0.55639648437499989,"top":0.76041666666666663},"atlasBounds":{"left":27.5,"bottom":183.5,"right":54.5,"top":222.5}},{"unicode":52,"advance":0.55615234375,"planeBounds":{"left":-0.02718098958333337,"bottom":-0.052083333333333336,"right":0.57698567708333326,"top":0.73958333333333326},"atlasBounds":{"left":129.5,"bottom":65.5,"right":158.5,"top":103.5}},{"unicode":53,"advance":0.55615234375,"planeBounds":{"left":-0.0041503906250000347,"bottom":-0.052083333333333336,"right":0.55834960937499989,"top":0.73958333333333326},"atlasBounds":{"left":159.5,"bottom":65.5,"right":186.5,"top":103.5}},{"unicode":54,"advance":0.55615234375,"planeBounds":{"left":0.00024414062499996357,"bottom":-0.052083333333333336,"right":0.56274414062499989,"top":0.76041666666666663},"atlasBounds":{"left":55.5,"bottom":183.5,"right":82.5,"top":222.5}},{"unicode":55,"advance":0.55615234375,"planeBounds":{"left":0.0077311197916666314,"bottom":-0.052083333333333336,"right":0.54939778645833326,"top":0.73958333333333326},"atlasBounds":{"left":187.5,"bottom":65.5,"right":213.5,"top":103.5}},{"unicode":56,"advance":0.55615234375,"planeBounds":{"left":-0.0031738281250000347,"bottom":-0.052083333333333336,"right":0.55932617187499989,"top":0.76041666666666663},"atlasBounds":{"left":83.5,"bottom":183.5,"right":110.5,"top":222.5}},{"unicode":57,"advance":0.55615234375,"planeBounds":{"left":-0.0034179687500000364,"bottom":-0.052083333333333336,"right":0.55908203124999989,"top":0.76041666666666663},"atlasBounds":{"left":111.5,"bottom":183.5,"right":138.5,"top":222.5}},{"unicode":58,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.052083333333333336,"right":0.232666015625,"top":0.57291666666666663},"atlasBounds":{"left":234.5,"bottom":34.5,"right":243.5,"top":64.5}},{"unicode":59,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.17708333333333334,"right":0.232666015625,"top":0.57291666666666663},"atlasBounds":{"left":303.5,"bottom":146.5,"right":312.5,"top":182.5}},{"unicode":60,"advance":0.583984375,"planeBounds":{"left":0.00056966145833329922,"bottom":0.03125,"right":0.58390299479166663,"top":0.63541666666666663},"atlasBounds":{"left":83.5,"bottom":3.5,"right":111.5,"top":32.5}},{"unicode":61,"advance":0.583984375,"planeBounds":{"left":8.1380208333299217e-05,"bottom":0.11458333333333334,"right":0.58341471354166663,"top":0.55208333333333326},"atlasBounds":{"left":140.5,"bottom":11.5,"right":168.5,"top":32.5}},{"unicode":62,"advance":0.583984375,"planeBounds":{"left":0.00056966145833329922,"bottom":0.03125,"right":0.58390299479166663,"top":0.63541666666666663},"atlasBounds":{"left":25.5,"bottom":3.5,"right":53.5,"top":32.5}},{"unicode":63,"advance":0.55615234375,"planeBounds":{"left":-0.0012207031250000347,"bottom":-0.052083333333333336,"right":0.56127929687499989,"top":0.76041666666666663},"atlasBounds":{"left":139.5,"bottom":183.5,"right":166.5,"top":222.5}},{"unicode":64,"advance":1.01513671875,"planeBounds":{"left":0.034912109375,"bottom":-0.19791666666666669,"right":0.972412109375,"top":0.78124999999999989},"atlasBounds":{"left":167.5,"bottom":268.5,"right":212.5,"top":315.5}},{"unicode":65,"advance":0.6669921875,"planeBounds":{"left":-0.04150390625,"bottom":-0.052083333333333336,"right":0.70849609375,"top":0.73958333333333326},"atlasBounds":{"left":66.5,"bottom":65.5,"right":102.5,"top":103.5}},{"unicode":66,"advance":0.6669921875,"planeBounds":{"left":0.035644531249999965,"bottom":-0.052083333333333336,"right":0.66064453124999989,"top":0.73958333333333326},"atlasBounds":{"left":35.5,"bottom":65.5,"right":65.5,"top":103.5}},{"unicode":67,"advance":0.72216796875,"planeBounds":{"left":0.0028483072916666696,"bottom":-0.052083333333333336,"right":0.73201497395833326,"top":0.76041666666666663},"atlasBounds":{"left":167.5,"bottom":183.5,"right":202.5,"top":222.5}},{"unicode":68,"advance":0.72216796875,"planeBounds":{"left":0.034423828125,"bottom":-0.052083333333333336,"right":0.721923828125,"top":0.73958333333333326},"atlasBounds":{"left":277.5,"bottom":104.5,"right":310.5,"top":142.5}},{"unicode":69,"advance":0.6669921875,"planeBounds":{"left":0.030110677083333301,"bottom":-0.052083333333333336,"right":0.67594401041666663,"top":0.73958333333333326},"atlasBounds":{"left":245.5,"bottom":104.5,"right":276.5,"top":142.5}},{"unicode":70,"advance":0.61083984375,"planeBounds":{"left":0.034749348958333301,"bottom":-0.052083333333333336,"right":0.61808268229166663,"top":0.73958333333333326},"atlasBounds":{"left":216.5,"bottom":104.5,"right":244.5,"top":142.5}},{"unicode":71,"advance":0.77783203125,"planeBounds":{"left":0.0017089843750000017,"bottom":-0.052083333333333336,"right":0.751708984375,"top":0.76041666666666663},"atlasBounds":{"left":203.5,"bottom":183.5,"right":239.5,"top":222.5}},{"unicode":72,"advance":0.72216796875,"planeBounds":{"left":0.038411458333333294,"bottom":-0.052083333333333336,"right":0.68424479166666663,"top":0.73958333333333326},"atlasBounds":{"left":152.5,"bottom":104.5,"right":183.5,"top":142.5}},{"unicode":73,"advance":0.27783203125,"planeBounds":{"left":0.045166015625,"bottom":-0.052083333333333336,"right":0.232666015625,"top":0.73958333333333326},"atlasBounds":{"left":293.5,"bottom":144.5,"right":302.5,"top":182.5}},{"unicode":74,"advance":0.5,"planeBounds":{"left":-0.029296875,"bottom":-0.052083333333333336,"right":0.47070312499999994,"top":0.73958333333333326},"atlasBounds":{"left":95.5,"bottom":104.5,"right":119.5,"top":142.5}},{"unicode":75,"advance":0.6669921875,"planeBounds":{"left":0.03556315104166663,"bottom":-0.052083333333333336,"right":0.70222981770833326,"top":0.73958333333333326},"atlasBounds":{"left":62.5,"bottom":104.5,"right":94.5,"top":142.5}},{"unicode":76,"advance":0.55615234375,"planeBounds":{"left":0.03165690104166663,"bottom":-0.052083333333333336,"right":0.57332356770833326,"top":0.73958333333333326},"atlasBounds":{"left":35.5,"bottom":104.5,"right":61.5,"top":142.5}},{"unicode":77,"advance":0.8330078125,"planeBounds":{"left":0.031087239583333336,"bottom":-0.052083333333333336,"right":0.80192057291666663,"top":0.73958333333333326},"atlasBounds":{"left":278.5,"bottom":184.5,"right":315.5,"top":222.5}},{"unicode":78,"advance":0.72216796875,"planeBounds":{"left":0.038411458333333294,"bottom":-0.052083333333333336,"right":0.68424479166666663,"top":0.73958333333333326},"atlasBounds":{"left":120.5,"bottom":104.5,"right":151.5,"top":142.5}},{"unicode":79,"advance":0.77783203125,"planeBounds":{"left":0.0032552083333333356,"bottom":-0.052083333333333336,"right":0.77408854166666663,"top":0.76041666666666663},"atlasBounds":{"left":240.5,"bottom":183.5,"right":277.5,"top":222.5}},{"unicode":80,"advance":0.6669921875,"planeBounds":{"left":0.035644531249999965,"bottom":-0.052083333333333336,"right":0.66064453124999989,"top":0.73958333333333326},"atlasBounds":{"left":262.5,"bottom":144.5,"right":292.5,"top":182.5}},{"unicode":81,"advance":0.77783203125,"planeBounds":{"left":0.0032552083333333356,"bottom":-0.23958333333333334,"right":0.77408854166666663,"top":0.76041666666666652},"atlasBounds":{"left":129.5,"bottom":267.5,"right":166.5,"top":315.5}},{"unicode":82,"advance":0.72216796875,"planeBounds":{"left":0.03515625,"bottom":-0.052083333333333336,"right":0.72265625,"top":0.73958333333333326},"atlasBounds":{"left":196.5,"bottom":144.5,"right":229.5,"top":182.5}},{"unicode":83,"advance":0.6669921875,"planeBounds":{"left":-8.1380208333368606e-05,"bottom":-0.052083333333333336,"right":0.66658528645833326,"top":0.76041666666666663},"atlasBounds":{"left":0.5,"bottom":143.5,"right":32.5,"top":182.5}},{"unicode":84,"advance":0.61083984375,"planeBounds":{"left":-0.02815755208333337,"bottom":-0.052083333333333336,"right":0.63850911458333326,"top":0.73958333333333326},"atlasBounds":{"left":113.5,"bottom":144.5,"right":145.5,"top":182.5}},{"unicode":85,"advance":0.72216796875,"planeBounds":{"left":0.02775065104166663,"bottom":-0.052083333333333336,"right":0.69441731770833326,"top":0.73958333333333326},"atlasBounds":{"left":80.5,"bottom":144.5,"right":112.5,"top":182.5}},{"unicode":86,"advance":0.6669921875,"planeBounds":{"left":-0.04150390625,"bottom":-0.052083333333333336,"right":0.70849609375,"top":0.73958333333333326},"atlasBounds":{"left":43.5,"bottom":144.5,"right":79.5,"top":182.5}},{"unicode":87,"advance":0.94384765625,"planeBounds":{"left":-0.038004557291666664,"bottom":-0.052083333333333336,"right":0.98282877604166663,"top":0.73958333333333326},"atlasBounds":{"left":146.5,"bottom":144.5,"right":195.5,"top":182.5}},{"unicode":88,"advance":0.6669921875,"planeBounds":{"left":-0.019938151041666664,"bottom":-0.052083333333333336,"right":0.68839518229166663,"top":0.73958333333333326},"atlasBounds":{"left":214.5,"bottom":65.5,"right":248.5,"top":103.5}},{"unicode":89,"advance":0.6669921875,"planeBounds":{"left":-0.020670572916666664,"bottom":-0.052083333333333336,"right":0.68766276041666663,"top":0.73958333333333326},"atlasBounds":{"left":0.5,"bottom":104.5,"right":34.5,"top":142.5}},{"unicode":90,"advance":0.61083984375,"planeBounds":{"left":-0.017252604166666699,"bottom":-0.052083333333333336,"right":0.62858072916666663,"top":0.73958333333333326},"atlasBounds":{"left":184.5,"bottom":104.5,"right":215.5,"top":142.5}},{"unicode":91,"advance":0.27783203125,"planeBounds":{"left":0.024820963541666685,"bottom":-0.26041666666666663,"right":0.31648763020833331,"top":0.78125},"atlasBounds":{"left":85.5,"bottom":265.5,"right":99.5,"top":315.5}},{"unicode":92,"advance":0.27783203125,"planeBounds":{"left":-0.048583984375,"bottom":-0.052083333333333336,"right":0.32641601562499994,"top":0.78125},"atlasBounds":{"left":102.5,"bottom":224.5,"right":120.5,"top":264.5}},{"unicode":93,"advance":0.27783203125,"planeBounds":{"left":-0.038655598958333336,"bottom":-0.26041666666666663,"right":0.25301106770833331,"top":0.78125},"atlasBounds":{"left":100.5,"bottom":265.5,"right":114.5,"top":315.5}},{"unicode":94,"advance":0.46923828125,"planeBounds":{"left":-0.046630859375000035,"bottom":0.28125,"right":0.51586914062499989,"top":0.73958333333333326},"atlasBounds":{"left":112.5,"bottom":10.5,"right":139.5,"top":32.5}},{"unicode":95,"advance":0.55615234375,"planeBounds":{"left":-0.057210286458333363,"bottom":-0.26041666666666663,"right":0.60945638020833326,"top":-0.072916666666666657},"atlasBounds":{"left":258.5,"bottom":23.5,"right":290.5,"top":32.5}},{"unicode":96,"advance":0.3330078125,"planeBounds":{"left":0.0094401041666666869,"bottom":0.53125,"right":0.30110677083333331,"top":0.78125},"atlasBounds":{"left":187.5,"bottom":20.5,"right":201.5,"top":32.5}},{"unicode":97,"advance":0.55615234375,"planeBounds":{"left":-0.0027669270833333686,"bottom":-0.052083333333333336,"right":0.60139973958333326,"top":0.59375},"atlasBounds":{"left":147.5,"bottom":33.5,"right":176.5,"top":64.5}},{"unicode":98,"advance":0.55615234375,"planeBounds":{"left":0.01847330729166663,"bottom":-0.052083333333333336,"right":0.56013997395833326,"top":0.78125},"atlasBounds":{"left":121.5,"bottom":224.5,"right":147.5,"top":264.5}},{"unicode":99,"advance":0.5,"planeBounds":{"left":-0.0023600260416667025,"bottom":-0.052083333333333336,"right":0.51847330729166663,"top":0.59375},"atlasBounds":{"left":26.5,"bottom":33.5,"right":51.5,"top":64.5}},{"unicode":100,"advance":0.55615234375,"planeBounds":{"left":-0.0039876302083333686,"bottom":-0.052083333333333336,"right":0.53767903645833326,"top":0.78125},"atlasBounds":{"left":184.5,"bottom":224.5,"right":210.5,"top":264.5}},{"unicode":101,"advance":0.55615234375,"planeBounds":{"left":-0.0041503906250000347,"bottom":-0.052083333333333336,"right":0.55834960937499989,"top":0.59375},"atlasBounds":{"left":80.5,"bottom":33.5,"right":107.5,"top":64.5}},{"unicode":102,"advance":0.27783203125,"planeBounds":{"left":-0.030354817708333336,"bottom":-0.052083333333333336,"right":0.32381184895833331,"top":0.78125},"atlasBounds":{"left":247.5,"bottom":224.5,"right":264.5,"top":264.5}},{"unicode":103,"advance":0.55615234375,"planeBounds":{"left":-0.0039876302083333686,"bottom":-0.26041666666666663,"right":0.53767903645833326,"top":0.59375},"atlasBounds":{"left":244.5,"bottom":274.5,"right":270.5,"top":315.5}},{"unicode":104,"advance":0.55615234375,"planeBounds":{"left":0.019856770833333301,"bottom":-0.052083333333333336,"right":0.54069010416666663,"top":0.78125},"atlasBounds":{"left":221.5,"bottom":224.5,"right":246.5,"top":264.5}},{"unicode":105,"advance":0.22216796875,"planeBounds":{"left":0.01708984375,"bottom":-0.052083333333333336,"right":0.20458984375,"top":0.78125},"atlasBounds":{"left":211.5,"bottom":224.5,"right":220.5,"top":264.5}},{"unicode":106,"advance":0.22216796875,"planeBounds":{"left":-0.070231119791666657,"bottom":-0.26041666666666663,"right":0.20060221354166669,"top":0.78125},"atlasBounds":{"left":115.5,"bottom":265.5,"right":128.5,"top":315.5}},{"unicode":107,"advance":0.5,"planeBounds":{"left":0.024007161458333297,"bottom":-0.052083333333333336,"right":0.54484049479166663,"top":0.78125},"atlasBounds":{"left":158.5,"bottom":224.5,"right":183.5,"top":264.5}},{"unicode":108,"advance":0.22216796875,"planeBounds":{"left":0.017578125,"bottom":-0.052083333333333336,"right":0.205078125,"top":0.78125},"atlasBounds":{"left":148.5,"bottom":224.5,"right":157.5,"top":264.5}},{"unicode":109,"advance":0.8330078125,"planeBounds":{"left":0.020914713541666668,"bottom":-0.052083333333333336,"right":0.81258138020833326,"top":0.59375},"atlasBounds":{"left":108.5,"bottom":33.5,"right":146.5,"top":64.5}},{"unicode":110,"advance":0.55615234375,"planeBounds":{"left":0.018391927083333301,"bottom":-0.052083333333333336,"right":0.53922526041666663,"top":0.59375},"atlasBounds":{"left":267.5,"bottom":72.5,"right":292.5,"top":103.5}},{"unicode":111,"advance":0.55615234375,"planeBounds":{"left":-0.0031738281250000347,"bottom":-0.052083333333333336,"right":0.55932617187499989,"top":0.59375},"atlasBounds":{"left":52.5,"bottom":33.5,"right":79.5,"top":64.5}},{"unicode":112,"advance":0.55615234375,"planeBounds":{"left":0.01847330729166663,"bottom":-0.26041666666666663,"right":0.56013997395833326,"top":0.59375},"atlasBounds":{"left":0.5,"bottom":223.5,"right":26.5,"top":264.5}},{"unicode":113,"advance":0.55615234375,"planeBounds":{"left":-0.0037434895833333686,"bottom":-0.26041666666666663,"right":0.53792317708333326,"top":0.59375},"atlasBounds":{"left":27.5,"bottom":223.5,"right":53.5,"top":264.5}},{"unicode":114,"advance":0.3330078125,"planeBounds":{"left":0.024739583333333336,"bottom":-0.052083333333333336,"right":0.35807291666666669,"top":0.59375},"atlasBounds":{"left":293.5,"bottom":72.5,"right":309.5,"top":103.5}},{"unicode":115,"advance":0.5,"planeBounds":{"left":-0.014567057291666704,"bottom":-0.052083333333333336,"right":0.50626627604166663,"top":0.59375},"atlasBounds":{"left":0.5,"bottom":33.5,"right":25.5,"top":64.5}},{"unicode":116,"advance":0.27783203125,"planeBounds":{"left":-0.034261067708333336,"bottom":-0.052083333333333336,"right":0.31990559895833331,"top":0.69791666666666663},"atlasBounds":{"left":249.5,"bottom":67.5,"right":266.5,"top":103.5}},{"unicode":117,"advance":0.55615234375,"planeBounds":{"left":0.016927083333333301,"bottom":-0.052083333333333336,"right":0.53776041666666663,"top":0.57291666666666663},"atlasBounds":{"left":244.5,"bottom":34.5,"right":269.5,"top":64.5}},{"unicode":118,"advance":0.5,"planeBounds":{"left":-0.041666666666666706,"bottom":-0.052083333333333336,"right":0.54166666666666663,"top":0.57291666666666663},"atlasBounds":{"left":177.5,"bottom":34.5,"right":205.5,"top":64.5}},{"unicode":119,"advance":0.72216796875,"planeBounds":{"left":-0.044677734375,"bottom":-0.052083333333333336,"right":0.767822265625,"top":0.57291666666666663},"atlasBounds":{"left":270.5,"bottom":34.5,"right":309.5,"top":64.5}},{"unicode":120,"advance":0.5,"planeBounds":{"left":-0.031005859375000035,"bottom":-0.052083333333333336,"right":0.53149414062499989,"top":0.57291666666666663},"atlasBounds":{"left":206.5,"bottom":34.5,"right":233.5,"top":64.5}},{"unicode":121,"advance":0.5,"planeBounds":{"left":-0.041422526041666706,"bottom":-0.26041666666666663,"right":0.54191080729166663,"top":0.57291666666666674},"atlasBounds":{"left":73.5,"bottom":224.5,"right":101.5,"top":264.5}},{"unicode":122,"advance":0.5,"planeBounds":{"left":-0.0046386718749999983,"bottom":-0.052083333333333336,"right":0.49536132812499994,"top":0.57291666666666663},"atlasBounds":{"left":0.5,"bottom":2.5,"right":24.5,"top":32.5}},{"unicode":123,"advance":0.333984375,"planeBounds":{"left":-0.031412760416666664,"bottom":-0.26041666666666663,"right":0.36442057291666669,"top":0.78125},"atlasBounds":{"left":65.5,"bottom":265.5,"right":84.5,"top":315.5}},{"unicode":124,"advance":0.259765625,"planeBounds":{"left":0.046549479166666657,"bottom":-0.26041666666666663,"right":0.21321614583333331,"top":0.78125},"atlasBounds":{"left":56.5,"bottom":265.5,"right":64.5,"top":315.5}},{"unicode":125,"advance":0.333984375,"planeBounds":{"left":-0.031656901041666664,"bottom":-0.26041666666666663,"right":0.36417643229166669,"top":0.78125},"atlasBounds":{"left":36.5,"bottom":265.5,"right":55.5,"top":315.5}},{"unicode":126,"advance":0.583984375,"planeBounds":{"left":0.00032552083333329748,"bottom":0.21875,"right":0.58365885416666663,"top":0.44791666666666663},"atlasBounds":{"left":202.5,"bottom":21.5,"right":230.5,"top":32.5}}],"kerning":[{"unicode1":32,"unicode2":65,"advance":-0.05517578125},{"unicode1":32,"unicode2":84,"advance":-0.01806640625},{"unicode1":32,"unicode2":89,"advance":-0.01806640625},{"unicode1":49,"unicode2":49,"advance":-0.07421875},{"unicode1":65,"unicode2":32,"advance":-0.05517578125},{"unicode1":65,"unicode2":84,"advance":-0.07421875},{"unicode1":65,"unicode2":86,"advance":-0.07421875},{"unicode1":65,"unicode2":87,"advance":-0.037109375},{"unicode1":65,"unicode2":89,"advance":-0.07421875},{"unicode1":65,"unicode2":118,"advance":-0.01806640625},{"unicode1":65,"unicode2":119,"advance":-0.01806640625},{"unicode1":65,"unicode2":121,"advance":-0.01806640625},{"unicode1":70,"unicode2":44,"advance":-0.11083984375},{"unicode1":70,"unicode2":46,"advance":-0.11083984375},{"unicode1":70,"unicode2":65,"advance":-0.05517578125},{"unicode1":76,"unicode2":32,"advance":-0.037109375},{"unicode1":76,"unicode2":84,"advance":-0.07421875},{"unicode1":76,"unicode2":86,"advance":-0.07421875},{"unicode1":76,"unicode2":87,"advance":-0.07421875},{"unicode1":76,"unicode2":89,"advance":-0.07421875},{"unicode1":76,"unicode2":121,"advance":-0.037109375},{"unicode1":80,"unicode2":32,"advance":-0.01806640625},{"unicode1":80,"unicode2":44,"advance":-0.12890625},{"unicode1":80,"unicode2":46,"advance":-0.12890625},{"unicode1":80,"unicode2":65,"advance":-0.07421875},{"unicode1":82,"unicode2":84,"advance":-0.01806640625},{"unicode1":82,"unicode2":86,"advance":-0.01806640625},{"unicode1":82,"unicode2":87,"advance":-0.01806640625},{"unicode1":82,"unicode2":89,"advance":-0.01806640625},{"unicode1":84,"unicode2":32,"advance":-0.01806640625},{"unicode1":84,"unicode2":44,"advance":-0.11083984375},{"unicode1":84,"unicode2":45,"advance":-0.05517578125},{"unicode1":84,"unicode2":46,"advance":-0.11083984375},{"unicode1":84,"unicode2":58,"advance":-0.11083984375},{"unicode1":84,"unicode2":59,"advance":-0.11083984375},{"unicode1":84,"unicode2":65,"advance":-0.07421875},{"unicode1":84,"unicode2":79,"advance":-0.01806640625},{"unicode1":84,"unicode2":97,"advance":-0.11083984375},{"unicode1":84,"unicode2":99,"advance":-0.11083984375},{"unicode1":84,"unicode2":101,"advance":-0.11083984375},{"unicode1":84,"unicode2":105,"advance":-0.037109375},{"unicode1":84,"unicode2":111,"advance":-0.11083984375},{"unicode1":84,"unicode2":114,"advance":-0.037109375},{"unicode1":84,"unicode2":115,"advance":-0.11083984375},{"unicode1":84,"unicode2":117,"advance":-0.037109375},{"unicode1":84,"unicode2":119,"advance":-0.05517578125},{"unicode1":84,"unicode2":121,"advance":-0.05517578125},{"unicode1":86,"unicode2":44,"advance":-0.091796875},{"unicode1":86,"unicode2":45,"advance":-0.05517578125},{"unicode1":86,"unicode2":46,"advance":-0.091796875},{"unicode1":86,"unicode2":58,"advance":-0.037109375},{"unicode1":86,"unicode2":59,"advance":-0.037109375},{"unicode1":86,"unicode2":65,"advance":-0.07421875},{"unicode1":86,"unicode2":97,"advance":-0.07421875},{"unicode1":86,"unicode2":101,"advance":-0.05517578125},{"unicode1":86,"unicode2":105,"advance":-0.01806640625},{"unicode1":86,"unicode2":111,"advance":-0.05517578125},{"unicode1":86,"unicode2":114,"advance":-0.037109375},{"unicode1":86,"unicode2":117,"advance":-0.037109375},{"unicode1":86,"unicode2":121,"advance":-0.037109375},{"unicode1":87,"unicode2":44,"advance":-0.05517578125},{"unicode1":87,"unicode2":45,"advance":-0.01806640625},{"unicode1":87,"unicode2":46,"advance":-0.05517578125},{"unicode1":87,"unicode2":58,"advance":-0.01806640625},{"unicode1":87,"unicode2":59,"advance":-0.01806640625},{"unicode1":87,"unicode2":65,"advance":-0.037109375},{"unicode1":87,"unicode2":97,"advance":-0.037109375},{"unicode1":87,"unicode2":101,"advance":-0.01806640625},{"unicode1":87,"unicode2":111,"advance":-0.01806640625},{"unicode1":87,"unicode2":114,"advance":-0.01806640625},{"unicode1":87,"unicode2":117,"advance":-0.01806640625},{"unicode1":87,"unicode2":121,"advance":-0.0087890625},{"unicode1":89,"unicode2":32,"advance":-0.01806640625},{"unicode1":89,"unicode2":44,"advance":-0.12890625},{"unicode1":89,"unicode2":45,"advance":-0.091796875},{"unicode1":89,"unicode2":46,"advance":-0.12890625},{"unicode1":89,"unicode2":58,"advance":-0.05517578125},{"unicode1":89,"unicode2":59,"advance":-0.06494140625},{"unicode1":89,"unicode2":65,"advance":-0.07421875},{"unicode1":89,"unicode2":97,"advance":-0.07421875},{"unicode1":89,"unicode2":101,"advance":-0.091796875},{"unicode1":89,"unicode2":105,"advance":-0.037109375},{"unicode1":89,"unicode2":111,"advance":-0.091796875},{"unicode1":89,"unicode2":112,"advance":-0.07421875},{"unicode1":89,"unicode2":113,"advance":-0.091796875},{"unicode1":89,"unicode2":117,"advance":-0.05517578125},{"unicode1":89,"unicode2":118,"advance":-0.05517578125},{"unicode1":102,"unicode2":102,"advance":-0.01806640625},{"unicode1":114,"unicode2":44,"advance":-0.05517578125},{"unicode1":114,"unicode2":46,"advance":-0.05517578125},{"unicode1":118,"unicode2":44,"advance":-0.07421875},{"unicode1":118,"unicode2":46,"advance":-0.07421875},{"unicode1":119,"unicode2":44,"advance":-0.05517578125},{"unicode1":119,"unicode2":46,"advance":-0.05517578125},{"unicode1":121,"unicode2":44,"advance":-0.07421875},{"unicode1":121,"unicode2":46,"advance":-0.07421875}]} diff --git a/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.png b/documentation-site/static/fonts/liberation-sans/liberation-sans-msdf.png deleted file mode 100644 index 86cdf043bca6f39ada3159cddb05291e7c107a49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79558 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�rucop`$<3959s&9CNEd0+#%ab5G8eLYR<|!+ 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 Xi`X+ybv|z(00000NkvXXu0mjfRJxYZ literal 0 HcmV?d00001 From 21cf2f0d86f241b366a4310f0c06ff40e7d5d23b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 22:57:34 +0000 Subject: [PATCH 4/5] docs(text): rewrite text docs for game-author consumers, not maintainers The text docs were full of internal implementation detail a game author doesn't need to know: GitHub issue references, "under the hood" render- command internals, roadmap framing ("not yet implemented"), and a pointer into the repo's own demo asset paths. Docs are for consumers of the package, not people reading the engine's source. Rewrites both pages around what a consumer actually needs: a complete, runnable Quick Start, a full Options reference table for addTextComponent (every field, type, default, and what it does), and a short factual list of current constraints with no roadmap language. Trims the same kind of internals aside out of the rendering module's overview page. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq --- .cspell/project-words.txt | 1 + .../docs/docs/rendering/index.md | 6 +- documentation-site/docs/docs/text/index.md | 103 ++++++--------- .../docs/docs/text/msdf-text.md | 125 ++++++------------ 4 files changed, 87 insertions(+), 148 deletions(-) diff --git a/.cspell/project-words.txt b/.cspell/project-words.txt index 04cfea00..1b32d41e 100644 --- a/.cspell/project-words.txt +++ b/.cspell/project-words.txt @@ -64,6 +64,7 @@ MSDF Narkowicz ndot Nisbet +nokerning normalise Oboro perlin diff --git a/documentation-site/docs/docs/rendering/index.md b/documentation-site/docs/docs/rendering/index.md index 9e60be67..11f5152d 100644 --- a/documentation-site/docs/docs/rendering/index.md +++ b/documentation-site/docs/docs/rendering/index.md @@ -8,10 +8,8 @@ draws the sprites matching that camera's `cullingMask`, sorted by each sprite's `layer` (draw order, lower first) and then by depth (world Y position) within a layer, batching consecutive sprites that share a [`Renderable`](/Forge/docs/api/classes/Renderable) into a single draw call. -Shaped text (`@forge-game-engine/forge/text`) draws through this same path -- each glyph quad expands into a `SpriteEcsComponent`-shaped render command, -so a label sorts and batches with sprites exactly like a nine-sliced -panel's regions do; see [Text](../text/index.md). +Text (`@forge-game-engine/forge/text`) draws through this same pass, sorted +and batched alongside your sprites; see [Text](../text/index.md). This section is a work in progress and currently covers the multipass rendering foundation and its first post-processing effect; a full guide to diff --git a/documentation-site/docs/docs/text/index.md b/documentation-site/docs/docs/text/index.md index 08538d32..bf4b9f0f 100644 --- a/documentation-site/docs/docs/text/index.md +++ b/documentation-site/docs/docs/text/index.md @@ -1,45 +1,15 @@ # Text -`@forge-game-engine/forge/text` renders text using -[MSDF](https://github.com/Chlumsky/msdf-atlas-gen) (multi-channel signed -distance field) font atlases: a pre-generated texture plus a JSON metrics -file, sampled by a small fragment shader that reconstructs crisp glyph -edges at any scale. Unlike drawing text to a `` and uploading it as -a texture, MSDF text batches into the same instanced draw call as sprites -and never re-uploads a texture when the string changes - the glyph -quads it draws are ordinary `SpriteEcsComponent`-shaped render commands -under the hood, so a label interleaves correctly with sprites in the same -depth-sorted draw order (see -[`createRenderEcsSystem`](/Forge/docs/api/functions/createRenderEcsSystem)). - -Three pieces work together: - -- [`loadFontAtlas`](/Forge/docs/api/functions/loadFontAtlas) - (`/asset-loading`) parses an `msdf-atlas-gen` JSON metrics file and loads - its atlas PNG into a [`FontAtlas`](/Forge/docs/api/interfaces/FontAtlas) - - glyph advances, kerning pairs, and line metrics, all normalized to em - units. -- [`createMsdfTextRenderable`](/Forge/docs/api/functions/createMsdfTextRenderable) - (`/rendering`) builds the GPU-side `Renderable` (the MSDF shader bound to - the atlas texture) for a `FontAtlas`. Call this once per font, not once - per label - every entity sharing the same `Renderable` batches together. -- [`addTextComponent`](/Forge/docs/api/functions/addTextComponent) and - [`createTextShapingEcsSystem`](/Forge/docs/api/functions/createTextShapingEcsSystem) - (`/text`) turn a string into glyph quads: attach a - [`TextEcsComponent`](/Forge/docs/api/interfaces/TextEcsComponent) to an - entity, and the shaping system dirty-tracks it into a - [`TextMeshEcsComponent`](/Forge/docs/api/interfaces/TextMeshEcsComponent) - the render system draws. - -Try it in the [Text demo](/Forge/demos/text), which renders word-wrapped, -aligned paragraphs from a real generated Open Sans MSDF atlas, -including a live per-frame-updated label showing the shaping system's dirty -tracking in action. +`@forge-game-engine/forge/text` draws text - labels, scores, dialogue, +damage numbers - using MSDF font atlases, so it stays crisp at any size and +draws in the same batched pass as your sprites. Guides in this section: -- [MSDF Text](./msdf-text.md): generating a font atlas, loading it, and - configuring wrapping, alignment, and line spacing. +- [MSDF Text](./msdf-text.md): generating a font atlas and configuring + wrapping, alignment, color, and line spacing. + +Try it in the [Text demo](/Forge/demos/text). ## Quick Start @@ -47,7 +17,10 @@ Guides in this section: import { addPositionComponent } from '@forge-game-engine/forge/common'; import { loadFontAtlas } from '@forge-game-engine/forge/asset-loading'; import { SystemRegistrationOrder } from '@forge-game-engine/forge/ecs'; -import { createMsdfTextRenderable } from '@forge-game-engine/forge/rendering'; +import { + createMsdfTextRenderable, + createRenderEcsSystem, +} from '@forge-game-engine/forge/rendering'; import { addTextComponent, createTextShapingEcsSystem, @@ -63,8 +36,9 @@ const font = await loadFontAtlas( ); const renderable = createMsdfTextRenderable(font, renderContext); -// Shape before render, so the render system always sees this frame's glyphs. -world.addSystem(createTextShapingEcsSystem(), SystemRegistrationOrder.late); +// Register shaping before rendering, so text always shows this frame's glyphs. +world.addSystem(createTextShapingEcsSystem(), SystemRegistrationOrder.early); +world.addSystem(createRenderEcsSystem(renderContext)); const label = world.createEntity(); addPositionComponent(world, label, { world: { x: 0, y: 0 } }); @@ -76,22 +50,33 @@ addTextComponent(world, label, { }); ``` -See [MSDF Text](./msdf-text.md#registration-order) for why shaping needs to -run before `createRenderEcsSystem`. - -## Current limitations - -This is the initial MSDF rendering path from -[issue #584](https://github.com/Forge-Game-Engine/Forge/issues/584). Not -yet implemented: - -- **No font shipped with the engine.** Every project currently generates - and hosts its own atlas (see [MSDF Text](./msdf-text.md#generating-an-atlas)). -- **No Canvas2D prototyping escape hatch.** `msdf-atlas-gen` is a real - toolchain step; a documented `fillText`-to-texture fallback for quick - prototyping is planned but not yet built. -- **No text effects** (outline, drop shadow, glow) - these fall out as - extra MSDF shader parameters later. -- **Word wrap only, not character wrap.** A single word wider than - `wrapWidth` is placed on its own line unbroken; see - [`shapeText`](/Forge/docs/api/functions/shapeText). +See [MSDF Text](./msdf-text.md) for generating the font atlas this example +loads. + +## Options + +Pass these to [`addTextComponent`](/Forge/docs/api/functions/addTextComponent): + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `text` | `string` | *(required)* | The string to display. Assign a new value any time to update it. | +| `font` | `FontAtlas` | *(required)* | The loaded font atlas, from [`loadFontAtlas`](./msdf-text.md). | +| `renderable` | `Renderable` | *(required)* | From [`createMsdfTextRenderable`](./msdf-text.md#loading-and-drawing-text) - share one per font across every label using it. | +| `fontSize` | `number` | *(required)* | The text size, in world units. | +| `color` | `Color` | `Color.white` | The text's color. | +| `alignment` | `'left'` \| `'center'` \| `'right'` | `'left'` | How lines are positioned relative to each other. | +| `wrapWidth` | `number` | *(none)* | Wraps `text` between words to fit this width, in world units. Omit for a single unwrapped line. | +| `lineSpacing` | `number` | `1` | Multiplier for the gap between lines. | +| `pivot` | `{ x, y }` | `(0.5, 0.5)` | The text block's origin, normalized to its own size - `(0, 0)` is top-left, `(1, 1)` is bottom-right. | +| `enabled` | `boolean` | `true` | Set `false` to hide the text. | +| `layer` | `number` | `0` | Draw order relative to other sprites/text. | + +Explicit `\n` characters in `text` always start a new line. + +## Good to know + +- Forge doesn't ship a bundled font - see [MSDF Text](./msdf-text.md) for + generating your own atlas. +- Wrapping breaks between words, not mid-word: a single word wider than + `wrapWidth` is placed on its own line unbroken. +- There's no built-in outline, glow, or drop shadow yet. diff --git a/documentation-site/docs/docs/text/msdf-text.md b/documentation-site/docs/docs/text/msdf-text.md index 49c19968..2040d737 100644 --- a/documentation-site/docs/docs/text/msdf-text.md +++ b/documentation-site/docs/docs/text/msdf-text.md @@ -6,9 +6,7 @@ sidebar_position: 1 ## Generating an atlas -Forge doesn't ship a default font yet (see the -[current limitations](./index.md#current-limitations)), so every project -generates its own atlas with +Generate a font atlas with [`msdf-atlas-gen`](https://github.com/Chlumsky/msdf-atlas-gen): ```sh @@ -17,13 +15,19 @@ msdf-atlas-gen -font Roboto-Regular.ttf -type msdf -format png \ -charset charset.txt -size 32 -pxrange 4 ``` -(The [Text demo](/Forge/demos/text)'s own atlas - -`documentation-site/static/fonts/open-sans/` - was generated the same -way, from Open Sans; see the `README.md` alongside it for the exact -command used.) - Host the resulting `.json` and `.png` as static assets (e.g. next to your -other sprite sheets) and load them with +other sprite sheets). + +Generate `charset.txt` from every string your game actually displays, +including punctuation and any non-English text - a character missing from +the atlas is silently skipped when drawn, rather than erroring, so it's +easy to miss until someone types it. Leave the default kerning table +enabled (`msdf-atlas-gen` includes it unless you pass `-nokerning`), or +letter pairs like "AV" or "To" will look subtly too far apart. + +## Loading and drawing text + +Load the atlas with [`loadFontAtlas`](/Forge/docs/api/functions/loadFontAtlas): ```ts @@ -36,24 +40,9 @@ const font = await loadFontAtlas( ); ``` -`-charset` matters: a code point missing from the atlas is silently -skipped by `shapeText` rather than erroring, so leave out a needed -character and text using it will look wrong in a way that's easy to miss -until someone actually types that character. Generate the charset from -every string your game actually displays (including punctuation and any -non-English text) rather than guessing. - -Kerning pairs must be present in the JSON (`msdf-atlas-gen` includes them -by default) or text will look subtly wrong - slightly-too-wide gaps -between specific letter pairs like "AV" or "To" - in a way that's hard to -attribute back to "missing kerning data" later. - -## Drawing text - -[`createMsdfTextRenderable`](/Forge/docs/api/functions/createMsdfTextRenderable) -builds the GPU-side renderable once per `FontAtlas` - share it across every -entity using that font, the same way one `Renderable` is shared across -every sprite using the same spritesheet: +Build a renderable for it with +[`createMsdfTextRenderable`](/Forge/docs/api/functions/createMsdfTextRenderable) - +once per font, not once per label: ```ts import { createMsdfTextRenderable } from '@forge-game-engine/forge/rendering'; @@ -61,8 +50,7 @@ import { createMsdfTextRenderable } from '@forge-game-engine/forge/rendering'; const renderable = createMsdfTextRenderable(font, renderContext); ``` -Then attach a [`TextEcsComponent`](/Forge/docs/api/interfaces/TextEcsComponent) -with [`addTextComponent`](/Forge/docs/api/functions/addTextComponent): +Then attach a text component to any entity with a position: ```ts import { addPositionComponent } from '@forge-game-engine/forge/common'; @@ -71,7 +59,7 @@ import { addTextComponent } from '@forge-game-engine/forge/text'; const score = world.createEntity(); addPositionComponent(world, score, { world: { x: -300, y: 260 } }); -addTextComponent(world, score, { +const scoreText = addTextComponent(world, score, { text: 'Score: 0', font, renderable, @@ -80,62 +68,29 @@ addTextComponent(world, score, { }); ``` -Updating `text` (e.g. every time the score changes) is cheap to write - -just assign a new string to `textComponent.text` - and -[`createTextShapingEcsSystem`](/Forge/docs/api/functions/createTextShapingEcsSystem) -only re-shapes the entities whose text actually changed that frame; see -[Dirty tracking](#dirty-tracking). - -## Wrapping, alignment, and line spacing +Update the score later just by assigning a new string: ```ts -addTextComponent(world, dialogueEntity, { - text: 'A long line of dialogue that should wrap inside the text box.', - font, - renderable, - fontSize: 18, - wrapWidth: 280, - alignment: 'center', - lineSpacing: 1.2, -}); +scoreText.text = `Score: ${points}`; ``` -- **`wrapWidth`** (world units) word-wraps `text` to fit, breaking between - words. Omit it for a single line however long `text` is. A single word - wider than `wrapWidth` is placed on its own line unbroken - there's no - character-level breaking yet. -- **`alignment`** (`'left'` | `'center'` | `'right'`, default `'left'`) - positions each line relative to the text block's own width (the widest - line, or `wrapWidth` when set). -- **`lineSpacing`** (default `1`) multiplies `font.lineHeight` for the - vertical gap between line baselines. -- **`pivot`** (default `(0.5, 0.5)`, same convention as - [`SpriteEcsComponent.pivot`](/Forge/docs/api/interfaces/SpriteEcsComponent)) - is the text block's origin, normalized to its own size: `(0, 0)` anchors - the block's top-left corner to the entity's position, `(1, 1)` its - bottom-right. -- Explicit `\n` characters in `text` always start a new line, wrapped or not. - -## Registration order - -Where `createTextShapingEcsSystem` sits in the tick matters: - -- **Before `createRenderEcsSystem`.** The render system reads a - `TextMeshEcsComponent`'s glyph quads directly - an entity with a - `TextEcsComponent` but no `TextMeshEcsComponent` yet (never shaped) is - simply skipped, so register shaping with a lower - `SystemRegistrationOrder` - than rendering (`SystemRegistrationOrder.late`, from `@forge-game-engine/forge/ecs`, is a safe default for both). -- **After anything that changes `wrapWidth` at runtime**, if you're - computing it dynamically (e.g. from a resizable container's width) - rather than passing a fixed number. - -## Dirty tracking - -Re-shaping a paragraph - word-wrapping, kerning lookups, one quad per -character - is real cost, unlike most systems in a typical ECS pipeline -that recompute unconditionally every frame. `createTextShapingEcsSystem` -only re-shapes an entity when `text`, `font`, `fontSize`, `wrapWidth`, -`lineSpacing`, `alignment`, or `pivot` changed since its last shape. -Changing `color`, `enabled`, or `layer` never triggers a re-shape - those -only affect how the already-shaped glyphs are drawn. +That's cheap to do every frame - only `text`, `font`, `fontSize`, +`wrapWidth`, `lineSpacing`, `alignment`, or `pivot` changing triggers a +re-shape. Changing `color`, `enabled`, or `layer` doesn't. + +See [Options](./index.md#options) for the full list of settings, including +wrapping, alignment, and line spacing. + +## Setup + +Register the shaping system before the render system, so text always +shows this frame's glyphs: + +```ts +import { SystemRegistrationOrder } from '@forge-game-engine/forge/ecs'; +import { createTextShapingEcsSystem } from '@forge-game-engine/forge/text'; +import { createRenderEcsSystem } from '@forge-game-engine/forge/rendering'; + +world.addSystem(createTextShapingEcsSystem(), SystemRegistrationOrder.early); +world.addSystem(createRenderEcsSystem(renderContext)); +``` From 93a2c68dc36255ad4f55e9c9cce585df3840bfbf Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 01:23:14 +0000 Subject: [PATCH 5/5] fix(text): make TextEcsComponent.pivot Y-up, matching #590's sprite pivot fix dev's #590 flipped SpriteEcsComponent.pivot to Y-up ((0, 0) bottom-left, (1, 1) top-right) and fixed computeNineSliceRegions to match, but shapeText has its own separate pivot math that was explicitly modeled on nine-slice's old (Y-down) formula. That's now inconsistent with the rest of the engine, including the sprite instancing pipeline text's own glyph quads render through. Applies the same fix nine-slice got: pivot.y's contribution flips via (1 - pivot.y) instead of pivot.y directly. Only affects a non-centered pivot; the default (0.5, 0.5) is unaffected. Updates the pivot tests and docs to match. Also merges dev in to pick up #590 and everything else merged since this branch was created. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq --- documentation-site/docs/docs/text/index.md | 2 +- src/text/components/text-component.ts | 6 +++--- src/text/utilities/shape-text.test.ts | 24 +++++++++++++--------- src/text/utilities/shape-text.ts | 14 +++++++++++-- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/documentation-site/docs/docs/text/index.md b/documentation-site/docs/docs/text/index.md index bf4b9f0f..81e05b3d 100644 --- a/documentation-site/docs/docs/text/index.md +++ b/documentation-site/docs/docs/text/index.md @@ -67,7 +67,7 @@ Pass these to [`addTextComponent`](/Forge/docs/api/functions/addTextComponent): | `alignment` | `'left'` \| `'center'` \| `'right'` | `'left'` | How lines are positioned relative to each other. | | `wrapWidth` | `number` | *(none)* | Wraps `text` between words to fit this width, in world units. Omit for a single unwrapped line. | | `lineSpacing` | `number` | `1` | Multiplier for the gap between lines. | -| `pivot` | `{ x, y }` | `(0.5, 0.5)` | The text block's origin, normalized to its own size - `(0, 0)` is top-left, `(1, 1)` is bottom-right. | +| `pivot` | `{ x, y }` | `(0.5, 0.5)` | The text block's origin, normalized to its own size - `(0, 0)` is bottom-left, `(1, 1)` is top-right. | | `enabled` | `boolean` | `true` | Set `false` to hide the text. | | `layer` | `number` | `0` | Draw order relative to other sprites/text. | diff --git a/src/text/components/text-component.ts b/src/text/components/text-component.ts index 0fefb8bd..c5fa78b1 100644 --- a/src/text/components/text-component.ts +++ b/src/text/components/text-component.ts @@ -83,9 +83,9 @@ export interface TextDefaultedOptions { /** * The text block's origin, normalized to the block's own size: `(0, 0)` - * is the top-left corner, `(0.5, 0.5)` (the default) is the center, and - * `(1, 1)` is the bottom-right corner - matching - * `SpriteEcsComponent.pivot`'s convention exactly. + * is the bottom-left corner, `(0.5, 0.5)` (the default) is the center, + * and `(1, 1)` is the top-right corner - matching + * `SpriteEcsComponent.pivot`'s Y-up convention exactly. */ pivot: Vector2; diff --git a/src/text/utilities/shape-text.test.ts b/src/text/utilities/shape-text.test.ts index a3318654..914266ba 100644 --- a/src/text/utilities/shape-text.test.ts +++ b/src/text/utilities/shape-text.test.ts @@ -210,30 +210,34 @@ describe('shapeText', () => { describe('pivot', () => { it('defaults to the block center (0.5, 0.5)', () => { const centered = shapeText('A', createFont(), 10); - const topLeft = shapeText('A', createFont(), 10, { + const bottomLeft = shapeText('A', createFont(), 10, { pivot: { x: 0, y: 0 }, }); expect(centered.glyphs[0].offset.x).toBeLessThan( - topLeft.glyphs[0].offset.x, + bottomLeft.glyphs[0].offset.x, ); }); - it('offsets every glyph by pivot * bounds', () => { - const topLeft = shapeText('AA', createFont(), 10, { + it('offsets every glyph by pivot * bounds, Y-up like SpriteEcsComponent.pivot', () => { + const bottomLeft = shapeText('AA', createFont(), 10, { pivot: { x: 0, y: 0 }, }); - const bottomRight = shapeText('AA', createFont(), 10, { + const topRight = shapeText('AA', createFont(), 10, { pivot: { x: 1, y: 1 }, }); - const { bounds } = topLeft; + const { bounds } = bottomLeft; - expect(bottomRight.glyphs[0].offset.x).toBeCloseTo( - topLeft.glyphs[0].offset.x - bounds.x, + expect(topRight.glyphs[0].offset.x).toBeCloseTo( + bottomLeft.glyphs[0].offset.x - bounds.x, ); - expect(bottomRight.glyphs[0].offset.y).toBeCloseTo( - topLeft.glyphs[0].offset.y + bounds.y, + // pivot.y = 1 means top (Y-up), so it shifts the block down (offset.y + // decreases) relative to pivot.y = 0 (bottom) - the opposite sign + // from offset.x, since a higher pivot.x (more "right") shifts the + // block left the same way a higher pivot.y (more "top") shifts it down. + expect(topRight.glyphs[0].offset.y).toBeCloseTo( + bottomLeft.glyphs[0].offset.y - bounds.y, ); }); }); diff --git a/src/text/utilities/shape-text.ts b/src/text/utilities/shape-text.ts index dfbc15fb..e230c75d 100644 --- a/src/text/utilities/shape-text.ts +++ b/src/text/utilities/shape-text.ts @@ -26,7 +26,11 @@ export interface ShapeTextOptions { /** A multiplier over `font.lineHeight`. Defaults to `1`. */ lineSpacing?: number; - /** The text block's origin, normalized to its own size. Defaults to `(0.5, 0.5)`. */ + /** + * The text block's origin, normalized to its own size and Y-up (`(0, 0)` + * is bottom-left, `(1, 1)` is top-right), matching + * `SpriteEcsComponent.pivot`. Defaults to `(0.5, 0.5)`. + */ pivot?: Vector2; } @@ -235,7 +239,13 @@ export function shapeText( glyphs.push({ offset: { x: (left + right) / 2 - pivot.x * blockWidth, - y: pivot.y * blockHeight - (topDown + bottomDown) / 2, + // `topDown`/`bottomDown` run Y-down (distance from the block's + // top edge), but `pivot.y` is Y-up public API - `(0, 0)` bottom, + // `(1, 1)` top, matching `SpriteEcsComponent.pivot` - so + // `1 - pivot.y` first converts it to the same Y-down + // distance-from-top before the subtraction flips the whole + // result back to the engine's Y-up world space. + y: (1 - pivot.y) * blockHeight - (topDown + bottomDown) / 2, }, size: { x: right - left, y: bottomDown - topDown }, uvOffset: glyph.uvOffset,