@flighthq/bitmapfont 0.3.0-next.1315.1d4eb64 → 0.3.0-next.1392.f0bd0cc
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/dist/bitmapFontFromGlyphAtlas.d.ts +19 -0
- package/dist/bitmapFontFromGlyphAtlas.d.ts.map +1 -0
- package/dist/bitmapFontFromGlyphAtlas.js +46 -0
- package/dist/bitmapFontFromGlyphAtlas.js.map +1 -0
- package/dist/contract.d.ts +1 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -0
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/bitmapFontFromGlyphAtlas.test.ts +81 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BitmapFont, GlyphAtlas } from '@flighthq/types/contract';
|
|
2
|
+
/** Freezes a live `GlyphAtlas` into a static `BitmapFont` — the dynamic cache's contents as a fixed
|
|
3
|
+
* font, so a build step can bake what a run actually rasterized and ship it instead of rasterizing
|
|
4
|
+
* again at startup.
|
|
5
|
+
*
|
|
6
|
+
* This lives in `@flighthq/bitmapfont` rather than in `@flighthq/glyphatlas` because a conversion
|
|
7
|
+
* constructor belongs to the package that owns the PRODUCT, the same way `createImageResourceFromBitmap`
|
|
8
|
+
* lives in `@flighthq/image`. Every type is in `@flighthq/types`, so reading a `GlyphAtlas` here costs
|
|
9
|
+
* no dependency on the atlas package, and `glyphatlas` gains no edge to `textureatlas` or `texture`.
|
|
10
|
+
*
|
|
11
|
+
* The page is CPU-only: the atlas's own bitmap is already a valid `TextureSource` under the flat
|
|
12
|
+
* texture model, so the font's page is a `TextureAtlas` over a bitmap-sourced `Texture` with no GPU
|
|
13
|
+
* work and no readback. Whichever backend consumes the font uploads it later through the ordinary
|
|
14
|
+
* kind-keyed resolver path, exactly as it would for any other bitmap-sourced texture.
|
|
15
|
+
*
|
|
16
|
+
* The result is a snapshot: later rasterization into the source atlas does not reach the font, and the
|
|
17
|
+
* font holds its own reference to the atlas bitmap. */
|
|
18
|
+
export declare function createBitmapFontFromGlyphAtlas(atlas: Readonly<GlyphAtlas>): BitmapFont;
|
|
19
|
+
//# sourceMappingURL=bitmapFontFromGlyphAtlas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitmapFontFromGlyphAtlas.d.ts","sourceRoot":"","sources":["../src/bitmapFontFromGlyphAtlas.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAuB,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAI5F;;;;;;;;;;;;;;;wDAewD;AACxD,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,CAyBtF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createTexture } from '@flighthq/texture/contract';
|
|
2
|
+
import { createTextureAtlas } from '@flighthq/textureatlas/contract';
|
|
3
|
+
import { createBitmapFont } from './bitmapFont';
|
|
4
|
+
/** Freezes a live `GlyphAtlas` into a static `BitmapFont` — the dynamic cache's contents as a fixed
|
|
5
|
+
* font, so a build step can bake what a run actually rasterized and ship it instead of rasterizing
|
|
6
|
+
* again at startup.
|
|
7
|
+
*
|
|
8
|
+
* This lives in `@flighthq/bitmapfont` rather than in `@flighthq/glyphatlas` because a conversion
|
|
9
|
+
* constructor belongs to the package that owns the PRODUCT, the same way `createImageResourceFromBitmap`
|
|
10
|
+
* lives in `@flighthq/image`. Every type is in `@flighthq/types`, so reading a `GlyphAtlas` here costs
|
|
11
|
+
* no dependency on the atlas package, and `glyphatlas` gains no edge to `textureatlas` or `texture`.
|
|
12
|
+
*
|
|
13
|
+
* The page is CPU-only: the atlas's own bitmap is already a valid `TextureSource` under the flat
|
|
14
|
+
* texture model, so the font's page is a `TextureAtlas` over a bitmap-sourced `Texture` with no GPU
|
|
15
|
+
* work and no readback. Whichever backend consumes the font uploads it later through the ordinary
|
|
16
|
+
* kind-keyed resolver path, exactly as it would for any other bitmap-sourced texture.
|
|
17
|
+
*
|
|
18
|
+
* The result is a snapshot: later rasterization into the source atlas does not reach the font, and the
|
|
19
|
+
* font holds its own reference to the atlas bitmap. */
|
|
20
|
+
export function createBitmapFontFromGlyphAtlas(atlas) {
|
|
21
|
+
const runtime = atlas.runtime;
|
|
22
|
+
const glyphs = [];
|
|
23
|
+
for (const [codepoint, entry] of runtime.entries) {
|
|
24
|
+
glyphs.push({
|
|
25
|
+
advance: entry.advance,
|
|
26
|
+
bearingX: entry.bearingX,
|
|
27
|
+
bearingY: entry.bearingY,
|
|
28
|
+
codepoint,
|
|
29
|
+
height: entry.height,
|
|
30
|
+
// The dynamic atlas is a single growing bitmap, so every glyph is on page 0.
|
|
31
|
+
page: 0,
|
|
32
|
+
width: entry.width,
|
|
33
|
+
x: entry.x,
|
|
34
|
+
y: entry.y,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Regions stay empty: a bitmap font addresses its glyphs through its own glyph table, so the page
|
|
38
|
+
// exists to carry the pixels, not to name sub-rectangles.
|
|
39
|
+
const page = createTextureAtlas({ texture: createTexture({ source: runtime.bitmap }) });
|
|
40
|
+
return createBitmapFont({
|
|
41
|
+
glyphs,
|
|
42
|
+
metrics: { ...runtime.metrics },
|
|
43
|
+
pages: [page],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=bitmapFontFromGlyphAtlas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitmapFontFromGlyphAtlas.js","sourceRoot":"","sources":["../src/bitmapFontFromGlyphAtlas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;wDAewD;AACxD,MAAM,UAAU,8BAA8B,CAAC,KAA2B;IACxE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,MAAM,MAAM,GAA0B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC;YACV,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS;YACT,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,6EAA6E;YAC7E,IAAI,EAAE,CAAC;YACP,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,CAAC,EAAE,KAAK,CAAC,CAAC;YACV,CAAC,EAAE,KAAK,CAAC,CAAC;SACX,CAAC,CAAC;IACL,CAAC;IACD,kGAAkG;IAClG,0DAA0D;IAC1D,MAAM,IAAI,GAAG,kBAAkB,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACxF,OAAO,gBAAgB,CAAC;QACtB,MAAM;QACN,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE;QAC/B,KAAK,EAAE,CAAC,IAAI,CAAC;KACd,CAAC,CAAC;AACL,CAAC"}
|
package/dist/contract.d.ts
CHANGED
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC"}
|
package/dist/contract.js
CHANGED
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createBitmapFont, createGlyphSourceFromBitmapFont, getBitmapFontGlyph, getBitmapFontKerning, getBitmapFontMetrics, getBitmapFontPage, getBitmapFontPages, } from './contract';
|
|
1
|
+
export { createBitmapFontFromGlyphAtlas, createBitmapFont, createGlyphSourceFromBitmapFont, getBitmapFontGlyph, getBitmapFontKerning, getBitmapFontMetrics, getBitmapFontPage, getBitmapFontPages, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,+BAA+B,EAC/B,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,gBAAgB,EAChB,+BAA+B,EAC/B,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createBitmapFont, createGlyphSourceFromBitmapFont, getBitmapFontGlyph, getBitmapFontKerning, getBitmapFontMetrics, getBitmapFontPage, getBitmapFontPages, } from './contract';
|
|
1
|
+
export { createBitmapFontFromGlyphAtlas, createBitmapFont, createGlyphSourceFromBitmapFont, getBitmapFontGlyph, getBitmapFontKerning, getBitmapFontMetrics, getBitmapFontPage, getBitmapFontPages, } 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,gBAAgB,EAChB,+BAA+B,EAC/B,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,gBAAgB,EAChB,+BAA+B,EAC/B,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/bitmapfont",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.1392.f0bd0cc",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,10 +36,12 @@
|
|
|
36
36
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@flighthq/
|
|
40
|
-
"@flighthq/
|
|
39
|
+
"@flighthq/texture": "0.3.0-next.1392.f0bd0cc",
|
|
40
|
+
"@flighthq/textureatlas": "0.3.0-next.1392.f0bd0cc",
|
|
41
|
+
"@flighthq/types": "0.3.0-next.1392.f0bd0cc"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
44
|
+
"@flighthq/glyphatlas": "*",
|
|
43
45
|
"typescript": "^5.3.0"
|
|
44
46
|
},
|
|
45
47
|
"description": "Static bitmap font — an immutable pre-baked atlas + glyph/kerning/metrics tables implementing the GlyphSource seam, the static sibling of the dynamic glyphatlas",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createGlyphAtlas, getGlyphAtlasEntry, setGlyphRasterizerBackend } from '@flighthq/glyphatlas/contract';
|
|
2
|
+
import type { GlyphRasterizerBackend } from '@flighthq/types/contract';
|
|
3
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { getBitmapFontGlyph, getBitmapFontMetrics } from './bitmapFont';
|
|
6
|
+
import { createBitmapFontFromGlyphAtlas } from './bitmapFontFromGlyphAtlas';
|
|
7
|
+
|
|
8
|
+
function backendProducing(width: number, height: number): GlyphRasterizerBackend {
|
|
9
|
+
return {
|
|
10
|
+
measureMetrics: () => ({ ascent: 12, descent: 3, lineGap: 1 }),
|
|
11
|
+
rasterize: () => ({
|
|
12
|
+
advance: width,
|
|
13
|
+
bearingX: 1,
|
|
14
|
+
bearingY: 2,
|
|
15
|
+
height,
|
|
16
|
+
pixels: new Uint8ClampedArray(width * height * 4),
|
|
17
|
+
width,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('createBitmapFontFromGlyphAtlas', () => {
|
|
23
|
+
afterEach(() => setGlyphRasterizerBackend(null));
|
|
24
|
+
|
|
25
|
+
function bakedFromTwoGlyphs() {
|
|
26
|
+
setGlyphRasterizerBackend(backendProducing(8, 8));
|
|
27
|
+
const atlas = createGlyphAtlas({ fontFamily: 'mock', fontSize: 16, height: 64, width: 64 });
|
|
28
|
+
getGlyphAtlasEntry(atlas, 65);
|
|
29
|
+
getGlyphAtlasEntry(atlas, 66);
|
|
30
|
+
return { atlas, font: createBitmapFontFromGlyphAtlas(atlas) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
it('carries every cached glyph across with its rect and pen data', () => {
|
|
34
|
+
const { atlas, font } = bakedFromTwoGlyphs();
|
|
35
|
+
|
|
36
|
+
const source = getGlyphAtlasEntry(atlas, 65)!;
|
|
37
|
+
const baked = getBitmapFontGlyph(font, 65)!;
|
|
38
|
+
|
|
39
|
+
expect(baked).not.toBeNull();
|
|
40
|
+
expect(baked.x).toBe(source.x);
|
|
41
|
+
expect(baked.y).toBe(source.y);
|
|
42
|
+
expect(baked.width).toBe(source.width);
|
|
43
|
+
expect(baked.advance).toBe(source.advance);
|
|
44
|
+
expect(baked.bearingY).toBe(source.bearingY);
|
|
45
|
+
expect(getBitmapFontGlyph(font, 66)).not.toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('carries the atlas metrics rather than re-deriving them', () => {
|
|
49
|
+
const { font } = bakedFromTwoGlyphs();
|
|
50
|
+
expect(getBitmapFontMetrics(font)).toEqual({ ascent: 12, descent: 3, lineGap: 1 });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// The page is CPU-only: the atlas bitmap is already a valid texture source under the flat model, so
|
|
54
|
+
// baking needs no GPU work and no readback.
|
|
55
|
+
it('builds a single page whose texture is sourced from the atlas bitmap', () => {
|
|
56
|
+
const { atlas, font } = bakedFromTwoGlyphs();
|
|
57
|
+
|
|
58
|
+
expect(font.pages).toHaveLength(1);
|
|
59
|
+
expect(font.pages[0]!.texture!.source).toBe(atlas.runtime.bitmap);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// A bake is a snapshot. Rasterizing more glyphs into the source atlas afterwards must not reach the
|
|
63
|
+
// font, or "bake" would just be another view of a live cache.
|
|
64
|
+
it('does not gain glyphs rasterized into the atlas after the bake', () => {
|
|
65
|
+
const { atlas, font } = bakedFromTwoGlyphs();
|
|
66
|
+
|
|
67
|
+
getGlyphAtlasEntry(atlas, 67);
|
|
68
|
+
|
|
69
|
+
expect(getBitmapFontGlyph(font, 67)).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('bakes an empty atlas to a font with no glyphs rather than failing', () => {
|
|
73
|
+
setGlyphRasterizerBackend(backendProducing(8, 8));
|
|
74
|
+
const atlas = createGlyphAtlas({ fontFamily: 'mock', fontSize: 16, height: 64, width: 64 });
|
|
75
|
+
|
|
76
|
+
const font = createBitmapFontFromGlyphAtlas(atlas);
|
|
77
|
+
|
|
78
|
+
expect(getBitmapFontGlyph(font, 65)).toBeNull();
|
|
79
|
+
expect(font.pages).toHaveLength(1);
|
|
80
|
+
});
|
|
81
|
+
});
|