@flighthq/bitmaptext 0.5.1-next.597.ab004dd → 0.5.1-next.638.d4dbd4e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/bitmaptext`. The `@f
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/bitmaptext@0.5.1-next.597.ab004dd](https://github.com/flighthq/flight/tree/ab004ddc85b350cbd99a944e1e8108a016ae8af0/packages/bitmaptext)
17
- - [License](https://github.com/flighthq/flight/blob/ab004ddc85b350cbd99a944e1e8108a016ae8af0/LICENSE.md)
16
+ - [Source for @flighthq/bitmaptext@0.5.1-next.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/bitmaptext)
17
+ - [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
@@ -5,6 +5,7 @@ export declare function createBitmapTextData(data?: Readonly<Partial<BitmapTextD
5
5
  export declare function createBitmapTextRuntime(): BitmapTextRuntime;
6
6
  export declare function getBitmapTextBounds(source: Readonly<BitmapText>): Rectangle;
7
7
  export declare function getBitmapTextPages(source: Readonly<BitmapText>): readonly BitmapTextPage[];
8
+ export declare function isBitmapTextGlyphLayoutStale(source: Readonly<BitmapText>): boolean;
8
9
  export declare function reserveBitmapText(target: BitmapText, glyphCapacity: number): void;
9
10
  export declare function setBitmapTextAlign(target: BitmapText, align: BitmapTextAlign): void;
10
11
  export declare function setBitmapTextGlyphSource(target: BitmapText, glyphSource: GlyphSource | null): void;
@@ -1 +1 @@
1
- {"version":3,"file":"bitmapText.d.ts","sourceRoot":"","sources":["../src/bitmapText.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,WAAW,EAGX,SAAS,EACV,MAAM,0BAA0B,CAAC;AASlC,wBAAgB,qCAAqC,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAWxG;AASD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,UAAU,CAanH;AAED,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,cAAc,CAS7F;AAED,wBAAgB,uBAAuB,IAAI,iBAAiB,CAK3D;AAID,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,SAAS,CAI3E;AAID,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,SAAS,cAAc,EAAE,CAE1F;AAKD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAMjF;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAEnF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAElG;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAE1F;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAEpF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAExE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEzF"}
1
+ {"version":3,"file":"bitmapText.d.ts","sourceRoot":"","sources":["../src/bitmapText.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,WAAW,EAGX,SAAS,EACV,MAAM,0BAA0B,CAAC;AASlC,wBAAgB,qCAAqC,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAWxG;AASD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,UAAU,CAanH;AAED,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,cAAc,CAS7F;AAED,wBAAgB,uBAAuB,IAAI,iBAAiB,CAQ3D;AAID,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,SAAS,CAI3E;AAID,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,SAAS,cAAc,EAAE,CAE1F;AAOD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,OAAO,CAIlF;AAKD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAMjF;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAEnF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAElG;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAE1F;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAEpF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAExE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEzF"}
@@ -48,6 +48,9 @@ export function createBitmapTextData(data) {
48
48
  }
49
49
  export function createBitmapTextRuntime() {
50
50
  const runtime = createNode2DRuntime(defaultMethods);
51
+ // -1 is below every real version, so a node that has never been laid out reads stale and one
52
+ // `refreshBitmapTextGlyphLayout` brings it up — no separate "was it ever updated" flag.
53
+ runtime.glyphLayoutVersion = -1;
51
54
  runtime.localBoundsRectangle = null;
52
55
  runtime.pages = [];
53
56
  return runtime;
@@ -64,6 +67,17 @@ export function getBitmapTextBounds(source) {
64
67
  export function getBitmapTextPages(source) {
65
68
  return getNode2DRuntime(source).pages;
66
69
  }
70
+ // True when the page regions baked by the last `updateBitmapText` no longer describe the glyphs they
71
+ // were built for, because the bound glyph source has repacked since. Rects that have gone stale stay
72
+ // well-formed — they simply cover other glyphs now — so this version comparison is the only way to
73
+ // tell, and it is the check `refreshBitmapTextGlyphLayout` performs. A node that has never been laid
74
+ // out reads true; a node with no glyph source, or one bound to a static font, reads false forever.
75
+ export function isBitmapTextGlyphLayoutStale(source) {
76
+ const glyphSource = source.data.glyphSource;
77
+ if (glyphSource === null)
78
+ return false;
79
+ return getNode2DRuntime(source).glyphLayoutVersion !== glyphSource.getGlyphLayoutVersion();
80
+ }
67
81
  // Grows each page's quad arrays to hold at least `glyphCapacity` glyph quads without reallocating during
68
82
  // layout. Optional — `updateBitmapText` auto-grows — but avoids incremental reallocation for large
69
83
  // strings. Reserving every page over-allocates for multi-page text but never under-sizes.
@@ -1 +1 @@
1
- {"version":3,"file":"bitmapText.js","sourceRoot":"","sources":["../src/bitmapText.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bitmapText.js","sourceRoot":"","sources":["../src/bitmapText.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  export * from './bitmapText';
2
+ export * from './enableBitmapTextGuards';
2
3
  export * from './updateBitmapText';
3
4
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
package/dist/contract.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './bitmapText';
2
+ export * from './enableBitmapTextGuards';
2
3
  export * from './updateBitmapText';
3
4
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /** Uninstalls the guard installed by `enableBitmapTextGuards`. */
2
+ export declare function disableBitmapTextGuards(): void;
3
+ /**
4
+ * Installs the caller-facing bitmap-text guard (opt-in, dev-only). It warns once — through
5
+ * `@flighthq/log` — when a layout never settles:
6
+ *
7
+ * **The layout did not converge.** `updateBitmapText` re-runs a layout whose glyph rects a repack
8
+ * invalidated mid-pass, and it ran out of passes. The string needs more glyphs resident at once than
9
+ * the atlas can hold, so rasterizing the end of it evicts the start, every pass invalidates the one
10
+ * before, and the quads that get drawn sample whatever displaced them. Unlike a full cache this does
11
+ * not resolve itself with use: the atlas has to be bigger or the font smaller.
12
+ *
13
+ * Pair with `isBitmapTextGlyphLayoutStale` for a pull-style answer about one node, and with
14
+ * `enableGlyphAtlasGuards` for the atlas-side view of the same pressure — that one reports the repacks
15
+ * and the glyphs they drop, this one reports the text that could not be laid out through them. Not
16
+ * importing this module costs production nothing: the wording and the `@flighthq/log` dependency live
17
+ * only here.
18
+ */
19
+ export declare function enableBitmapTextGuards(): void;
20
+ //# sourceMappingURL=enableBitmapTextGuards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enableBitmapTextGuards.d.ts","sourceRoot":"","sources":["../src/enableBitmapTextGuards.ts"],"names":[],"mappings":"AAKA,kEAAkE;AAClE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
@@ -0,0 +1,35 @@
1
+ import { logOnce } from '@flighthq/log/contract';
2
+ import { LogLevel } from '@flighthq/types/contract';
3
+ import { setBitmapTextLayoutGuard } from './updateBitmapText';
4
+ /** Uninstalls the guard installed by `enableBitmapTextGuards`. */
5
+ export function disableBitmapTextGuards() {
6
+ setBitmapTextLayoutGuard(null);
7
+ }
8
+ /**
9
+ * Installs the caller-facing bitmap-text guard (opt-in, dev-only). It warns once — through
10
+ * `@flighthq/log` — when a layout never settles:
11
+ *
12
+ * **The layout did not converge.** `updateBitmapText` re-runs a layout whose glyph rects a repack
13
+ * invalidated mid-pass, and it ran out of passes. The string needs more glyphs resident at once than
14
+ * the atlas can hold, so rasterizing the end of it evicts the start, every pass invalidates the one
15
+ * before, and the quads that get drawn sample whatever displaced them. Unlike a full cache this does
16
+ * not resolve itself with use: the atlas has to be bigger or the font smaller.
17
+ *
18
+ * Pair with `isBitmapTextGlyphLayoutStale` for a pull-style answer about one node, and with
19
+ * `enableGlyphAtlasGuards` for the atlas-side view of the same pressure — that one reports the repacks
20
+ * and the glyphs they drop, this one reports the text that could not be laid out through them. Not
21
+ * importing this module costs production nothing: the wording and the `@flighthq/log` dependency live
22
+ * only here.
23
+ */
24
+ export function enableBitmapTextGuards() {
25
+ setBitmapTextLayoutGuard(warnOnBitmapTextLayoutBlocked);
26
+ }
27
+ function warnOnBitmapTextLayoutBlocked(_reason, attempts) {
28
+ logOnce('bitmaptext:layout-did-not-converge', LogLevel.Warn, {
29
+ message: `updateBitmapText: the glyph source repacked during all ${attempts} layout passes, so this text ` +
30
+ 'never settled — its glyphs cannot all be resident in the atlas at once, and the quads drawn from ' +
31
+ 'the final pass sample stale rects. Enlarge the glyph atlas or reduce the font size; ' +
32
+ 'enableGlyphAtlasGuards reports the repacks from the atlas side.',
33
+ }, 'bitmaptext');
34
+ }
35
+ //# sourceMappingURL=enableBitmapTextGuards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enableBitmapTextGuards.js","sourceRoot":"","sources":["../src/enableBitmapTextGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,kEAAkE;AAClE,MAAM,UAAU,uBAAuB;IACrC,wBAAwB,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB;IACpC,wBAAwB,CAAC,6BAA6B,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,6BAA6B,CAAC,OAAe,EAAE,QAAgB;IACtE,OAAO,CACL,oCAAoC,EACpC,QAAQ,CAAC,IAAI,EACb;QACE,OAAO,EACL,0DAA0D,QAAQ,+BAA+B;YACjG,mGAAmG;YACnG,sFAAsF;YACtF,iEAAiE;KACpE,EACD,YAAY,CACb,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { computeBitmapTextLocalBoundsRectangle, createBitmapText, getBitmapTextBounds, getBitmapTextPages, reserveBitmapText, setBitmapTextAlign, setBitmapTextGlyphSource, setBitmapTextLetterSpacing, setBitmapTextLineHeight, setBitmapTextText, setBitmapTextWrapWidth, updateBitmapText, } from './contract';
1
+ export { computeBitmapTextLocalBoundsRectangle, createBitmapText, disableBitmapTextGuards, enableBitmapTextGuards, getBitmapTextBounds, getBitmapTextPages, isBitmapTextGlyphLayoutStale, refreshBitmapTextGlyphLayout, reserveBitmapText, setBitmapTextAlign, setBitmapTextGlyphSource, setBitmapTextLetterSpacing, setBitmapTextLineHeight, setBitmapTextText, setBitmapTextWrapWidth, updateBitmapText, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qCAAqC,EACrC,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qCAAqC,EACrC,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,4BAA4B,EAC5B,4BAA4B,EAC5B,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { computeBitmapTextLocalBoundsRectangle, createBitmapText, getBitmapTextBounds, getBitmapTextPages, reserveBitmapText, setBitmapTextAlign, setBitmapTextGlyphSource, setBitmapTextLetterSpacing, setBitmapTextLineHeight, setBitmapTextText, setBitmapTextWrapWidth, updateBitmapText, } from './contract';
1
+ export { computeBitmapTextLocalBoundsRectangle, createBitmapText, disableBitmapTextGuards, enableBitmapTextGuards, getBitmapTextBounds, getBitmapTextPages, isBitmapTextGlyphLayoutStale, refreshBitmapTextGlyphLayout, reserveBitmapText, setBitmapTextAlign, setBitmapTextGlyphSource, setBitmapTextLetterSpacing, setBitmapTextLineHeight, setBitmapTextText, setBitmapTextWrapWidth, updateBitmapText, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qCAAqC,EACrC,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qCAAqC,EACrC,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,4BAA4B,EAC5B,4BAA4B,EAC5B,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
@@ -1,3 +1,8 @@
1
1
  import type { BitmapText } from '@flighthq/types/contract';
2
+ export declare function refreshBitmapTextGlyphLayout(bitmapText: BitmapText): boolean;
3
+ /** Installs the bitmap-text guard, or clears it with `null`. The seam keeps the wording and the
4
+ * `@flighthq/log` dependency in the separately-importable guard module rather than in layout; not
5
+ * importing that module costs production nothing. Called by `enableBitmapTextGuards`. */
6
+ export declare function setBitmapTextLayoutGuard(guard: ((reason: string, attempts: number) => void) | null): void;
2
7
  export declare function updateBitmapText(bitmapText: BitmapText): void;
3
8
  //# sourceMappingURL=updateBitmapText.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"updateBitmapText.d.ts","sourceRoot":"","sources":["../src/updateBitmapText.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,UAAU,EAOX,MAAM,0BAA0B,CAAC;AAgBlC,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CA2E7D"}
1
+ {"version":3,"file":"updateBitmapText.d.ts","sourceRoot":"","sources":["../src/updateBitmapText.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,UAAU,EAOX,MAAM,0BAA0B,CAAC;AAWlC,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAI5E;AAED;;0FAE0F;AAC1F,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAEzG;AAsBD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAwB7D"}
@@ -3,9 +3,26 @@ import { invalidateNodeLocalBounds } from '@flighthq/node/contract';
3
3
  import { getNode2DRuntime } from '@flighthq/scene2d/contract';
4
4
  import { createTexture, setTextureSource } from '@flighthq/texture/contract';
5
5
  import { addTextureAtlasRegion, createTextureAtlas } from '@flighthq/textureatlas/contract';
6
- const BITMAP_TEXT_TRANSFORM_STRIDE = 2;
7
- const CARRIAGE_RETURN = 0x0d;
8
- const SPACE = 0x20;
6
+ import { isBitmapTextGlyphLayoutStale } from './bitmapText';
7
+ // Re-lays-out `bitmapText` when, and only when, its glyph source has repacked since the last layout —
8
+ // the pairing that keeps a dynamic atlas correct. Returns whether it re-laid-out, so a caller can count
9
+ // churn. Call it once per frame per BitmapText bound to a `@flighthq/glyphatlas` source: a repack
10
+ // triggered by ANY consumer of that atlas (another BitmapText adding a glyph, a direct
11
+ // `getGlyphAtlasEntry`) relocates this node's glyphs, and its baked page regions then sample whatever
12
+ // took their place. The comparison is one number against one number, so the frames where nothing
13
+ // repacked cost nothing. A node bound to a static bitmap font never re-lays-out here.
14
+ export function refreshBitmapTextGlyphLayout(bitmapText) {
15
+ if (!isBitmapTextGlyphLayoutStale(bitmapText))
16
+ return false;
17
+ updateBitmapText(bitmapText);
18
+ return true;
19
+ }
20
+ /** Installs the bitmap-text guard, or clears it with `null`. The seam keeps the wording and the
21
+ * `@flighthq/log` dependency in the separately-importable guard module rather than in layout; not
22
+ * importing that module costs production nothing. Called by `enableBitmapTextGuards`. */
23
+ export function setBitmapTextLayoutGuard(guard) {
24
+ _layoutGuard = guard;
25
+ }
9
26
  // Lays out `bitmapText`'s current string and rewrites its `BitmapTextPage` quads: one quad per visible
10
27
  // glyph, partitioned by the glyph's atlas page into one page's quads, positioned by the glyph source's
11
28
  // advances and kerning, broken on explicit newlines and (when `wrapWidth` is set) at word boundaries,
@@ -16,84 +33,40 @@ const SPACE = 0x20;
16
33
  // glyphs are skipped. Missing glyphs (`getGlyphEntry` → null) are omitted entirely — no quad and no
17
34
  // advance. Bounds span every drawn glyph across all pages. Tint is the node's own color-adjustment stack,
18
35
  // not touched here.
36
+ //
37
+ // A dynamic glyph source can repack DURING this call: rasterizing a glyph late in the string can evict
38
+ // one placed early in it. A repack that merely relocates a survivor needs nothing from this pass — the
39
+ // atlas moves the same entry object the pass is holding, so the emit below reads the new rect. What
40
+ // this loop exists for is a repack that DROPS a glyph: the entry the pass captured is then orphaned at
41
+ // a rect another glyph has taken over, and only re-running the pass reaches the fresh one. So the
42
+ // layout runs against a placement version and repeats while that version moves under it, and the
43
+ // version the winning pass agreed with is what gets stamped for `isBitmapTextGlyphLayoutStale`. Passes
44
+ // are bounded — a string whose glyphs cannot all be resident at once would otherwise evict each other
45
+ // forever — and the guard layer reports a string that never settles.
19
46
  export function updateBitmapText(bitmapText) {
20
- const data = bitmapText.data;
21
47
  const runtime = getNode2DRuntime(bitmapText);
22
- const bounds = ensureBoundsRectangle(runtime);
23
- // Clear every existing page; pages with glyphs this layout are refilled below, pages that fall silent
24
- // stay as empty pages drawing nothing.
25
- for (const page of runtime.pages) {
26
- page.instanceCount = 0;
27
- page.atlas.regions.length = 0;
28
- }
29
- const glyphSource = data.glyphSource;
30
- if (glyphSource === null || data.text.length === 0) {
31
- setEmptyRectangle(bounds);
32
- invalidateNodeLocalBounds(bitmapText);
48
+ const glyphSource = bitmapText.data.glyphSource;
49
+ if (glyphSource === null) {
50
+ layoutBitmapTextPages(bitmapText, runtime);
51
+ runtime.glyphLayoutVersion = -1;
33
52
  return;
34
53
  }
35
- const metrics = glyphSource.getGlyphMetrics();
36
- const lineAdvance = (metrics.ascent + metrics.descent + metrics.lineGap) * data.lineHeight;
37
- const lines = layoutBitmapTextLines(glyphSource, data);
38
- const refWidth = data.wrapWidth ?? maxLineWidth(lines);
39
- const pages = new Map();
40
- let minX = Infinity;
41
- let minY = Infinity;
42
- let maxX = -Infinity;
43
- let maxY = -Infinity;
44
- for (let li = 0; li < lines.length; li++) {
45
- const line = lines[li];
46
- const baselineY = metrics.ascent + li * lineAdvance;
47
- let startX = 0;
48
- let gapExtra = 0;
49
- if (data.align === 'center')
50
- startX = (refWidth - line.width) / 2;
51
- else if (data.align === 'right')
52
- startX = refWidth - line.width;
53
- else if (data.align === 'justify' && data.wrapWidth !== null && !line.paragraphEnd && line.gaps.length > 0) {
54
- gapExtra = (data.wrapWidth - line.width) / line.gaps.length;
55
- }
56
- let penX = startX;
57
- for (let wi = 0; wi < line.words.length; wi++) {
58
- if (wi > 0)
59
- penX += line.gaps[wi - 1] + gapExtra;
60
- const word = line.words[wi];
61
- for (const glyph of word.glyphs) {
62
- const entry = glyph.entry;
63
- const context = ensureBitmapTextPage(runtime, glyphSource, pages, entry.page);
64
- if (context === null)
65
- continue;
66
- const quadX = penX + glyph.penWithinWord + entry.bearingX;
67
- const quadY = baselineY - entry.bearingY;
68
- let regionId = context.regionByCodepoint.get(glyph.codepoint);
69
- if (regionId === undefined) {
70
- addTextureAtlasRegion(context.page.atlas, entry.x, entry.y, entry.width, entry.height);
71
- regionId = context.page.atlas.regions.length - 1;
72
- context.regionByCodepoint.set(glyph.codepoint, regionId);
73
- }
74
- appendBitmapTextPageQuad(context.page, regionId, quadX, quadY);
75
- if (quadX < minX)
76
- minX = quadX;
77
- if (quadY < minY)
78
- minY = quadY;
79
- if (quadX + entry.width > maxX)
80
- maxX = quadX + entry.width;
81
- if (quadY + entry.height > maxY)
82
- maxY = quadY + entry.height;
83
- }
84
- penX += word.width;
54
+ for (let attempt = 1; attempt <= BITMAP_TEXT_LAYOUT_ATTEMPTS; attempt++) {
55
+ const version = glyphSource.getGlyphLayoutVersion();
56
+ layoutBitmapTextPages(bitmapText, runtime);
57
+ if (glyphSource.getGlyphLayoutVersion() === version) {
58
+ runtime.glyphLayoutVersion = version;
59
+ return;
85
60
  }
86
61
  }
87
- if (minX === Infinity) {
88
- setEmptyRectangle(bounds);
89
- }
90
- else {
91
- bounds.x = minX;
92
- bounds.y = minY;
93
- bounds.width = maxX - minX;
94
- bounds.height = maxY - minY;
95
- }
96
- invalidateNodeLocalBounds(bitmapText);
62
+ // Out of passes. The last layout is the best available and some of its rects are already wrong, and
63
+ // the current version is still what gets stamped: another pass would reach the same place, so leaving
64
+ // the node marked stale would only re-lay-out it every frame forever. The atlas is too small for this
65
+ // string, which is a sizing decision the caller has to make — hence the guard rather than a retry
66
+ // budget. A node here does come right on its own if the atlas stops repacking, because the version
67
+ // then holds still and the next refresh's single pass sees one placement.
68
+ runtime.glyphLayoutVersion = glyphSource.getGlyphLayoutVersion();
69
+ _layoutGuard?.('layout-did-not-converge', BITMAP_TEXT_LAYOUT_ATTEMPTS);
97
70
  }
98
71
  // Appends one glyph quad (region id + vector2 pen position) to `page`, auto-growing its arrays. The
99
72
  // page-local twin of `appendQuadBatchInstance`, over plain owned arrays rather than a QuadBatch node.
@@ -220,6 +193,86 @@ function layoutBitmapTextLines(glyphSource, data) {
220
193
  }
221
194
  return lines;
222
195
  }
196
+ // One layout pass: the glyph placement itself, with no version bookkeeping. Split out from
197
+ // `updateBitmapText` so the retry above re-runs exactly the work a repack invalidated.
198
+ function layoutBitmapTextPages(bitmapText, runtime) {
199
+ const data = bitmapText.data;
200
+ const bounds = ensureBoundsRectangle(runtime);
201
+ // Clear every existing page; pages with glyphs this layout are refilled below, pages that fall silent
202
+ // stay as empty pages drawing nothing.
203
+ for (const page of runtime.pages) {
204
+ page.instanceCount = 0;
205
+ page.atlas.regions.length = 0;
206
+ }
207
+ const glyphSource = data.glyphSource;
208
+ if (glyphSource === null || data.text.length === 0) {
209
+ setEmptyRectangle(bounds);
210
+ invalidateNodeLocalBounds(bitmapText);
211
+ return;
212
+ }
213
+ const metrics = glyphSource.getGlyphMetrics();
214
+ const lineAdvance = (metrics.ascent + metrics.descent + metrics.lineGap) * data.lineHeight;
215
+ const lines = layoutBitmapTextLines(glyphSource, data);
216
+ const refWidth = data.wrapWidth ?? maxLineWidth(lines);
217
+ const pages = new Map();
218
+ let minX = Infinity;
219
+ let minY = Infinity;
220
+ let maxX = -Infinity;
221
+ let maxY = -Infinity;
222
+ for (let li = 0; li < lines.length; li++) {
223
+ const line = lines[li];
224
+ const baselineY = metrics.ascent + li * lineAdvance;
225
+ let startX = 0;
226
+ let gapExtra = 0;
227
+ if (data.align === 'center')
228
+ startX = (refWidth - line.width) / 2;
229
+ else if (data.align === 'right')
230
+ startX = refWidth - line.width;
231
+ else if (data.align === 'justify' && data.wrapWidth !== null && !line.paragraphEnd && line.gaps.length > 0) {
232
+ gapExtra = (data.wrapWidth - line.width) / line.gaps.length;
233
+ }
234
+ let penX = startX;
235
+ for (let wi = 0; wi < line.words.length; wi++) {
236
+ if (wi > 0)
237
+ penX += line.gaps[wi - 1] + gapExtra;
238
+ const word = line.words[wi];
239
+ for (const glyph of word.glyphs) {
240
+ const entry = glyph.entry;
241
+ const context = ensureBitmapTextPage(runtime, glyphSource, pages, entry.page);
242
+ if (context === null)
243
+ continue;
244
+ const quadX = penX + glyph.penWithinWord + entry.bearingX;
245
+ const quadY = baselineY - entry.bearingY;
246
+ let regionId = context.regionByCodepoint.get(glyph.codepoint);
247
+ if (regionId === undefined) {
248
+ addTextureAtlasRegion(context.page.atlas, entry.x, entry.y, entry.width, entry.height);
249
+ regionId = context.page.atlas.regions.length - 1;
250
+ context.regionByCodepoint.set(glyph.codepoint, regionId);
251
+ }
252
+ appendBitmapTextPageQuad(context.page, regionId, quadX, quadY);
253
+ if (quadX < minX)
254
+ minX = quadX;
255
+ if (quadY < minY)
256
+ minY = quadY;
257
+ if (quadX + entry.width > maxX)
258
+ maxX = quadX + entry.width;
259
+ if (quadY + entry.height > maxY)
260
+ maxY = quadY + entry.height;
261
+ }
262
+ penX += word.width;
263
+ }
264
+ }
265
+ if (minX === Infinity) {
266
+ setEmptyRectangle(bounds);
267
+ }
268
+ else {
269
+ bounds.x = minX;
270
+ bounds.y = minY;
271
+ bounds.width = maxX - minX;
272
+ bounds.height = maxY - minY;
273
+ }
274
+ invalidateNodeLocalBounds(bitmapText);
275
+ }
223
276
  function maxLineWidth(lines) {
224
277
  let max = 0;
225
278
  for (const line of lines)
@@ -233,4 +286,14 @@ function setEmptyRectangle(out) {
233
286
  out.width = 0;
234
287
  out.height = 0;
235
288
  }
289
+ // How many times `updateBitmapText` re-runs a layout that a repack invalidated under it. Two would
290
+ // leave no room to distinguish "the first pass warmed the atlas" from "this string cannot settle";
291
+ // three is one full pass past a warm atlas, and every pass past that is the same evict-repack cycle.
292
+ const BITMAP_TEXT_LAYOUT_ATTEMPTS = 3;
293
+ // Two floats (x, y) per glyph quad — the vector2 (translation-only) transform stride the BitmapText
294
+ // renderer reads. Kept internal so callers never hand-write i*2.
295
+ const BITMAP_TEXT_TRANSFORM_STRIDE = 2;
296
+ const CARRIAGE_RETURN = 0x0d;
297
+ const SPACE = 0x20;
298
+ let _layoutGuard = null;
236
299
  //# sourceMappingURL=updateBitmapText.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"updateBitmapText.js","sourceRoot":"","sources":["../src/updateBitmapText.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"updateBitmapText.js","sourceRoot":"","sources":["../src/updateBitmapText.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/bitmaptext",
3
- "version": "0.5.1-next.597.ab004dd",
3
+ "version": "0.5.1-next.638.d4dbd4e",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,12 +38,13 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/scene2d": "0.5.1-next.597.ab004dd",
42
- "@flighthq/geometry": "0.5.1-next.597.ab004dd",
43
- "@flighthq/node": "0.5.1-next.597.ab004dd",
44
- "@flighthq/texture": "0.5.1-next.597.ab004dd",
45
- "@flighthq/textureatlas": "0.5.1-next.597.ab004dd",
46
- "@flighthq/types": "0.5.1-next.597.ab004dd"
41
+ "@flighthq/scene2d": "0.5.1-next.638.d4dbd4e",
42
+ "@flighthq/geometry": "0.5.1-next.638.d4dbd4e",
43
+ "@flighthq/log": "0.5.1-next.638.d4dbd4e",
44
+ "@flighthq/node": "0.5.1-next.638.d4dbd4e",
45
+ "@flighthq/texture": "0.5.1-next.638.d4dbd4e",
46
+ "@flighthq/textureatlas": "0.5.1-next.638.d4dbd4e",
47
+ "@flighthq/types": "0.5.1-next.638.d4dbd4e"
47
48
  },
48
49
  "devDependencies": {
49
50
  "@flighthq/glyphatlas": "*",
@@ -11,6 +11,7 @@ import {
11
11
  createBitmapTextRuntime,
12
12
  getBitmapTextBounds,
13
13
  getBitmapTextPages,
14
+ isBitmapTextGlyphLayoutStale,
14
15
  reserveBitmapText,
15
16
  setBitmapTextAlign,
16
17
  setBitmapTextGlyphSource,
@@ -35,6 +36,7 @@ function createTestGlyphSource(): GlyphSource {
35
36
  getGlyphAtlasImage: (page = 0) => (page === 0 ? image : null),
36
37
  getGlyphEntry: (cp) => entries.get(cp) ?? null,
37
38
  getGlyphKerning: () => 0,
39
+ getGlyphLayoutVersion: () => 0,
38
40
  getGlyphMetrics: () => ({ ascent: 8, descent: 2, lineGap: 0 }),
39
41
  };
40
42
  }
@@ -125,6 +127,8 @@ describe('createBitmapTextRuntime', () => {
125
127
  const runtime = createBitmapTextRuntime();
126
128
  expect(runtime.localBoundsRectangle).toBeNull();
127
129
  expect(runtime.pages).toEqual([]);
130
+ // Below every real version, so a node that has never been laid out reads stale.
131
+ expect(runtime.glyphLayoutVersion).toBe(-1);
128
132
  });
129
133
  });
130
134
 
@@ -149,6 +153,34 @@ describe('getBitmapTextPages', () => {
149
153
  });
150
154
  });
151
155
 
156
+ describe('isBitmapTextGlyphLayoutStale', () => {
157
+ it('reads stale before the first layout and settled after it', () => {
158
+ const text = createBitmapText(createTestGlyphSource(), { text: 'AB' });
159
+ expect(isBitmapTextGlyphLayoutStale(text)).toBe(true);
160
+ updateBitmapText(text);
161
+ expect(isBitmapTextGlyphLayoutStale(text)).toBe(false);
162
+ });
163
+
164
+ // A node with nothing bound has no rects to go stale, so it must not report work to do — otherwise
165
+ // a per-frame refresh would re-lay-out every empty node in the scene forever.
166
+ it('reads settled for a node with no glyph source', () => {
167
+ expect(isBitmapTextGlyphLayoutStale(createBitmapText(null, { text: 'AB' }))).toBe(false);
168
+ });
169
+
170
+ // Version numbering is per source, so a stamp from one source says nothing about another and this
171
+ // query does NOT detect a rebind: two sources at version 0 compare equal. That is why
172
+ // `setBitmapTextGlyphSource` documents an explicit `updateBitmapText` afterwards rather than leaving
173
+ // the rebind to the refresh path. Asserted so a later reader does not mistake the gap for coverage.
174
+ it('does not report a rebound glyph source as stale', () => {
175
+ const text = createBitmapText(createTestGlyphSource(), { text: 'AB' });
176
+ updateBitmapText(text);
177
+
178
+ setBitmapTextGlyphSource(text, createTestGlyphSource());
179
+
180
+ expect(isBitmapTextGlyphLayoutStale(text)).toBe(false);
181
+ });
182
+ });
183
+
152
184
  describe('reserveBitmapText', () => {
153
185
  it('grows each page quad-array capacity', () => {
154
186
  const text = createBitmapText(createTestGlyphSource());
@@ -0,0 +1,77 @@
1
+ import { createGlyphAtlas, createGlyphSourceFromGlyphAtlas } from '@flighthq/glyphatlas/contract';
2
+ import { setLogSink } from '@flighthq/log/contract';
3
+ import type { GlyphAtlas, GlyphRasterizerBackend, LogEntry } from '@flighthq/types/contract';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+
6
+ import { createBitmapText } from './bitmapText';
7
+ import { disableBitmapTextGuards, enableBitmapTextGuards } from './enableBitmapTextGuards';
8
+ import { updateBitmapText } from './updateBitmapText';
9
+
10
+ let entries: LogEntry[];
11
+
12
+ beforeEach(() => {
13
+ entries = [];
14
+ setLogSink((entry) => entries.push(entry));
15
+ });
16
+
17
+ afterEach(() => {
18
+ disableBitmapTextGuards();
19
+ setLogSink(null);
20
+ });
21
+
22
+ function messages(): string {
23
+ return entries.map((e) => String((e.data as { message?: unknown } | undefined)?.message ?? '')).join('\n');
24
+ }
25
+
26
+ // Holds exactly nine 8x8 glyphs, so a string of more distinct characters than that can never have all
27
+ // of them resident at once: laying out its tail evicts its head, every pass invalidates the last, and
28
+ // the layout cannot settle however many passes it is given.
29
+ function createTinyGlyphAtlas(): GlyphAtlas {
30
+ const backend: GlyphRasterizerBackend = {
31
+ rasterize: () => ({
32
+ advance: 8,
33
+ bearingX: 0,
34
+ bearingY: 8,
35
+ height: 8,
36
+ pixels: new Uint8ClampedArray(8 * 8 * 4),
37
+ width: 8,
38
+ }),
39
+ };
40
+ return createGlyphAtlas({
41
+ fontFamily: 'block',
42
+ fontSize: 8,
43
+ height: 32,
44
+ padding: 1,
45
+ rasterizerBackend: backend,
46
+ width: 32,
47
+ });
48
+ }
49
+
50
+ describe('disableBitmapTextGuards', () => {
51
+ it('stops the guard reporting later layouts', () => {
52
+ enableBitmapTextGuards();
53
+ disableBitmapTextGuards();
54
+ const source = createGlyphSourceFromGlyphAtlas(createTinyGlyphAtlas());
55
+ updateBitmapText(createBitmapText(source, { text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }));
56
+ expect(messages()).toBe('');
57
+ });
58
+ });
59
+
60
+ describe('enableBitmapTextGuards', () => {
61
+ it('says nothing for a layout that settles', () => {
62
+ enableBitmapTextGuards();
63
+ const source = createGlyphSourceFromGlyphAtlas(createTinyGlyphAtlas());
64
+ updateBitmapText(createBitmapText(source, { text: 'ABC' }));
65
+ expect(messages()).toBe('');
66
+ });
67
+
68
+ // Without this the failure is silent and looks like a rendering bug: the text draws, every quad is
69
+ // where layout put it, and the pixels under them belong to other glyphs.
70
+ it('warns when a string needs more glyphs at once than the atlas can hold', () => {
71
+ enableBitmapTextGuards();
72
+ const source = createGlyphSourceFromGlyphAtlas(createTinyGlyphAtlas());
73
+ updateBitmapText(createBitmapText(source, { text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }));
74
+ expect(messages()).toContain('never settled');
75
+ expect(messages()).toContain('Enlarge the glyph atlas');
76
+ });
77
+ });
@@ -2,14 +2,22 @@ import {
2
2
  createGlyphAtlas,
3
3
  createGlyphSourceFromGlyphAtlas,
4
4
  createStubGlyphRasterizerBackend,
5
+ getGlyphAtlasBitmap,
5
6
  setGlyphRasterizerBackend,
6
7
  } from '@flighthq/glyphatlas/contract';
7
8
  import { getTextureSource } from '@flighthq/texture/contract';
8
- import type { GlyphEntry, GlyphSource, Image } from '@flighthq/types/contract';
9
- import { describe, expect, it } from 'vitest';
9
+ import type {
10
+ Bitmap,
11
+ GlyphAtlas,
12
+ GlyphEntry,
13
+ GlyphRasterizerBackend,
14
+ GlyphSource,
15
+ Image,
16
+ } from '@flighthq/types/contract';
17
+ import { afterEach, describe, expect, it } from 'vitest';
10
18
 
11
- import { createBitmapText, getBitmapTextBounds, getBitmapTextPages } from './bitmapText';
12
- import { updateBitmapText } from './updateBitmapText';
19
+ import { createBitmapText, getBitmapTextBounds, getBitmapTextPages, isBitmapTextGlyphLayoutStale } from './bitmapText';
20
+ import { refreshBitmapTextGlyphLayout, setBitmapTextLayoutGuard, updateBitmapText } from './updateBitmapText';
13
21
 
14
22
  // A deterministic single-page glyph source: every visible glyph is 6x8 with advance 10 and bearingY 8
15
23
  // (so line tops sit at y=0), a space advances 5 with no pixels, and the pair (A, B) kerns by -2. All
@@ -28,6 +36,7 @@ function createTestGlyphSource(): GlyphSource {
28
36
  getGlyphAtlasImage: (page = 0) => (page === 0 ? image : null),
29
37
  getGlyphEntry: (cp) => entries.get(cp) ?? null,
30
38
  getGlyphKerning: (l, r) => kerning.get((l << 16) | r) ?? 0,
39
+ getGlyphLayoutVersion: () => 0,
31
40
  getGlyphMetrics: () => ({ ascent: 8, descent: 2, lineGap: 0 }),
32
41
  };
33
42
  }
@@ -44,12 +53,234 @@ function createTwoPageGlyphSource(): { source: GlyphSource; page0Image: Image; p
44
53
  getGlyphAtlasImage: (page = 0) => (page === 0 ? page0Image : page === 1 ? page1Image : null),
45
54
  getGlyphEntry: (cp) => entries.get(cp) ?? null,
46
55
  getGlyphKerning: () => 0,
56
+ getGlyphLayoutVersion: () => 0,
47
57
  getGlyphMetrics: () => ({ ascent: 8, descent: 2, lineGap: 0 }),
48
58
  };
49
59
  return { source, page0Image, page1Image };
50
60
  }
51
61
 
62
+ // Rasterizes every glyph as a solid block whose RED channel IS the codepoint, so a rect in the atlas
63
+ // bitmap identifies which glyph actually occupies it. That is the oracle this file needs: after a
64
+ // repack every rect is still a well-formed rect over real pixels, so only reading the pixels can tell a
65
+ // correct region from one that now covers the wrong glyph. Codepoints stay under 0x100 for the channel
66
+ // to hold them exactly.
67
+ function createCodepointColorRasterizerBackend(size: number): GlyphRasterizerBackend {
68
+ return {
69
+ rasterize(codepoint) {
70
+ const pixels = new Uint8ClampedArray(size * size * 4);
71
+ for (let i = 0; i < size * size; i++) {
72
+ pixels[i * 4] = codepoint;
73
+ pixels[i * 4 + 3] = 0xff;
74
+ }
75
+ return { advance: size, bearingX: 0, bearingY: size, height: size, pixels, width: size };
76
+ },
77
+ };
78
+ }
79
+
80
+ // The codepoint whose pixels sit at `region`'s top-left in the atlas bitmap, per the rasterizer above.
81
+ function readGlyphCodepointAtRegion(bitmap: Readonly<Bitmap>, x: number, y: number): number {
82
+ return bitmap.data[(y * bitmap.width + x) * 4];
83
+ }
84
+
85
+ // A source that REPLACES 'A' with a relocated entry and bumps its layout version once, on the `nth`
86
+ // glyph lookup — a repack landing part way through a layout, driven at the seam so the moment is exact.
87
+ //
88
+ // Replacing the object rather than moving the one already handed out is the whole point. A repack that
89
+ // only relocates survivors mutates their entries in place, so a pass that captured those references
90
+ // picks the move up for free and never needs re-running. The case that genuinely needs it is a repack
91
+ // that DROPS a glyph: the reference the pass captured is orphaned at its old rect, and the next lookup
92
+ // mints a fresh entry somewhere else. `nth: 0` never fires, for the held-still half of the pair.
93
+ function createMidLayoutRelocatingGlyphSource(nth: number): GlyphSource {
94
+ const image = {} as Image;
95
+ const entries = new Map<number, GlyphEntry>();
96
+ for (let codepoint = 0x41; codepoint <= 0x5a; codepoint++) {
97
+ entries.set(codepoint, { advance: 10, bearingX: 0, bearingY: 8, height: 8, page: 0, width: 6, x: 0, y: 0 });
98
+ }
99
+ let lookups = 0;
100
+ let version = 0;
101
+ return {
102
+ getGlyphAtlasImage: (page = 0) => (page === 0 ? image : null),
103
+ getGlyphEntry(codepoint) {
104
+ lookups++;
105
+ if (lookups === nth) {
106
+ entries.set(0x41, { ...entries.get(0x41)!, x: RELOCATED_GLYPH_X });
107
+ version++;
108
+ }
109
+ return entries.get(codepoint) ?? null;
110
+ },
111
+ getGlyphKerning: () => 0,
112
+ getGlyphLayoutVersion: () => version,
113
+ getGlyphMetrics: () => ({ ascent: 8, descent: 2, lineGap: 0 }),
114
+ };
115
+ }
116
+
117
+ // An atlas small enough that a modest run of glyphs exhausts it and forces the evict-then-repack path
118
+ // that relocates everything already cached — the situation `refreshBitmapTextGlyphLayout` exists for.
119
+ function createRepackingGlyphAtlas(): GlyphAtlas {
120
+ return createGlyphAtlas({
121
+ fontFamily: 'codepoint-color',
122
+ fontSize: 8,
123
+ height: 32,
124
+ padding: 1,
125
+ rasterizerBackend: createCodepointColorRasterizerBackend(8),
126
+ width: 32,
127
+ });
128
+ }
129
+
130
+ describe('refreshBitmapTextGlyphLayout', () => {
131
+ // The defect this whole seam exists for: a repack relocates and re-uses atlas space, so a node laid
132
+ // out BEFORE it keeps regions that now cover other glyphs. Nothing about the node, the entry object,
133
+ // or the atlas image changes to reveal it — the pixels under the baked rect are the only witness.
134
+ it('re-bakes regions that a repack left covering the wrong glyphs', () => {
135
+ const atlas = createRepackingGlyphAtlas();
136
+ const source = createGlyphSourceFromGlyphAtlas(atlas);
137
+ const text = createBitmapText(source, { text: 'A' });
138
+ updateBitmapText(text);
139
+
140
+ const baked = getBitmapTextPages(text)[0].atlas.regions[0];
141
+ const bitmap = getGlyphAtlasBitmap(atlas);
142
+ expect(readGlyphCodepointAtRegion(bitmap, baked.x, baked.y)).toBe(0x41);
143
+
144
+ // Fill the atlas from elsewhere — a second text node, a direct lookup, any other consumer of the
145
+ // same atlas would do. 'A' is the least recently used, so it is evicted and its space handed on.
146
+ for (let codepoint = 0x42; codepoint <= 0x5a; codepoint++) source.getGlyphEntry(codepoint);
147
+
148
+ expect(isBitmapTextGlyphLayoutStale(text)).toBe(true);
149
+ expect(readGlyphCodepointAtRegion(bitmap, baked.x, baked.y)).not.toBe(0x41);
150
+
151
+ expect(refreshBitmapTextGlyphLayout(text)).toBe(true);
152
+ const rebaked = getBitmapTextPages(text)[0].atlas.regions[0];
153
+ expect(readGlyphCodepointAtRegion(bitmap, rebaked.x, rebaked.y)).toBe(0x41);
154
+ expect(isBitmapTextGlyphLayoutStale(text)).toBe(false);
155
+ });
156
+
157
+ it('does nothing and reports nothing done when the glyph placement has not moved', () => {
158
+ const atlas = createRepackingGlyphAtlas();
159
+ const text = createBitmapText(createGlyphSourceFromGlyphAtlas(atlas), { text: 'A' });
160
+ updateBitmapText(text);
161
+ const before = getBitmapTextPages(text)[0].atlas.regions[0];
162
+
163
+ expect(refreshBitmapTextGlyphLayout(text)).toBe(false);
164
+ expect(getBitmapTextPages(text)[0].atlas.regions[0]).toBe(before);
165
+ });
166
+
167
+ it('lays out a node that has never been laid out', () => {
168
+ const atlas = createRepackingGlyphAtlas();
169
+ const text = createBitmapText(createGlyphSourceFromGlyphAtlas(atlas), { text: 'A' });
170
+
171
+ expect(refreshBitmapTextGlyphLayout(text)).toBe(true);
172
+ expect(getBitmapTextPages(text)[0].instanceCount).toBe(1);
173
+ });
174
+
175
+ // Fan-out is the reason the seam versions rather than the consumer comparing rects: one atlas backs
176
+ // every node bound to it, and the node that triggered the repack must not be the only one repaired.
177
+ it('repairs every node sharing the atlas, not only the one that forced the repack', () => {
178
+ const atlas = createRepackingGlyphAtlas();
179
+ const source = createGlyphSourceFromGlyphAtlas(atlas);
180
+ const first = createBitmapText(source, { text: 'A' });
181
+ updateBitmapText(first);
182
+
183
+ const second = createBitmapText(source, { text: 'BCDEFGHIJKLMNOPQRSTUVWXYZ' });
184
+ updateBitmapText(second);
185
+
186
+ expect(isBitmapTextGlyphLayoutStale(first)).toBe(true);
187
+ refreshBitmapTextGlyphLayout(first);
188
+ const bitmap = getGlyphAtlasBitmap(atlas);
189
+ const region = getBitmapTextPages(first)[0].atlas.regions[0];
190
+ expect(readGlyphCodepointAtRegion(bitmap, region.x, region.y)).toBe(0x41);
191
+ });
192
+ });
193
+
194
+ describe('setBitmapTextLayoutGuard', () => {
195
+ afterEach(() => setBitmapTextLayoutGuard(null));
196
+
197
+ it('reports the reason and the passes spent when a layout cannot settle', () => {
198
+ const seen: [string, number][] = [];
199
+ setBitmapTextLayoutGuard((reason, attempts) => seen.push([reason, attempts]));
200
+ const source = createGlyphSourceFromGlyphAtlas(createRepackingGlyphAtlas());
201
+
202
+ updateBitmapText(createBitmapText(source, { text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }));
203
+
204
+ expect(seen).toEqual([['layout-did-not-converge', 3]]);
205
+ });
206
+
207
+ it('stays silent for a layout that settles', () => {
208
+ const seen: string[] = [];
209
+ setBitmapTextLayoutGuard((reason) => seen.push(reason));
210
+ const source = createGlyphSourceFromGlyphAtlas(createRepackingGlyphAtlas());
211
+
212
+ updateBitmapText(createBitmapText(source, { text: 'AB' }));
213
+
214
+ expect(seen).toEqual([]);
215
+ });
216
+
217
+ it('stops reporting once cleared with null', () => {
218
+ let calls = 0;
219
+ setBitmapTextLayoutGuard(() => (calls += 1));
220
+ const source = createGlyphSourceFromGlyphAtlas(createRepackingGlyphAtlas());
221
+
222
+ updateBitmapText(createBitmapText(source, { text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }));
223
+ setBitmapTextLayoutGuard(null);
224
+ updateBitmapText(createBitmapText(source, { text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }));
225
+
226
+ expect(calls).toBe(1);
227
+ });
228
+ });
229
+
52
230
  describe('updateBitmapText', () => {
231
+ // A repack can land DURING a layout — a glyph late in the string evicting one placed early in it —
232
+ // and the pass that saw both placements is wrong about the first. The retry is what makes a single
233
+ // `updateBitmapText` call self-consistent rather than merely detectably stale afterwards.
234
+ // A pass that read a rect BEFORE the relocation and one that read it after cannot both be right, and
235
+ // the pass has no way to go back for the ones it already consumed. Re-running is what makes a single
236
+ // `updateBitmapText` call self-consistent rather than merely detectably stale afterwards.
237
+ it('re-runs a layout whose glyph placement moved part way through it', () => {
238
+ const source = createMidLayoutRelocatingGlyphSource(2);
239
+ const text = createBitmapText(source, { text: 'ABC' });
240
+
241
+ updateBitmapText(text);
242
+
243
+ // 'A' is the first region baked, and the relocation landed after the pass had already read it.
244
+ expect(getBitmapTextPages(text)[0].atlas.regions[0].x).toBe(RELOCATED_GLYPH_X);
245
+ expect(isBitmapTextGlyphLayoutStale(text)).toBe(false);
246
+ });
247
+
248
+ it('does not re-run a layout whose glyph placement held still', () => {
249
+ const source = createMidLayoutRelocatingGlyphSource(0);
250
+ const text = createBitmapText(source, { text: 'ABC' });
251
+ const seen: string[] = [];
252
+ setBitmapTextLayoutGuard((reason) => seen.push(reason));
253
+
254
+ updateBitmapText(text);
255
+
256
+ expect(getBitmapTextPages(text)[0].atlas.regions[0].x).toBe(0);
257
+ expect(seen).toEqual([]);
258
+ setBitmapTextLayoutGuard(null);
259
+ });
260
+
261
+ // The end-to-end companion to the seam-driven pair above, over the real atlas. It passes without the
262
+ // retry as well — this atlas only relocates, and relocation moves the entry the pass is holding — so
263
+ // it is here to prove the whole path stays coherent, not to exercise the re-run.
264
+ it('ends coherent through an atlas that repacks during the layout', () => {
265
+ const atlas = createRepackingGlyphAtlas();
266
+ const source = createGlyphSourceFromGlyphAtlas(atlas);
267
+ // Fill most of the atlas with glyphs this text does not use, so laying it out has to evict and
268
+ // repack partway through — over the glyphs the same pass already placed.
269
+ for (let codepoint = 0x61; codepoint <= 0x66; codepoint++) source.getGlyphEntry(codepoint);
270
+ const text = createBitmapText(source, { text: 'ABCDEF' });
271
+ updateBitmapText(text);
272
+
273
+ const bitmap = getGlyphAtlasBitmap(atlas);
274
+ const page = getBitmapTextPages(text)[0];
275
+ expect(page.instanceCount).toBeGreaterThan(0);
276
+ for (let id = 0; id < page.atlas.regions.length; id++) {
277
+ const region = page.atlas.regions[id];
278
+ const codepoint = readGlyphCodepointAtRegion(bitmap, region.x, region.y);
279
+ expect(codepoint).toBe(0x41 + id);
280
+ }
281
+ expect(isBitmapTextGlyphLayoutStale(text)).toBe(false);
282
+ });
283
+
53
284
  it('emits non-empty glyph quads end-to-end from a stub-fed glyph atlas (headless, issue #8)', () => {
54
285
  // The real headless path: glyphatlas → GlyphSource → bitmaptext, with the deterministic stub
55
286
  // rasterizer standing in for a web font that is unavailable in jsdom/CI. Proves BitmapText renders
@@ -246,3 +477,7 @@ describe('updateBitmapText', () => {
246
477
  expect(bounds.height).toBe(8);
247
478
  });
248
479
  });
480
+
481
+ // Where the mid-layout relocation moves 'A' to — distinct from every rect the source starts with, so
482
+ // a region carrying it can only have come from a pass that ran AFTER the move.
483
+ const RELOCATED_GLYPH_X = 17;