{
  "markdown": "# @pmndrs/glyph\n\nPortable font baking, Unicode shaping, paragraph layout, and batched text rendering for every Canvas.\n\n`@pmndrs/glyph` retains authored text, shapes and lays it out in Rust/Wasm, then publishes a revisioned command buffer for the active renderer. The maintained Three.js integration supports Bitmap, MSDF, and Slug through WebGPU and Three's WebGL fallback.\n\n## Render text with React Three Fiber\n\n```tsx\nimport { Text, TextGroup } from '@pmndrs/glyph/react';\nimport { useBitmap } from '@pmndrs/glyph/react/bitmap';\nimport { useMsdf } from '@pmndrs/glyph/react/msdf';\nimport { useSlug } from '@pmndrs/glyph/react/slug';\n\nconst VT323 = '/fonts/VT323.font.glb';\nconst INTER = '/fonts/Inter.font.glb';\nconst LOVERS_QUARREL = '/fonts/LoversQuarrel.font.glb';\n\nawait useMsdf.preload(INTER);\n\nfunction Labels() {\n  const inter = useMsdf(INTER);\n  const loversQuarrel = useSlug(LOVERS_QUARREL);\n  const vt323 = useBitmap(VT323, { strikes: [8, 16] });\n\n  return (\n    <>\n      <Text\n        font={loversQuarrel}\n        style={{ fontSize: 32, color: '#f7f7f7' }}\n        layout={{ align: 'center' }}\n        constraints={{ width: { mode: 'exact', size: 480 } }}\n        position={[0, -4, 0]}\n      >\n        Lorem <Text style={{ color: '#f70000' }}>Ipsum</Text>\n      </Text>\n      <TextGroup>\n        <Text\n          font={inter}\n          style={{ fontSize: 32, color: '#f7f7f7' }}\n          layout={{ align: 'center' }}\n          constraints={{ width: { mode: 'exact', size: 480 } }}\n          position={[0, -1, 0]}\n        >\n          Eos tempor iusto mollit reprehenderit dolor cillum.\n        </Text>\n        <Text\n          font={inter}\n          style={{ fontSize: 32, color: '#f7f7f7' }}\n          layout={{ align: 'center' }}\n          constraints={{ width: { mode: 'exact', size: 480 } }}\n          position={[0, -1, 0]}\n        >\n          Irure accusamus voluptate est cupidatat eu commodo.\n        </Text>\n      </TextGroup>\n      <Text\n        font={vt323}\n        style={{ fontSize: 8, color: '#f7f7f7' }}\n        layout={{ wrap: 'word' }}\n        constraints={{ width: { mode: 'at-most', size: 480 } }}\n        position={[0, 0, 0]}\n      >\n        lorem ipsum dolor sit amet consectetur adipiscing elit eiusmod anim vel proident nam sint quo laborum ut eu amet\n        quis placeat qui reprehenderit in ad est accusamus et cupiditate fugiat voluptas ipsum et lorem nulla aut animi\n        et aut reprehenderit harum commodo quas et pariatur sit omnis ad harum aute\n      </Text>\n    </>\n  );\n}\n```\n\nAn outer `Text` is a retained paragraph and a Three `Object3D`. A nested `Text` is an inline run: it inherits the surrounding font, text style, and material unless it overrides them, and creates no scene object. The React integration rejects box-level props on nested text because JSX does not preserve enough generic element identity for TypeScript to enforce that distinction at every composition boundary. Runs may not always land in the same draw if they cannot be batched with their parent.\n\n`TextGroup` is an optional batching and ordering boundary. It collects descendant `Text` objects through the ordinary scene graph, so regular Three groups may appear between them. A standalone `Text` has the same text semantics and lazily owns an implicit batch of one.\n\nA `Text` always batches its own spans, so one paragraph mixing faces or sizes collapses into as few draws as its resources allow. Draw order inside a paragraph is shaping's, not the caller's: it is deterministic but not a promise. Order between paragraphs is stated by grouping them.\n\n`GlyphProvider` is optional. Use it only to select a named/custom root for a subtree or to declare scoped string FontFace\naliases. Its handle and FontFace table are immutable for the lifetime of that provider:\n\n```tsx\nimport { GlyphProvider, Text } from '@pmndrs/glyph/react';\n\n<GlyphProvider handle=\"hud\" fontFaces={{ Inter: '/fonts/Inter.font.glb' }} fallback={null}>\n  <Text font=\"Inter\">Hello, HUD</Text>\n</GlyphProvider>;\n```\n\n`handle=\"hud\"` selects the idempotent `hud` root on R3F's default Three handle. Pass an existing handle or root instead\nwhen a custom `ThreeConfig` is required. `<Text>` intentionally has no handle prop.\n\n## Render text with Three.js\n\n```ts\nimport { glyph, span, txt } from '@pmndrs/glyph';\nimport { ThreeConfig } from '@pmndrs/glyph/three';\n\nawait glyph.init();\nconst three = glyph.handle('main', ThreeConfig);\nconst interFace = await glyph.fontFace('/fonts/Inter.font.glb').load();\n\nconst accent = span({ color: '#70d6ff' });\nconst labels = three.createTextGroup();\nconst label = three.createText({\n  font: interFace,\n  text: txt`Hello ${accent`world`}`,\n  style: { fontSize: 32, lineHeight: 1.2, color: '#f4f7ff' },\n  layout: { wrap: 'word' },\n  constraints: { width: { mode: 'at-most', size: 480 } },\n});\n\nlabels.add(label);\nscene.add(labels);\nglyph.shape();\nrenderer.render(scene, camera);\n```\n\nThree uses `txt` and `span` where React uses nested `Text`. A span may override its font selection or text style without manually maintaining UTF-16 ranges; the Three `spans` form also accepts a material override.\n\n`handle.createText()` creates an ordinary Three `Object3D`, not a canvas or renderer. During `glyph.shape()` or Three\nscene traversal, Glyph attaches decoded `Mesh` children below that `Text`/`TextGroup`; the application's later\n`renderer.render(scene, camera)` performs the actual host draw. Dispose the text/group, FontFace, and handle when their\nowning application scope ends.\n\nAdd a `Text` directly to the scene when it does not need to share a batch. The nearest `TextGroup` applies all pending descendant changes together during Three's normal scene traversal.\n\nParagraph layout may also declare `columns: { count, gap }` to flow one paragraph through side-by-side ordered columns. Columns fill in order without balancing, so the last column may run short, and an exact width constraint is required.\n\nSetters update the desired state, mutating the text or style property will not mark the label as dirty:\n\n```ts\nlabel.text = 'Updated label';\nlabel.style = { ...label.style, letterSpacing: 0.5 };\nlabel.position.x += 1;\n```\n\nAssigning `text` queues the narrowest UTF-16 edit between the previous string and the new one, so an editor sends one\nnarrow update per keystroke without describing the edit itself.\n`measure()` synchronously measures current desired state without traversing matrices, realizing renderer resources, or\npublishing a draw. `glyphs()` explicitly requests the current positioned line and\nglyph details.\n\n## Measure before you render\n\nA `Text` can be measured before its first rendered frame, whether or not it has scene ancestry. Measurement does not\nrequire `scene.updateMatrixWorld()` and does not create renderer resources:\n\n```ts\nscene.add(label);\nconst measuredLabel = label.measure();\nlabel.position.x = -measuredLabel.contentWidth / 2;\nrenderer.render(scene, camera);\n```\n\nThe same explicit one-Text query path works inside a flexbox measure callback. Set the probe constraints, then call\n`measure()`; this deliberately pays one synchronous Wasm crossing for that Text without traversing a scene, publishing a\ndraw, or realizing renderer resources. Call `glyphs()` only after the host resolves the final content box.\n\n```ts\nlabel.constraints = { width: { mode: 'at-most', size: 360 } };\nconst measured = label.measure();\n\nmeasured.contentWidth; // advance extent\nmeasured.firstBaseline; // from the box top edge\nmeasured.ascent; // per paragraph; per line on measured.lines\nmeasured.minContentWidth; // longest unbreakable run, from the same pass\n\nlabel.constraints = { width: { mode: 'exact', size: measured.width } };\nconst positioned = label.glyphs();\n```\n\nEvery value is paragraph-local: the origin is the box's top-left corner, positive X is right, positive Y is down.\nScale and placement are yours to apply afterwards.\n\n`measure()` returns sizes, baselines, counts, and intrinsic widths without per-glyph array copies. When you need\npositioned output (`x`, `y`, `glyphIds`, ink boxes), call `glyphs()`; every call returns caller-owned column copies.\nUnchanged queries reuse retained engine preparation, and the next normal publication adopts that prepared work rather\nthan shaping it again. A caller that probes sizes alone never pays for arrays it never touches. A query answers or\nthrows: a constraint that is not finite and nonnegative throws from the call, naming the axis.\n\nOne baked GLB may contain several raster formats. Declare the exact formats the application uses, then load all declared\nformats in parallel through the FontFace or load one keyed selection on demand:\n\n```ts\nimport { glyph } from '@pmndrs/glyph';\nimport { bitmap } from '@pmndrs/glyph/raster/bitmap';\nimport { msdf } from '@pmndrs/glyph/raster/msdf';\nimport { slug } from '@pmndrs/glyph/raster/slug';\n\nconst inter = glyph.fontFace('/fonts/Inter.font.glb', {\n  family: 'Inter',\n  format: [msdf, bitmap({ strikes: [32] }), slug],\n});\n\nawait inter.load();\n// Or load only one selection: await inter.slug.load();\n\nscene.add(three.createText({ font: inter.msdf, text: 'Body' }));\nscene.add(three.createText({ font: inter.slug, text: 'Display' }));\n```\n\n## Capacity, materials, and ownership\n\nCapacity is optional immutable handle policy. `ThreeConfig` defaults every root to 4,096-glyph chunks. Create a specialized config for known bounds or memory behavior:\n\n```ts\nimport { defineThreeConfig } from '@pmndrs/glyph/three';\n\nconst dense = glyph.handle(\n  'dense-labels',\n  defineThreeConfig({\n    capacity: { size: 20_000, policy: 'chunk' },\n  }),\n);\nconst denseLabels = dense.createTextGroup();\n```\n\n- `chunk` retains bounded chunks as demand grows.\n- `grow` replaces full storage with a larger allocation.\n\n`grow` and `chunk` both resize; `fixed` rejects an update whose glyph requirement exceeds the declared size and\nkeeps the last complete revision visible. The requirement is a text-length upper bound computed before shaping, so\ncontent can be sized against the cap rather than discovered past it.\n\nCustom materials are renderer-owned factories. Rust carries their internal material identity through command-buffer\nconstruction, while Three creates the actual material only when a draw needs it. Different materials may still share\ninstance buffers.\n\n```ts\nimport { defineTextMaterial } from '@pmndrs/glyph/three';\n\nconst material = defineTextMaterial((context) => {\n  const value = context.createDefaultMaterial();\n  // Customize the technique-specific TSL material here.\n  return value;\n});\n\nconst custom = three.createText({ font: interFace, text: 'Custom material', material });\n```\n\nCall `dispose()` when a `Text`, `TextGroup`, FontFace, immutable loaded Font, or handle will not be reused. Disposing a\ngroup releases its publication boundary and renderer resources but does not dispose descendant `Text` objects, which may move\nto another live group.\n\n## Bake fonts\n\nThe `glyph` CLI bakes the canonical font GLB consumed by the loader. Bake one known font directly:\n\n```sh\npnpm exec glyph bake --input Inter-Regular.ttf --output Inter.font.glb --bitmap 32 --msdf --slug\n```\n\nAdd `--unicodes U+0020-007E` to bake a subset, or `--check` to rebuild temporarily and require byte-identical output.\nFor an icon font, `--glyph-map <path>` writes a directly importable JSON lookup from each authored glyph name in that\nsame Unicode selection to its code point:\n\n```sh\npnpm exec glyph bake --input fa-solid-900.ttf --output icons.font.glb --unicodes U+F000-F8FF --glyph-map icons.json --msdf\n```\n\nNames with multiple code points inside the selected set are rejected as ambiguous; narrow `--unicodes` rather than\nletting the generator silently choose an alias.\n\nOr let the CLI discover every `glyph.fontFace()` declaration in a project and write each artifact beside its source asset:\n\n```sh\npnpm exec glyph bake --project-root . --entry src/text.ts --asset-root public\n```\n\nDiscovery scans the declared entries, resolves each font's raster requirements from its declaration, and mirrors asset-relative outputs under `--output-root` when the artifacts belong somewhere other than the asset root. `glyph bake --help` lists every option. Runtime baking uses the same baker Wasm in a Worker and is opt-in; it is dynamically imported and split into its own chunk so it never reaches the default bundle.\n\nInspect authored `post` or CFF glyph names to find icon code points or produce a bake-ready Unicode set:\n\n```sh\npnpm exec glyph glyphs fa-solid-900.ttf --name globe --json\npnpm exec glyph glyphs fa-solid-900.ttf --name globe --name earth-americas --unicode-set\n```\n\nFonts without authored glyph names still report exact glyph IDs.\n\n## Integrate another renderer\n\n`GlyphConfig` is the complete renderer-integration boundary. It composes renderer-neutral schema, FontFace formats,\nCodec encoding, resource resolution, renderer decoding, and root construction without exposing a second engine or\nbackend API. The root package owns the one process-local `glyph` runtime; integrators import authoring helpers from the\nspecific `/config/*` leaves that define them.\n\nThe external example packages exercise that lifecycle against a real TypeGPU/WebGPU device. This is the same public\nsequence used by the hardware renderer lab:\n\n```ts\nimport { glyph } from '@pmndrs/glyph';\nimport { glyphExample } from '@pmndrs/glyph-example-raster';\nimport { defineExampleConfig, TypeGpuExampleRendererDevice } from '@pmndrs/glyph-example-renderer';\n\nconst adapter = await navigator.gpu.requestAdapter();\nif (adapter === null) throw new Error('WebGPU is unavailable');\nconst gpuDevice = await adapter.requestDevice();\n\nawait glyph.init();\nconst device = new TypeGpuExampleRendererDevice({ device: gpuDevice, width: 768, height: 192 });\nconst renderer = glyph.handle('typegpu', defineExampleConfig(device));\nconst font = glyph.fontFace('/fonts/Inter.font.glb', {\n  format: glyphExample({ paletteSeed: 17, inset: 0.08 }),\n});\nawait font.load();\nconst title = renderer.createText({\n  font,\n  text: 'Portable TypeGPU',\n  fontSize: 64,\n  width: 768,\n  height: 192,\n});\n\nglyph.shape();\nconst initial = renderer.drawList;\nconst initialPixels = await device.readPixels();\nif (initial.draws.length === 0 || initialPixels.every((byte) => byte === 0)) {\n  throw new Error('the renderer produced no visible draw');\n}\n\ntitle.update({ text: 'Updated WebGPU', color: '#ff40a0' });\nglyph.shape();\n\ntitle.dispose();\nrenderer.dispose();\nfont.dispose();\ndevice.dispose();\ngpuDevice.destroy();\n```\n\n`defineExampleConfig()` is intentionally an external package using the same public configuration leaves available to any\nintegrator. Its renderer synchronously decodes a borrowed `CommandBufferView`, stages one device transaction, and returns\n`commit()`/`discard()` without retaining the view. `glyph.shape()` publishes every dirty root across every live handle in\none engine crossing. Raw Wasm offsets and numeric identities remain package-private. See the\n[renderer integration guide](.agents/docs/guides/renderer-integration.md) and the\n[`glyph-example-renderer` source](packages/glyph-example-renderer/src/config.ts) for the complete configuration.\n\n## Codec and command buffer\n\nThe public text API describes typography. A Codec describes how that semantic result becomes physical instance records\nand compatible draws. It is registered once as typed numeric data, not called as JavaScript during layout or packing.\n\n```mermaid\nflowchart LR\n  mutations[\"Text and font mutations\"] --> layout[\"Rust shaping and layout\"]\n  layout --> codec[\"Renderer Codec\"]\n  codec --> commands[\"Revisioned command buffer\"]\n  commands --> render[\"Renderer resources, uploads, materials, and draws\"]\n```\n\nWho supplies each piece matters more than the order, because it decides what you write once and what\nyou write again for every engine:\n\n```mermaid\nflowchart TD\n  baker[\"Baker<br/><i>RasterBakerModule</i>\"] -->|\"baked GLB: strikes, atlases, curves\"| artifact[\"Font artifact\"]\n  artifact --> raster\n  subgraph portable[\"Written once — works in every engine\"]\n    raster[\"RasterFormat<br/><i>decode, dispose, schema</i>\"]\n    codec[\"Codec body<br/><i>portable operations</i>\"]\n    binding[\"Cold compiler<br/><i>binding bytes + resources</i>\"]\n  end\n  raster --> codec --> assemble[\"Engine Codec assembly<br/><i>system lanes + capabilities</i>\"]\n  assemble --> commands[\"Command buffer<br/><i>fixed-record data</i>\"]\n  raster --> binding --> commands\n  subgraph engine[\"Written once per engine\"]\n    gpu[\"Bind buffers, textures, resources<br/><i>from the command buffer</i>\"]\n    material[\"Realize material and submit\"]\n  end\n  commands --> gpu --> draw[\"Draws\"]\n  commands --> material --> draw\n```\n\nThe portable `RasterCodec` and compiled font result contain no renderer types. The `RasterCodec` owns the schema, Codec\nbody, and cold binding/resource composition; each engine supplies its own system-lane numbers, capabilities, transform\nand allocation choices, and final `CodecProgram` assembly. Only buffer/texture/resource binding and material realization\nare engine objects. A RasterFormat is therefore authored once and consumed by any renderer whose Codec and shader\nsupport it.\n\nThe Codec declares:\n\n- supported raster formats and paint/compositing capabilities;\n- physical buffer schemas and the semantic fields they consume;\n- storage and draw compatibility keys, including resource, material, clipping, depth, and ordering identity;\n- allocation strategy and renderer limits; and\n- an upload cost model for coalescing dirty ranges or replacing a whole buffer update.\n\nIts small forward-only packing program is the only bytecode in this design. Rust validates it before use and executes it over the semantic records, including SIMD lanes where available. It cannot branch backward, allocate, call JavaScript, or change shaping and layout.\n\nThe engine retains the resulting fixed-record command-buffer data internally. During `glyph.shape()`, it projects that\ntrusted data into one borrowed `CommandBufferView`: a transient revisioned display list and resource transaction, not\nexecutable bytecode and not a GPU-specific submission stream. The view contains:\n\n- identity and revision requirements;\n- resource and physical-buffer lifetimes;\n- allocate, resize, write, copy, and retirement patches;\n- ordered glyph, decoration, inline-object, and clip primitives; and\n- draw packets with exact buffer, resource, program, material, and ordering identities.\n\nNo GPU is required to shape, lay out, execute the Codec, or produce this data. The configured renderer begins host work\nonly when its synchronous `decode(view)` callback realizes the bound view. Adjacent revisions carry minimal Codec-costed\npatches; a consumer that misses the required base revision receives a complete checkpoint instead of applying an unsafe\ndelta.\n\n### Implement a renderer\n\nA `GlyphConfig` renderer integration has five responsibilities:\n\n1. Define a schema that maps bound command payloads to renderer-owned types.\n2. Implement `encode` to supply the Codec and its capabilities.\n3. Implement `resolve` to create lease-counted renderer resources from portable payloads.\n4. Implement `renderer().decode(view)` to stage buffers, patches, materials, primitives, and ordered draws without\n   re-shaping or reconstructing layout.\n5. Implement the root recipe that constructs retained Text-like objects through the supplied root services.\n\nThree is the maintained reference renderer. Bitmap, MSDF, Slug, Three, and the external example renderer all compose the\nsame public `/config/*` vocabulary; none reaches a privileged engine API. Three retains only its scene objects,\nshader/material realization, GPU resources, and transform synchronization.\n\nEach RasterFormat declares which authored text effects its Codec and shader support. MSDF supports outline and shadow;\nBitmap and Slug currently support neither. Unsupported effects throw rather than disappearing from the display list.\n\nStart with the [renderer integration guide](.agents/docs/guides/renderer-integration.md), which walks these responsibilities with\nthe external TypeGPU implementation. Internal engine, wire, projection, and planner modules are deliberately not package\nexports.\n\n## Experimental Three shaders\n\n`@pmndrs/glyph/three` retains the native TSL implementation from `main`. To test the migrated TypeGPU shaders,\nimport `ThreeConfig` or `defineThreeConfig` from `@pmndrs/glyph/three/typegpu` instead:\n\n```ts\nimport { ThreeConfig } from '@pmndrs/glyph/three/typegpu';\n```\n\nBoth entries share Text, TextGroup, materials, and lifecycle behavior. Shader selection belongs to each handle's\nconfig, so stable and experimental handles can coexist. The experimental entry also exports `bitmapShader`,\n`msdfShader`, `slugShader`, and `decorationShader` for material composition. `/shaders/tsl` retains the native TSL\nshaders; `/shaders/typegpu` retains the shared TypeGPU functions. The experimental entry requires the optional `typegpu`,\n`@typegpu/three`, and `@typegpu/gl` peers. It remains separate while parity testing continues.\n\n## TypeGPU applications\n\nUse `@pmndrs/glyph/typegpu` for retained text rendering with a caller-owned TypeGPU root and render pass.\n`defineTypeGpuConfig({ root, format })` plugs into `glyph.handle()`. Create text with `handle.createText()`, publish updates\nwith `glyph.shape()`, then record draws with `handle.draw(pass, { width, height })`. Bitmap, MSDF, and Slug share the same\nshader functions as the Three.js integration.\n\nRun `mise exec -- pnpm scripts run typegpu:dev` for the [editable hello-world app](apps/typegpu-hello-world/README.md).\n\n## Technique shaders on their own\n\nThe technique shaders ship without an engine or a scene attached, in two explicitly named sibling realizations:\n`@pmndrs/glyph/shaders/tsl` as Three.js Shading Language node graphs, and `@pmndrs/glyph/shaders/typegpu` as TypeGPU\nfunctions for any TypeGPU host. The experimental Three adapters are checked by compiling their graphs to WGSL and comparing against\nthe real generated source, rather than translating the node graph by inspection.\n\n```ts\nimport { bitmapShader } from '@pmndrs/glyph/shaders/tsl/bitmap';\nimport { msdfShader } from '@pmndrs/glyph/shaders/tsl/msdf';\nimport { slugShader } from '@pmndrs/glyph/shaders/tsl/slug';\nimport { bitmapFragment, bitmapVertexSnapped } from '@pmndrs/glyph/shaders/typegpu/bitmap';\n```\n\n## Develop\n\n```sh\nmise install\npnpm install\npnpm dev\n```\n\n### Enable the repository hooks\n\nEvery `.agents/docs/packages/<name>.md` pins a `source_digest` over its package tree, and CI rejects commits whose\ndigests trail their sources. Hook definitions ship versioned in the repository's `.gitconfig` (Git 2.54\nconfig-based hooks) with their scripts in `.githooks/`; the committed pre-commit hook re-pins those digests\nautomatically at commit time and runs the knowledge-base validation, so the pin can never go stale by\naccident. Opt in once per clone — the explicit include is the consent boundary for repository-supplied\nconfiguration, and every future hook change then ships with `git pull`, nothing to re-run:\n\n```sh\ngit config set include.path ../.gitconfig\n```\n\nVerify with `git hook list --show-scope pre-commit`. On older Git, `git config core.hooksPath .githooks`\nenables the same script through the fallback dispatcher. The hook never blocks a commit: it computes\ndigests from the staged tree (unstaged edits never leak into a pin), rewrites and stages the affected\n`.agents/docs/packages/*.md` pins automatically, and downgrades anything it cannot do — including a missing\nRuby — to a warning, leaving CI's knowledge-base gate as the enforcement. It runs on any Ruby 3.1 or\nnewer, however installed; no managed toolchain is required. Run it directly at any time as\n`.githooks/okf-digests`.\n\n`@pmndrs/glyph` is ESM-only and MIT licensed.\n",
  "bytes": 23826,
  "sha": "8da154b660aa276e11caefb9cbafa18ec52f1ffceb8b70d8284edf465eef8382",
  "repo_slug": "pmndrs/glyph",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_pmndrs_glyph_agents_docs_index_md_6e982e2e/readme"
}