{"_id":"@basementuniverse/image-font","_rev":"8-09cc99029cc7ba0f951db971d070230e","name":"@basementuniverse/image-font","dist-tags":{"latest":"1.4.0"},"versions":{"1.0.0":{"name":"@basementuniverse/image-font","version":"1.0.0","author":{"url":"https://gordonlarrigan.com","name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com"},"license":"MIT","_id":"@basementuniverse/image-font@1.0.0","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"homepage":"https://github.com/basementuniverse/image-font#readme","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"dist":{"shasum":"b7c7b722038f279d614d8e684f00b75b978ca51d","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.0.0.tgz","fileCount":5,"integrity":"sha512-y8UarYonKBHEXm3TN/qO6NQWVi3W8tcmkROIFv4lWM6FXP62ft405iMU3z2sadthCh4uGq+OnkovOYtCk2eIFw==","signatures":[{"sig":"MEUCIHBK/EVtp/bxmL4PRqD5WY1c1qCZQMStWY1iBHelqSVTAiEAn7a/QTlRwC0Qll8zlXQMQ5rjCm9/w1sZLmZ3fT8lecs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":61459},"main":"build/index.js","types":"build/index.d.ts","gitHead":"89377f88208b2e12b67d51ee8847c2b3a7e59c85","scripts":{"build":"webpack","watch":"webpack --watch"},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"repository":{"url":"git+https://github.com/basementuniverse/image-font.git","type":"git"},"_npmVersion":"11.5.1","description":"A component for rendering text using image fonts","directories":{},"_nodeVersion":"24.5.0","dependencies":{"@basementuniverse/vec":"^2.3.4","@basementuniverse/texture-atlas":"^1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"webpack":"^5.3.2","ts-loader":"^8.0.7","typescript":"^4.9.4","@types/node":"^18.11.12","webpack-cli":"^4.1.0","clean-webpack-plugin":"^4.0.0-alpha.0"},"_npmOperationalInternal":{"tmp":"tmp/image-font_1.0.0_1754842553890_0.19200697068874506","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@basementuniverse/image-font","version":"1.0.1","author":{"url":"https://gordonlarrigan.com","name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com"},"license":"MIT","_id":"@basementuniverse/image-font@1.0.1","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"homepage":"https://github.com/basementuniverse/image-font#readme","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"dist":{"shasum":"b7b04f105f7301a91a3e69cd58d48451b81708c9","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.0.1.tgz","fileCount":5,"integrity":"sha512-kxaDoh9B8UJUOKdZLghR+L8/3mQ+X4R5zmN9bkjZ8/N3gMRUcgRSbtg+wxtslgYePYYs9LW+4qW0U3a0BEK+pQ==","signatures":[{"sig":"MEYCIQCz18c0X+tigiJ+/dYoBnvWJhtKLweBAapoLMGAFf34zAIhALZKpObxe3fbWPrA62qX0zccYeSbRxQ9I9ic3mEoblsQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":61935},"main":"build/index.js","types":"build/index.d.ts","gitHead":"8b52a223c5e1aa5bf91842d0b393ab5be74579f8","scripts":{"build":"webpack","watch":"webpack --watch"},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"repository":{"url":"git+https://github.com/basementuniverse/image-font.git","type":"git"},"_npmVersion":"11.5.1","description":"A component for rendering text using image fonts","directories":{},"_nodeVersion":"24.5.0","dependencies":{"@basementuniverse/vec":"^2.3.4","@basementuniverse/texture-atlas":"^1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"webpack":"^5.3.2","ts-loader":"^8.0.7","typescript":"^4.9.4","@types/node":"^18.11.12","webpack-cli":"^4.1.0","clean-webpack-plugin":"^4.0.0-alpha.0"},"_npmOperationalInternal":{"tmp":"tmp/image-font_1.0.1_1755707016479_0.7534450479029207","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@basementuniverse/image-font","version":"1.0.2","author":{"url":"https://gordonlarrigan.com","name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com"},"license":"MIT","_id":"@basementuniverse/image-font@1.0.2","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"homepage":"https://github.com/basementuniverse/image-font#readme","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"dist":{"shasum":"a873d2471d5d3a4c4c9effb47b3d33078dce02e7","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.0.2.tgz","fileCount":5,"integrity":"sha512-KRkYSW9PG2P/koy8g9L0kMcSnArDL2u9WhraW+d6wYcdsR6ZiJvNPd79HJ0B40n0LAiEVZXNHpQwqQUSmfdWrA==","signatures":[{"sig":"MEUCIGu8LNNiddZcoThpwMTjTap13SNobiySyNLkN5Okf1oJAiEA4bR9IanpS1IKj9BqWELdlyATNxItwGmar6NNxDYqG+0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":62414},"main":"build/index.js","types":"build/index.d.ts","gitHead":"0e48fdf50eb3d68b55e62463dd0be6c4a63e99d5","scripts":{"build":"webpack","watch":"webpack --watch"},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"repository":{"url":"git+https://github.com/basementuniverse/image-font.git","type":"git"},"_npmVersion":"11.5.1","description":"A component for rendering text using image fonts","directories":{},"_nodeVersion":"24.5.0","dependencies":{"@basementuniverse/vec":"^2.3.4","@basementuniverse/texture-atlas":"^1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"webpack":"^5.3.2","ts-loader":"^8.0.7","typescript":"^4.9.4","@types/node":"^18.11.12","webpack-cli":"^4.1.0","clean-webpack-plugin":"^4.0.0-alpha.0"},"_npmOperationalInternal":{"tmp":"tmp/image-font_1.0.2_1755966570237_0.2566037702700876","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@basementuniverse/image-font","version":"1.1.0","author":{"url":"https://gordonlarrigan.com","name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com"},"license":"MIT","_id":"@basementuniverse/image-font@1.1.0","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"homepage":"https://github.com/basementuniverse/image-font#readme","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"dist":{"shasum":"e04dbaa311dad84305ddbf6c0a24d5cd89da7ec8","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.1.0.tgz","fileCount":5,"integrity":"sha512-iYrb5bRKTiSuR5JnSTE2/4KZFG7IdCRwEvOKBr33l3lyAAfRVpJjfUbchqJhV1J82dounWfzyxRa3V0hS5SySA==","signatures":[{"sig":"MEUCIQDVKSrUIIDDuzzmPt53K+KaSuRV12hTsDf3YfNeFWxXbwIgK9vEorqKpoVmGl3CujWIuSvfGQqUlYiHrspgexJAn2Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68159},"main":"build/index.js","types":"build/index.d.ts","gitHead":"8bfdd706ed023a896ce94923b7fb58345272fbce","scripts":{"build":"webpack","watch":"webpack --watch"},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"repository":{"url":"git+https://github.com/basementuniverse/image-font.git","type":"git"},"_npmVersion":"11.5.1","description":"A component for rendering text using image fonts","directories":{},"_nodeVersion":"24.5.0","dependencies":{"@basementuniverse/vec":"^2.3.4","@basementuniverse/texture-atlas":"^1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"webpack":"^5.3.2","ts-loader":"^8.0.7","typescript":"^4.9.4","@types/node":"^18.11.12","webpack-cli":"^4.1.0","clean-webpack-plugin":"^4.0.0-alpha.0"},"_npmOperationalInternal":{"tmp":"tmp/image-font_1.1.0_1758477904758_0.500253201710618","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@basementuniverse/image-font","version":"1.1.1","author":{"url":"https://gordonlarrigan.com","name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com"},"license":"MIT","_id":"@basementuniverse/image-font@1.1.1","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"homepage":"https://github.com/basementuniverse/image-font#readme","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"dist":{"shasum":"a394ca191d94835cd358d6ac0fc9fd74fc1dc6df","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.1.1.tgz","fileCount":5,"integrity":"sha512-ZtsWPhm9QiC4SWiiCWQhdJeApdYTBXQeSxXfoIp0WhNJ1xESaQ28T++ffZrazh4tSWut+kyfF+XfWQYprCFQ9g==","signatures":[{"sig":"MEYCIQCejIxJ5p0zTAphmHeWxliz42DOQ7NT387dVpkjuRu1XwIhAKsaiIeD3Md5mZ9NpGAir7ocYDsbfzCutuXTjGy6reXx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68403},"main":"build/index.js","types":"build/index.d.ts","gitHead":"e03580b8e8c3a4c138bf4cf201ed18e9a98dc9f9","scripts":{"build":"webpack","watch":"webpack --watch"},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"repository":{"url":"git+https://github.com/basementuniverse/image-font.git","type":"git"},"_npmVersion":"11.5.1","description":"A component for rendering text using image fonts","directories":{},"_nodeVersion":"24.5.0","dependencies":{"@basementuniverse/vec":"^2.3.4","@basementuniverse/texture-atlas":"^1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"webpack":"^5.3.2","ts-loader":"^8.0.7","typescript":"^4.9.4","@types/node":"^18.11.12","webpack-cli":"^4.1.0","clean-webpack-plugin":"^4.0.0-alpha.0"},"_npmOperationalInternal":{"tmp":"tmp/image-font_1.1.1_1759406419257_0.5074995598286958","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@basementuniverse/image-font","version":"1.2.0","author":{"url":"https://gordonlarrigan.com","name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com"},"license":"MIT","_id":"@basementuniverse/image-font@1.2.0","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"homepage":"https://github.com/basementuniverse/image-font#readme","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"dist":{"shasum":"eb245b83191c36910eb982d91bf688c88ffc5676","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.2.0.tgz","fileCount":5,"integrity":"sha512-Ym8exxawu0w3qJv1FLTHV90Q6Sp00NZLCHsdXj+W+yAvMr7uhix9RnKA3SdgT0Jrq2j7kNyO6qM+hspubC2v9A==","signatures":[{"sig":"MEYCIQDYq4EX4QlC/Y0dFVqrNMAN0xIlD5MF8IP7TJTWsp9N3AIhANvMPQ5ujXtxxAuHltzv28tf0ztNZ+8VieWD0iVs6GL+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68451},"main":"build/index.js","types":"build/index.d.ts","gitHead":"01dd68f7be687d41aa3ab3865c15aaff6c1907ee","scripts":{"build":"webpack","watch":"webpack --watch"},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"repository":{"url":"git+https://github.com/basementuniverse/image-font.git","type":"git"},"_npmVersion":"11.5.1","description":"A component for rendering text using image fonts","directories":{},"_nodeVersion":"24.5.0","dependencies":{"@basementuniverse/vec":"^2.3.4","@basementuniverse/texture-atlas":"^1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"webpack":"^5.3.2","ts-loader":"^8.0.7","typescript":"^4.9.4","@types/node":"^18.11.12","webpack-cli":"^4.1.0","clean-webpack-plugin":"^4.0.0-alpha.0"},"_npmOperationalInternal":{"tmp":"tmp/image-font_1.2.0_1763496927231_0.19572192619783446","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@basementuniverse/image-font","version":"1.3.0","author":{"url":"https://gordonlarrigan.com","name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com"},"license":"MIT","_id":"@basementuniverse/image-font@1.3.0","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"homepage":"https://github.com/basementuniverse/image-font#readme","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"dist":{"shasum":"e5ecb36fd93e174a91fb849a16b23d4985a95afc","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.3.0.tgz","fileCount":5,"integrity":"sha512-8sxrGKYbcVSf9PFP+Jqf9rBpInNBvLNHmvUWvXOZ4SRh9F9Ep63Ck/5rpulPSPlhiIBwMSimS4RRF7LsVL/I6w==","signatures":[{"sig":"MEYCIQCSbfiBHbpdG9CuiB9awgh41fIEe9anPRtj4K8xE09ivgIhAMX5/c7m4Fgp+e8XFBer411rKEdGdC+JYVgkHmPNYB6H","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":99691},"main":"build/index.js","types":"build/index.d.ts","gitHead":"af56eda816a1b86114dbab9397e25a129a0f4c8f","scripts":{"build":"webpack","watch":"webpack --watch"},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"repository":{"url":"git+https://github.com/basementuniverse/image-font.git","type":"git"},"_npmVersion":"11.6.2","description":"A component for rendering text using image fonts","directories":{},"_nodeVersion":"24.12.0","dependencies":{"@basementuniverse/vec":"^2.3.4","@basementuniverse/texture-atlas":"^1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"webpack":"^5.3.2","ts-loader":"^8.0.7","typescript":"^4.9.4","@types/node":"^18.11.12","webpack-cli":"^4.1.0","clean-webpack-plugin":"^4.0.0-alpha.0"},"_npmOperationalInternal":{"tmp":"tmp/image-font_1.3.0_1772357121210_0.8922425326242696","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@basementuniverse/image-font","version":"1.4.0","description":"A component for rendering text using image fonts","author":{"name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com","url":"https://gordonlarrigan.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/basementuniverse/image-font.git"},"main":"build/index.js","types":"build/index.d.ts","scripts":{"build":"webpack --mode production","build:dev":"webpack --mode development","watch":"webpack --watch"},"dependencies":{"@basementuniverse/texture-atlas":"^1.4.0","@basementuniverse/vec":"^2.5.1"},"devDependencies":{"@types/node":"^18.11.12","clean-webpack-plugin":"^4.0.0-alpha.0","ts-loader":"^8.0.7","typescript":"^4.9.4","webpack":"^5.3.2","webpack-cli":"^4.1.0"},"gitHead":"21e2df99ee7c8971dc543ddbd881acd792bb6e68","_id":"@basementuniverse/image-font@1.4.0","bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"homepage":"https://github.com/basementuniverse/image-font#readme","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-EYVdxYxnpkiPC4sEJVB5E+NlWfZrj2sgChd2iVDV8Vi/l1BiQa9fdH8Bm1wsIyyEoTc7ko3a+0kU8jRHew/K1Q==","shasum":"960ee5a81a2bcd22daeed062595df90bce780793","tarball":"https://registry.npmjs.org/@basementuniverse/image-font/-/image-font-1.4.0.tgz","fileCount":5,"unpackedSize":51350,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDA/zE2Y7pvvKcLn4Et0zfeM+3WVms7fesY7Y/r1qg+fAIhALHPP3M4+EIm0a/b5lRbXDMhM12172s9LeWto2Wm6us+"}]},"_npmUser":{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"},"directories":{},"maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/image-font_1.4.0_1786899482640_0.4323624507539572"},"_hasShrinkwrap":false}},"time":{"created":"2025-08-10T16:15:53.619Z","modified":"2026-08-16T16:58:02.942Z","1.0.0":"2025-08-10T16:15:54.055Z","1.0.1":"2025-08-20T16:23:36.660Z","1.0.2":"2025-08-23T16:29:30.416Z","1.1.0":"2025-09-21T18:05:04.947Z","1.1.1":"2025-10-02T12:00:19.457Z","1.2.0":"2025-11-18T20:15:27.434Z","1.3.0":"2026-03-01T09:25:21.363Z","1.4.0":"2026-08-16T16:58:02.784Z"},"bugs":{"url":"https://github.com/basementuniverse/image-font/issues"},"author":{"name":"Gordon Larrigan","email":"gordonlarrigan@gmail.com","url":"https://gordonlarrigan.com"},"license":"MIT","homepage":"https://github.com/basementuniverse/image-font#readme","repository":{"type":"git","url":"git+https://github.com/basementuniverse/image-font.git"},"description":"A component for rendering text using image fonts","maintainers":[{"name":"basementuniverse","email":"gordonlarrigan@gmail.com"}],"readme":"# Game Component: Image Font\n\nRender text using image fonts in your game.\n\n## Installation\n\n```bash\nnpm install @basementuniverse/image-font\n```\n\nFor direct browser usage, include the UMD build with a script tag:\n\n```html\n<script src=\"build/index.js\"></script>\n```\n\nThe build exposes its exports through the `BasementUniverseImageFont` browser\nnamespace. It does not add each export directly to `window`.\n\n## How to use\n\n![Image Font Example](./image-font-example.png)\n\nWe can initialise an image font with a texture atlas and some configuration data:\n\n```ts\nimport { textureAtlas } from '@basementuniverse/texture-atlas';\nimport { ImageFont } from '@basementuniverse/image-font';\n\nlet font: ImageFont | null = null;\n\nconst image = new Image();\nimage.src = './image-font.png';\nimage.onload = () => {\n  const atlas = textureAtlas(\n    image,\n    {\n      relative: true,\n      width: 8,\n      height: 5,\n      regions: {\n        'A': { x: 0, y: 0 },\n        'B': { x: 1, y: 0 },\n        'C': { x: 2, y: 0 },\n        // ...etc.\n      },\n    }\n  );\n\n  font = new ImageFont(\n    atlas,\n    {\n      defaultCharacterConfig: {\n        offset: { x: 14, y: 8 },\n        width: 32,\n        height: 48,\n      },\n      characters: {\n        'A': { width: 37 },\n        'B': { width: 37 },\n        'C': { width: 33 },\n        // ...etc.\n      }\n    }\n  );\n};\n```\n\nWhen using the library directly from a browser script, prefix exports with\n`BasementUniverseImageFont`:\n\n```html\n<script src=\"build/index.js\"></script>\n<script>\n  const font = new BasementUniverseImageFont.ImageFont(atlas, configData);\n\n  font.draw(context, 'HELLO', { x: 10, y: 10 });\n</script>\n```\n\nOr we can use a content processor:\n\n```ts\nimport { imageFontContentProcessor } from '@basementuniverse/image-font';\nimport ContentManager from '@basementuniverse/content-manager';\n\nContentManager.initialise({\n  processors: {\n    imageFont: imageFontContentProcessor,\n  },\n});\n\nContentManager.load([\n  {\n    name: 'font-spritesheet',\n    type: 'image',\n    args: ['./image-font.png'],\n  },\n  {\n    name: 'font',\n    type: 'json',\n    args: [\n      // Path to .json or inline JSON:\n      {\n        textureAtlasSize: { x: 8, y: 5 },\n        defaultCharacterConfig: {\n          offset: { x: 14, y: 8 },\n          width: 32,\n          height: 48,\n        },\n        characters: {\n          'A': { textureAtlasPosition: { x: 0, y: 0 }, width: 37 },\n          'B': { textureAtlasPosition: { x: 1, y: 0 }, width: 37 },\n          'C': { textureAtlasPosition: { x: 2, y: 0 }, width: 33 },\n          // ...etc.\n        }\n      }\n    ],\n    processors: [\n      {\n        name: 'imageFont',\n        args: ['font-spritesheet'],\n      },\n    ],\n  },\n]);\n\n// ContentManager.get('font') returns an ImageFont instance\n```\n\nThen we can use the `ImageFont` instance to measure and render text:\n\n```ts\n// Measure text\nconst textSize = font.measureText('ABC', options);\n\n// Render text\nfont.drawText(context, 'ABC', 0, 0, options);\n```\n\nSee './example/example.html' for a full example.\n\n## Glyph layout\n\nFor effects that require per-character control — typewriter reveals, path-following, per-character animations — you can separate the *layout* phase from the *draw* phase using `layoutText` and `drawLayout`.\n\n`layoutText` runs the same line-splitting, alignment, and cursor-advance logic as `drawText`, but instead of drawing immediately it returns a `GlyphLayout` object containing a `glyphs` array. Each element of the array is a `GlyphInfo` object describing one character in the rendered output. You can inspect and mutate these objects freely before passing the layout to `drawLayout`.\n\n`drawText` is unchanged and continues to work as before.\n\n### Typewriter reveal\n\n```ts\nconst layout = font.layoutText('HELLO WORLD', x, y, options);\n\n// In your update loop, track how many characters have been revealed:\nlayout.glyphs.forEach((glyph, i) => {\n  glyph.visible = i < revealedCount;\n});\n\n// In your draw loop:\nfont.drawLayout(context, layout);\n```\n\n### Per-character animation (wave)\n\n```ts\nconst layout = font.layoutText('HELLO WORLD', x, y, options);\n\n// Each frame, apply a sine-wave vertical offset to every glyph:\nlayout.glyphs.forEach((glyph, i) => {\n  glyph.offset = { x: 0, y: Math.sin(Date.now() / 200 + i * 0.5) * 4 };\n});\n\nfont.drawLayout(context, layout);\n```\n\n### Per-character colour gradient\n\n```ts\nconst layout = font.layoutText('RAINBOW', x, y, options);\nconst hueStep = 360 / layout.glyphs.length;\n\nlayout.glyphs.forEach((glyph, i) => {\n  glyph.color = `hsl(${i * hueStep}, 100%, 50%)`;\n});\n\nfont.drawLayout(context, layout);\n```\n\n### Text along a path\n\n```ts\nconst layout = font.layoutText('ALONG A CURVE', 0, 0, options);\n\n// Place each glyph at a point on a path, rotating it to follow the tangent:\nlet distance = 0;\nlayout.glyphs.forEach(glyph => {\n  const { point, tangentAngle } = samplePath(path, distance + glyph.advance / 2);\n  glyph.position = { x: point.x - glyph.size.x / 2, y: point.y - glyph.size.y / 2 };\n  glyph.rotation = tangentAngle;\n  distance += glyph.advance;\n});\n\nfont.drawLayout(context, layout);\n```\n\n### `preDraw` / `postDraw` callbacks\n\nFor effects that require additional canvas operations around individual characters (outlines, drop shadows, debug bounds), use the `preDraw` and `postDraw` callbacks on each `GlyphInfo`. The context is saved before and restored after each glyph when any per-glyph property is set.\n\n```ts\nconst layout = font.layoutText('OUTLINED', x, y, options);\n\nlayout.glyphs.forEach(glyph => {\n  glyph.postDraw = (ctx, g) => {\n    ctx.strokeStyle = 'black';\n    ctx.lineWidth = 2;\n    ctx.strokeRect(g.bounds.x, g.bounds.y, g.bounds.width, g.bounds.height);\n  };\n});\n\nfont.drawLayout(context, layout);\n```\n\n## GlyphLayout and GlyphInfo types\n\n```ts\ntype GlyphLayout = {\n  /** The original text string that was laid out */\n  text: string;\n\n  /** The x position originally passed to layoutText */\n  x: number;\n\n  /** The y position originally passed to layoutText */\n  y: number;\n\n  /** The rendering options used to produce this layout */\n  options?: ImageFontRenderingOptions;\n\n  /**\n   * The total bounding box of the laid-out text in canvas pixels\n   *\n   * Equivalent to the value returned by measureText with the same arguments\n   */\n  bounds: vec2;\n\n  /**\n   * Per-glyph layout information, one entry per character in the rendered\n   * output (after wrapping/truncation)\n   *\n   * Glyphs with no atlas texture (e.g. spaces) are included with\n   * visible: false so that index-based effects remain aligned with the string\n   */\n  glyphs: GlyphInfo[];\n};\n\ntype GlyphInfo = {\n  /** The character this glyph represents */\n  character: string;\n\n  /**\n   * The sequential index of this glyph in the layout (0-based)\n   *\n   * This is the glyph's position in the rendered output (after any wrapping\n   * or truncation applied by the overflow options), not necessarily its index\n   * in the original input string\n   */\n  index: number;\n\n  /** The line index this glyph is on (0-based) */\n  lineIndex: number;\n\n  /**\n   * The top-left draw position of this glyph in canvas pixels\n   *\n   * Computed from the cursor position minus any configured per-character or\n   * global offset, with the font and render scale already applied\n   */\n  position: vec2;\n\n  /**\n   * The size of this glyph's atlas tile in canvas pixels (post-scale)\n   *\n   * Will be (0, 0) for glyphs that have no texture in the atlas (e.g. spaces)\n   */\n  size: vec2;\n\n  /**\n   * The advance width of this glyph in canvas pixels\n   *\n   * This is how far the cursor moves after this glyph, including kerning\n   */\n  advance: number;\n\n  /**\n   * The bounding box of this glyph in canvas pixels\n   *\n   * Equivalent to { x: position.x, y: position.y, width: size.x, height: size.y }.\n   * Useful for hit-testing, debug overlays, and path-following calculations\n   */\n  bounds: { x: number; y: number; width: number; height: number };\n\n  /**\n   * Whether this glyph should be rendered\n   *\n   * Automatically false for characters with no texture in the atlas (e.g.\n   * spaces). Set to false manually to hide individual glyphs (typewriter\n   * reveal etc.)\n   */\n  visible: boolean;\n\n  /**\n   * Additional per-glyph draw offset in canvas pixels, applied on top of the\n   * computed position\n   */\n  offset?: vec2;\n\n  /**\n   * Per-glyph scale multiplier, applied around the pivot point\n   *\n   * Multiplied on top of the font and render scale already baked into\n   * position and size\n   */\n  scale?: number;\n\n  /**\n   * Rotation in radians, applied around the pivot point\n   */\n  rotation?: number;\n\n  /**\n   * The pivot point for rotation and scale transforms, in canvas pixels\n   *\n   * Defaults to the centre of the glyph's bounding box (after any per-glyph\n   * offset is applied)\n   */\n  pivot?: vec2;\n\n  /**\n   * Opacity multiplier (0–1), multiplied against the context's current\n   * globalAlpha at the time drawLayout is called\n   */\n  alpha?: number;\n\n  /**\n   * Per-glyph color override\n   *\n   * Overrides the color set in the layout's ImageFontRenderingOptions for this\n   * glyph only. The coloringMode and coloringFunction from the layout options\n   * are still used\n   */\n  color?: string;\n\n  /**\n   * Called immediately before this glyph is drawn, after all canvas transforms\n   * have been applied\n   *\n   * The context is saved and restored around each glyph when any per-glyph\n   * property (alpha, rotation, scale, preDraw, postDraw) is set\n   */\n  preDraw?: (context: CanvasRenderingContext2D, glyph: GlyphInfo) => void;\n\n  /**\n   * Called immediately after this glyph is drawn, before the context is\n   * restored\n   *\n   * Useful for outlines, drop shadows, or debug bounding boxes\n   */\n  postDraw?: (context: CanvasRenderingContext2D, glyph: GlyphInfo) => void;\n};\n```\n\n## ImageFont configuration\n\n```ts\ntype ImageFontConfig = {\n  /**\n   * Global offset applied to all characters from top-left of the texture atlas\n   * tile, measured in pixels\n   */\n  offset?: vec2;\n\n  /**\n   * Optional scaling factor for the font\n   */\n  scale?: number;\n\n  /**\n   * Default character configuration, used for characters that do not have\n   * a specific configuration defined, or for undefined characters\n   */\n  defaultCharacterConfig?: ImageFontCharacterConfig;\n\n  /**\n   * Per-character configuration\n   */\n  characters: Record<string, ImageFontCharacterConfig>;\n};\n\ntype ImageFontCharacterConfig = {\n  /**\n   * Offset from the top-left of the texture atlas tile, measured in pixels\n   */\n  offset?: vec2;\n\n  /**\n   * Width of the character in pixels, used for kerning\n   *\n   * If not specified, use the default width, or the width of the texture atlas\n   * tile\n   */\n  width?: number;\n\n  /**\n   * Height of the character in pixels, used for measuring text\n   *\n   * If not specified, use the default height, or the height of the texture\n   * atlas tile\n   */\n  height?: number;\n};\n```\n\n## Image Font content data\n\nWhen using a content processor, the configuration data for an image font is the same as the `ImageFontConfig` type, but with an additional `textureAtlasSize: vec2` property, containing the size of the texture atlas in tiles.\n\nAlso, each character configuration should have a `textureAtlasPosition: vec2` property, containing the tile address of the character in the texture atlas.\n\n## Rendering and measuring options\n\n```ts\ntype OverflowMode = 'word-wrap' | 'character-wrap' | 'hidden' | 'ellipsis' | 'none';\n\ntype ImageFontRenderingOptions = {\n  /**\n   * The scale factor to apply to the font when rendering\n   */\n  scale?: number;\n\n  /**\n   * Whether to disable per-character width and draw every character with the\n   * same spacing\n   *\n   * If this is true, the kerning value will be used and measured in pixels\n   *\n   * If this is true and the kerning value is undefined, use the pixel width\n   * of each texture atlas tile\n   */\n  monospace?: boolean;\n\n  /**\n   * The amount of kerning to apply between characters\n   *\n   * 0 means no spacing between characters, 1 means normal spacing, 2 means\n   * double spacing, etc.\n   *\n   * Default is 1\n   */\n  kerning?: number;\n\n  /**\n   * Horizontal alignment of the text relative to the x position\n   *\n   * Default is 'left'\n   */\n  align?: 'left' | 'center' | 'right';\n\n  /**\n   * Verticle alignment of the text relative to the baseline\n   *\n   * Default is 'top'\n   */\n  baseLine?: 'top' | 'middle' | 'bottom';\n\n  /**\n   * Color to apply to the text\n   *\n   * If not specified, no coloring is applied\n   */\n  color?: string;\n\n  /**\n   * How to apply the color\n   *\n   * Default is 'multiply'\n   */\n  coloringMode?: ColoringMode;\n\n  /**\n   * Custom coloring function when coloringMode is 'custom'\n   *\n   * If coloringMode is 'custom' but no function is provided, falls back to\n   * 'multiply'\n   */\n  coloringFunction?: (\n    context: CanvasRenderingContext2D,\n    texture: HTMLCanvasElement,\n    color: string\n  ) => void;\n\n  /**\n   * Maximum width of the text in pixels (pre-scale)\n   *\n   * When set, text that exceeds this width will be handled according to the\n   * overflow option. Alignment works correctly regardless of maxWidth.\n   *\n   * If not specified, text will not be wrapped or clipped.\n   */\n  maxWidth?: number;\n\n  /**\n   * How to handle text that exceeds maxWidth\n   *\n   * - 'word-wrap': wrap at word boundaries (falls back to character-wrap for\n   *   single words that exceed maxWidth)\n   * - 'character-wrap': wrap at character boundaries\n   * - 'hidden': text is cut off at maxWidth\n   * - 'ellipsis': text is cut off and an ellipsis string is appended\n   * - 'none': maxWidth is ignored\n   *\n   * Default is 'word-wrap'\n   */\n  overflow?: OverflowMode;\n\n  /**\n   * The string to use as an ellipsis when overflow is 'ellipsis'\n   *\n   * Default is '...'\n   */\n  ellipsisString?: string;\n\n  /**\n   * The height of each line in pixels (pre-scale), scaled by the active scale\n   * factor\n   *\n   * If not specified, defaults to the tallest character defined in the font,\n   * giving consistent line spacing regardless of the actual string content.\n   */\n  lineHeight?: number;\n};\n```\n\n### Multi-line text example\n\n```ts\n// Word-wrap within 200px, with custom line height\nfont.drawText(context, 'HELLO WORLD THIS IS A LONG STRING', x, y, {\n  maxWidth: 200,\n  overflow: 'word-wrap',\n  lineHeight: 50,\n  align: 'center',\n  baseLine: 'top',\n});\n\n// Truncate with ellipsis\nfont.drawText(context, 'HELLO WORLD', x, y, {\n  maxWidth: 100,\n  overflow: 'ellipsis',\n  ellipsisString: '...',\n});\n\n// measureText respects wrapping and returns the actual rendered bounding box\nconst size = font.measureText('HELLO WORLD', { maxWidth: 100, overflow: 'word-wrap' });\n```\n\n## Utility scripts\n\nIt can be rather tedious creating data for large image-fonts with lots of characters, so I vibe-coded a utility script to help with that in './example/generate-data.html'.\n\nYou will need to run this using [http-server](https://www.npmjs.com/package/http-server) or a similar tool to serve the HTML file, because it loads images and renders/processes them using a canvas; your browser will complain about this if you open the HTML file directly.\n\nWe also have a script './example/rescale.js' which re-scales sizes and offsets in a configuration JSON file by a given factor, which is useful if you want to change the size of an existing font texture atlas without having to manually edit all the values.\n","readmeFilename":"README.md"}