@flighthq/glyphatlas 0.2.0 → 0.2.1-next.410.a23f189

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.
@@ -1,4 +1,5 @@
1
1
  import type { GlyphRasterizerBackend } from '@flighthq/types';
2
+ export declare function createStubGlyphRasterizerBackend(): GlyphRasterizerBackend;
2
3
  export declare function createWebGlyphRasterizerBackend(): GlyphRasterizerBackend;
3
4
  export declare function getGlyphRasterizerBackend(): GlyphRasterizerBackend;
4
5
  export declare function setGlyphRasterizerBackend(backend: GlyphRasterizerBackend | null): void;
@@ -1 +1 @@
1
- {"version":3,"file":"glyphRasterizerBackend.d.ts","sourceRoot":"","sources":["../src/glyphRasterizerBackend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgD,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAQ5G,wBAAgB,+BAA+B,IAAI,sBAAsB,CAQxE;AAID,wBAAgB,yBAAyB,IAAI,sBAAsB,CAGlE;AAGD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,GAAG,IAAI,CAEtF"}
1
+ {"version":3,"file":"glyphRasterizerBackend.d.ts","sourceRoot":"","sources":["../src/glyphRasterizerBackend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgD,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAQ5G,wBAAgB,gCAAgC,IAAI,sBAAsB,CAmBzE;AAQD,wBAAgB,+BAA+B,IAAI,sBAAsB,CAQxE;AAID,wBAAgB,yBAAyB,IAAI,sBAAsB,CAGlE;AAGD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,GAAG,IAAI,CAEtF"}
@@ -1,3 +1,29 @@
1
+ // Builds a deterministic, font-independent rasterizer backend for headless/jsdom use. Every codepoint
2
+ // rasterizes to a solid opaque-white box sized from the requested `fontSize` (never a font lookup, never
3
+ // a canvas), so `updateBitmapText`/`getGlyphAtlasEntry` produce non-blank glyphs with no `FontFace`
4
+ // loaded and no `whenFontsReady()` await. Install it with `setGlyphRasterizerBackend`. This is the
5
+ // test/CI sibling of the web backend, using the same swappable seam a native host replaces — not a
6
+ // production text renderer (every glyph is the same box; there are no real outlines).
7
+ export function createStubGlyphRasterizerBackend() {
8
+ return {
9
+ rasterize(_codepoint, options) {
10
+ const size = Math.max(1, Math.round(options.fontSize));
11
+ const width = Math.max(1, Math.round(size * 0.6));
12
+ const height = Math.max(1, Math.round(size * 0.7));
13
+ // Fully opaque white so a text renderer can tint the box; straight-alpha RGBA, width*height*4.
14
+ const pixels = new Uint8ClampedArray(width * height * 4);
15
+ pixels.fill(255);
16
+ return {
17
+ advance: width + Math.max(1, Math.round(size * 0.1)),
18
+ bearingX: 0,
19
+ bearingY: height,
20
+ height,
21
+ pixels,
22
+ width,
23
+ };
24
+ },
25
+ };
26
+ }
1
27
  // Builds the default web backend, which rasterizes a glyph on an offscreen canvas: it sets the font,
2
28
  // measures the glyph box + advance with `measureText`, fills the glyph with `fillText`, and reads the
3
29
  // pixels back with `getImageData`. Nothing touches the DOM at construction time — the canvas is
@@ -1 +1 @@
1
- {"version":3,"file":"glyphRasterizerBackend.js","sourceRoot":"","sources":["../src/glyphRasterizerBackend.ts"],"names":[],"mappings":"AAEA,qGAAqG;AACrG,sGAAsG;AACtG,gGAAgG;AAChG,kGAAkG;AAClG,kGAAkG;AAClG,qGAAqG;AACrG,MAAM,UAAU,+BAA+B;IAC7C,OAAO;QACL,SAAS,CAAC,SAAS,EAAE,OAAO;YAC1B,MAAM,OAAO,GAAG,0BAA0B,EAAE,CAAC;YAC7C,IAAI,OAAO,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAClC,OAAO,wBAAwB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAC/D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,yEAAyE;AACzE,MAAM,UAAU,yBAAyB;IACvC,IAAI,QAAQ,KAAK,IAAI;QAAE,QAAQ,GAAG,+BAA+B,EAAE,CAAC;IACpE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,yBAAyB,CAAC,OAAsC;IAC9E,QAAQ,GAAG,OAAO,CAAC;AACrB,CAAC;AAED,IAAI,QAAQ,GAAkC,IAAI,CAAC;AAEnD,oGAAoG;AACpG,oGAAoG;AACpG,qGAAqG;AACrG,SAAS,0BAA0B;IACjC,IAAI,CAAC;QACH,IAAI,OAAO,eAAe,KAAK,WAAW,EAAE,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC3D,IAAI,OAAO,KAAK,IAAI;gBAAE,OAAO,OAAO,CAAC;QACvC,CAAC;QACD,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,OAAO,QAAQ,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;YACpF,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAClE,IAAI,OAAO,KAAK,IAAI;gBAAE,OAAO,OAAO,CAAC;QACvC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oGAAoG;AACpG,qGAAqG;AACrG,oGAAoG;AACpG,sGAAsG;AACtG,SAAS,wBAAwB,CAC/B,OAAqE,EACrE,SAAiB,EACjB,OAAwC;IAExC,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAC;IAChD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,QAAQ,CAAC;IAClD,OAAO,CAAC,IAAI,GAAG,GAAG,SAAS,IAAI,UAAU,IAAI,OAAO,CAAC,QAAQ,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;IACxF,OAAO,CAAC,YAAY,GAAG,YAAY,CAAC;IACpC,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC;IAE3B,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,qBAAqB,IAAI,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,sBAAsB,IAAI,OAAO,CAAC;IACxD,MAAM,MAAM,GAAG,OAAO,CAAC,uBAAuB,IAAI,OAAO,CAAC,QAAQ,CAAC;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,wBAAwB,IAAI,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,CAAC,CAAC;IAChB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IACpE,IAAI,KAAK,IAAI,KAAK,GAAG,CAAC,IAAI,MAAM,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,wEAAwE;IACxE,OAAO,CAAC,IAAI,GAAG,GAAG,SAAS,IAAI,UAAU,IAAI,OAAO,CAAC,QAAQ,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;IACxF,OAAO,CAAC,YAAY,GAAG,YAAY,CAAC;IACpC,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACvC,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;IAErD,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACxD,OAAO;QACL,OAAO;QACP,QAAQ,EAAE,CAAC,IAAI;QACf,QAAQ,EAAE,MAAM;QAChB,MAAM;QACN,MAAM,EAAE,IAAI,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC;QACzC,KAAK;KACN,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"glyphRasterizerBackend.js","sourceRoot":"","sources":["../src/glyphRasterizerBackend.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/glyphatlas",
3
- "version": "0.2.0",
3
+ "version": "0.2.1-next.410.a23f189",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/flighthq/flight.git",
@@ -32,9 +32,9 @@
32
32
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@flighthq/geometry": "0.2.0",
36
- "@flighthq/surface": "0.2.0",
37
- "@flighthq/types": "0.2.0"
35
+ "@flighthq/geometry": "0.2.1-next.410.a23f189",
36
+ "@flighthq/surface": "0.2.1-next.410.a23f189",
37
+ "@flighthq/types": "0.2.1-next.410.a23f189"
38
38
  },
39
39
  "devDependencies": {
40
40
  "typescript": "^5.3.0"
@@ -4,11 +4,29 @@ import { afterEach, describe, expect, it } from 'vitest';
4
4
 
5
5
  import { createGlyphAtlas, getGlyphAtlasSurface } from './glyphAtlas';
6
6
  import { getGlyphAtlasEntry } from './glyphAtlasEntry';
7
- import { setGlyphRasterizerBackend } from './glyphRasterizerBackend';
7
+ import { createStubGlyphRasterizerBackend, setGlyphRasterizerBackend } from './glyphRasterizerBackend';
8
8
 
9
9
  describe('getGlyphAtlasEntry', () => {
10
10
  afterEach(() => setGlyphRasterizerBackend(null));
11
11
 
12
+ it('produces a non-blank glyph in a headless env with the stub backend (issue #8)', () => {
13
+ // The web backend returns null in jsdom (no canvas) and renders blank in headless Chromium when
14
+ // the FontFace has not loaded. The stub backend is font- and canvas-independent, so this is the
15
+ // deterministic headless render path.
16
+ setGlyphRasterizerBackend(createStubGlyphRasterizerBackend());
17
+ const atlas = createGlyphAtlas({ fontFamily: 'unavailable', fontSize: 32, height: 256, width: 256 });
18
+
19
+ const entry = getGlyphAtlasEntry(atlas, 0x41)!;
20
+ expect(entry).not.toBeNull();
21
+ expect(entry.width).toBeGreaterThan(0);
22
+ expect(entry.height).toBeGreaterThan(0);
23
+
24
+ // A pixel inside the glyph rect is opaque white — the atlas surface is provably not blank.
25
+ const surface = getGlyphAtlasSurface(atlas);
26
+ const inside = getSurfacePixel(surface, entry.x + 1, entry.y + 1);
27
+ expect(inside & 0xff).toBe(0xff);
28
+ });
29
+
12
30
  it('rasterizes a missing glyph once and caches it', () => {
13
31
  const { backend, calls } = createMockRasterizerBackend();
14
32
  setGlyphRasterizerBackend(backend);
@@ -2,11 +2,46 @@ import type { GlyphRasterizerBackend } from '@flighthq/types';
2
2
  import { afterEach, describe, expect, it } from 'vitest';
3
3
 
4
4
  import {
5
+ createStubGlyphRasterizerBackend,
5
6
  createWebGlyphRasterizerBackend,
6
7
  getGlyphRasterizerBackend,
7
8
  setGlyphRasterizerBackend,
8
9
  } from './glyphRasterizerBackend';
9
10
 
11
+ describe('createStubGlyphRasterizerBackend', () => {
12
+ it('emits a non-blank opaque-white box for any codepoint without a font or canvas', () => {
13
+ const backend = createStubGlyphRasterizerBackend();
14
+ const bitmap = backend.rasterize(65, { fontFamily: 'missing-font', fontSize: 20 })!;
15
+
16
+ expect(bitmap).not.toBeNull();
17
+ expect(bitmap.width).toBeGreaterThan(0);
18
+ expect(bitmap.height).toBeGreaterThan(0);
19
+ expect(bitmap.pixels.length).toBe(bitmap.width * bitmap.height * 4);
20
+ // Every byte is 255 → solid opaque white, so the atlas blit is provably non-blank.
21
+ expect(bitmap.pixels.every((v) => v === 255)).toBe(true);
22
+ });
23
+
24
+ it('sizes the box and advance from the requested fontSize', () => {
25
+ const backend = createStubGlyphRasterizerBackend();
26
+ const small = backend.rasterize(65, { fontFamily: 'x', fontSize: 10 })!;
27
+ const large = backend.rasterize(65, { fontFamily: 'x', fontSize: 40 })!;
28
+
29
+ expect(large.width).toBeGreaterThan(small.width);
30
+ expect(large.height).toBeGreaterThan(small.height);
31
+ expect(large.advance).toBeGreaterThan(large.width);
32
+ });
33
+
34
+ it('is deterministic — the same codepoint and size give the same box regardless of font', () => {
35
+ const backend = createStubGlyphRasterizerBackend();
36
+ const a = backend.rasterize(66, { fontFamily: 'a', fontSize: 24 })!;
37
+ const b = backend.rasterize(66, { fontFamily: 'b', fontSize: 24 })!;
38
+
39
+ expect(a.width).toBe(b.width);
40
+ expect(a.height).toBe(b.height);
41
+ expect(a.advance).toBe(b.advance);
42
+ });
43
+ });
44
+
10
45
  describe('createWebGlyphRasterizerBackend', () => {
11
46
  it('constructs a backend without a DOM side effect', () => {
12
47
  expect(typeof createWebGlyphRasterizerBackend().rasterize).toBe('function');