@godot-scene-web/canvas 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyph-pass-DfQlp_IH.d.ts","names":[],"sources":["../src/damage.ts","../src/draw-list.ts","../src/present.ts","../src/glyph-pass.ts"],"mappings":"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"}
@@ -0,0 +1,227 @@
1
+ import { F as GlyphsView, t as GlyphPass } from "./glyph-pass-DfQlp_IH.js";
2
+ import { HbGpuContrast, HbGpuFace, HbGpuRenderer } from "@godot-scene-web/hb-gpu/webgl";
3
+ import { HbGpu, HbGpuFailure, HbGpuFont, HbGpuShapeOptions } from "@godot-scene-web/hb-gpu";
4
+
5
+ //#region src/glyph-pass-hbgpu.d.ts
6
+ /**
7
+ * Below this ppem the outline path is measurably blurrier than the alternatives.
8
+ *
9
+ * `ppem` here is `GlyphsView.pixelsPerEm` times the scale in `GlyphsView.m` times the device-pixel
10
+ * ratio — the size in DEVICE pixels, which is the only size the shader can see, and it sees all
11
+ * three factors (it derives ppem from `fwidth`). The number is HarfBuzz's own branch point, not a
12
+ * threshold chosen here: `hb_gpu_draw` switches to a five-tap MSAA approximation under it.
13
+ */
14
+ declare const PPEM_FIDELITY_FLOOR = 16;
15
+ /**
16
+ * The id `slotFor` returns for a glyph with no ink, or one the atlas declined.
17
+ *
18
+ * Write it into `GlyphsView.slots` and the pass skips the glyph. It is a distinct value ON PURPOSE:
19
+ * a caller that wrote a real-looking id for a space would get a degenerate quad reading texel 0,
20
+ * which is some other glyph's header.
21
+ */
22
+ declare const GLYPH_SLOT_NONE = -1;
23
+ /**
24
+ * A face registered with the pass: the handle `slotFor` takes.
25
+ *
26
+ * It pairs the ENCODER (`HbGpuFont`, which turns a glyph id into an outline blob) with the atlas
27
+ * FACE handle that namespaces that glyph id, because the two must not drift: glyph 42 of Noto Sans
28
+ * SC and glyph 42 of Roboto are unrelated outlines, and an atlas that cannot tell them apart
29
+ * renders fluent, crisp, wrong text.
30
+ */
31
+ interface GlyphFace {
32
+ readonly font: HbGpuFont;
33
+ readonly face: HbGpuFace;
34
+ /** Units per em. The scale `GlyphsView.pixelsPerEm` is measured against. */
35
+ readonly upem: number;
36
+ readonly label: string;
37
+ }
38
+ interface HbGpuGlyphPassStats {
39
+ /** Distinct `(face, glyph)` pairs that have ever been given an id. */
40
+ slots: number;
41
+ /** Runs drawn. */
42
+ runs: number;
43
+ /** Glyphs the renderer reported drawing, summed over runs. */
44
+ glyphs: number;
45
+ /** Glyphs skipped because their slot id was {@link GLYPH_SLOT_NONE} — spaces, mostly. */
46
+ inkless: number;
47
+ /**
48
+ * Glyphs whose slot id resolved to nothing and had to be encoded and uploaded again.
49
+ *
50
+ * THE COUNTER THAT SAYS THE ATLAS IS TOO SMALL. Non-zero once, on a cold list, is the mechanism
51
+ * working. Non-zero every frame means the working set does not fit and every frame is paying an
52
+ * encode — raise `atlasTexels`.
53
+ */
54
+ reuploads: number;
55
+ /**
56
+ * Glyphs the pass could not draw at all: an id it never issued, an encode that failed, or a
57
+ * re-upload the atlas refused. A hole in a word, so it is counted rather than left to a
58
+ * screenshot.
59
+ */
60
+ dropped: number;
61
+ /** Runs whose device ppem was under {@link PPEM_FIDELITY_FLOOR}. See this file's header. */
62
+ runsBelowPpemFloor: number;
63
+ /** Additive shaping-cache counters. */
64
+ shapeHits: number;
65
+ shapeMisses: number;
66
+ shapeEntries: number;
67
+ shapeGlyphs: number;
68
+ shapeEvicted: number;
69
+ }
70
+ interface HbGpuGlyphPassOptions {
71
+ /**
72
+ * The context to draw in — the SAME one the executor was given.
73
+ *
74
+ * It must be `premultipliedAlpha: true`, which is what `createCanvasStage` asks for; hb-gpu's
75
+ * fragment writes premultiplied coverage and a straight-alpha canvas composites it twice,
76
+ * silently, as a merely darker picture.
77
+ */
78
+ gl: WebGL2RenderingContext;
79
+ /** An instantiated hb-gpu module. The pass reads HarfBuzz's own GLSL and its encoders out of it. */
80
+ module: HbGpu;
81
+ /**
82
+ * The scene's coordinate extent — `CanvasStage.designWidth`, the space a `GlyphsView` lives in.
83
+ *
84
+ * Only a starting value: `drawRun` takes both sizes from the frame's `StageProjection`, which is
85
+ * the one authority on where design space lands.
86
+ */
87
+ designWidth: number;
88
+ designHeight: number;
89
+ /** The achieved drawing buffer. Defaults to the design pair, i.e. a device-pixel ratio of 1. */
90
+ framebufferWidth?: number;
91
+ framebufferHeight?: number;
92
+ /** Atlas capacity in texels; see `HbGpuRendererOptions.atlasTexels`. */
93
+ atlasTexels?: number;
94
+ /**
95
+ * The contrast curve hb-gpu applies to the coverage it computed. Omit for the renderer's own
96
+ * default, `HB_GPU_CONTRAST_DEFAULT` — stem darkening ON.
97
+ *
98
+ * OMITTING IT IS EXACTLY THE PICTURE THIS PASS DREW BEFORE THE OPTION EXISTED. It is not
99
+ * defaulted here: `undefined` is forwarded as `undefined` and `createHbGpuRenderer` decides, so
100
+ * every existing consumer and every committed pixel golden is byte-identical to before.
101
+ *
102
+ * WHY A GODOT-PARITY CONSUMER PASSES `HB_GPU_CONTRAST_NONE`, AND IT IS MEASURED RATHER THAN
103
+ * PREFERRED. Stem darkening is not a tie-breaker inside this path's quality — it IS the quality
104
+ * crossover against the other text arms. Distortion against an 8x area-coverage reference, swept
105
+ * over ppem 16-52 by `packages/perf-harness/probes/text-crossover.ts`
106
+ * (`pnpm -w run text:crossover -- --hb-contrast default|none`):
107
+ *
108
+ * ppem 16 default 0.1091 none 0.0258 canvas2d 0.0925
109
+ * ppem 24 default 0.0537 none 0.0138
110
+ *
111
+ * So the shipped curve LOSES to a plain `ctx.fillText` at 16 and costs this path ~4x its own
112
+ * achievable distortion at 24, while with the curve off hb-gpu wins at every ppem swept, with
113
+ * the tightest edges of any arm. AND GODOT APPLIES NO CURVE OF ITS OWN — its grayscale and MSDF
114
+ * glyph interiors come out byte-uniform — so a consumer whose acceptance test is "does this look
115
+ * like the engine we are mirroring" is holding its text up against a rasterizer that never
116
+ * darkened a stem, and the correction reads as a weight mismatch rather than as contrast.
117
+ *
118
+ * THE DEFAULT STAYS ON, because the case it was measured for is real and is a different case:
119
+ * against the DOM text path, uncorrected coverage is washed out (`HbGpuContrast`'s own doc — DOM
120
+ * puts 66% more pixels in the deep-dark end of one fixed crop). Text that is not standing next
121
+ * to Godot's own output still wants it.
122
+ *
123
+ * IT MOVES EDGES, NOT INK, WHICHEVER WAY YOU SET IT. `hb_gpu_stem_darken` is gated on partial
124
+ * coverage (`cov > 0 && cov < 1`), so a glyph INTERIOR is byte-identical either way and only the
125
+ * ramp at the boundary narrows or widens. The library also ramps the correction off by ppem 48,
126
+ * so display-sized text barely moves and small UI text moves most.
127
+ *
128
+ * IT REACHES THE FILL PASS ONLY, and that is the renderer's rule rather than this pass's: a run
129
+ * drawn with a non-zero `GlyphsView.spreadPx` emits raw coverage whatever this says. An outlined
130
+ * label is therefore already half-uncorrected today — see `HbGpuContrast.stemDarkening`.
131
+ */
132
+ contrast?: HbGpuContrast;
133
+ /** Opt-in expanded hb-gpu records used only by adjacent-run batching. */
134
+ batchAdjacentRuns?: boolean;
135
+ /** Every refusal, from this file and from the renderer under it. */
136
+ onError?(failure: HbGpuFailure): void;
137
+ /**
138
+ * Warn on the console the first time a run is drawn below {@link PPEM_FIDELITY_FLOOR}. Default
139
+ * on.
140
+ *
141
+ * ONCE, not per run: a page of small text would otherwise emit a line per label per frame, and a
142
+ * warning nobody can read is a warning nobody reads. {@link HbGpuGlyphPassStats.runsBelowPpemFloor}
143
+ * keeps the full tally. It is a `console.warn` rather than an `onError` because it is not a
144
+ * refusal — the text IS drawn, and only a measurement says another path would draw it better.
145
+ */
146
+ warnBelowPpemFloor?: boolean;
147
+ /** Maximum cached shaped runs. Zero disables memoisation. Defaults to 512. */
148
+ shapeCacheEntries?: number;
149
+ /** Maximum glyphs retained by shaped-run memoisation. Defaults to entries × 64. */
150
+ shapeCacheGlyphs?: number;
151
+ }
152
+ interface HbGpuGlyphPass extends GlyphPass {
153
+ /** The renderer underneath. Exposed for its `atlas` / `blobs` stats. */
154
+ readonly renderer: HbGpuRenderer;
155
+ readonly stats: HbGpuGlyphPassStats;
156
+ /**
157
+ * Register a face from its bytes, or `null` if hb-gpu will not take them.
158
+ *
159
+ * THE SAME BYTES THE SHAPER WAS GIVEN. Glyph ids come out of one HarfBuzz and outlines out of
160
+ * another; two different files make gid 97 two different outlines, and the result is crisp,
161
+ * fluent, wrong text that no counter downstream can see.
162
+ */
163
+ registerFace(bytes: Uint8Array, label?: string): GlyphFace | null;
164
+ /**
165
+ * The stable slot id for one glyph of one face — what a caller writes into `GlyphsView.slots`.
166
+ *
167
+ * Uploads the outline on first use and is a map lookup after that. The id is dense, permanent
168
+ * and survives eviction: it names an entry in THIS pass's table, not a place in the atlas.
169
+ *
170
+ * {@link GLYPH_SLOT_NONE} for a glyph with no ink and for one the atlas refused.
171
+ */
172
+ slotFor(face: GlyphFace, glyphId: number): number;
173
+ /** Glyph id for a code point in `face`, or 0 (`.notdef`). A convenience over `HbGpuFont`. */
174
+ glyphFor(face: GlyphFace, codepoint: number): number;
175
+ /**
176
+ * Shape `text` and fill `run` with the result: slots, pen positions and count. `false` if the
177
+ * shaper refused, leaving `run` untouched.
178
+ *
179
+ * ONE HARFBUZZ, WHICH IS THE WHOLE POINT OF HAVING THIS AT ALL. hb-gpu's wasm exports the
180
+ * OpenType shaper as well as the Slug encoder, so a consumer that calls this loads one HarfBuzz
181
+ * and holds each face once. Shaping elsewhere — npm `harfbuzzjs`, most likely — means a second
182
+ * build, a second heap and the same font bytes resident twice, which measured 4.19 MiB across
183
+ * the pair on a phone (`docs/text-rendering.md`). It is also the configuration in which glyph
184
+ * ids and outlines can come from two DIFFERENT files, and gid 97 meaning two different things is
185
+ * crisp, fluent, wrong text that no counter downstream can see.
186
+ *
187
+ * THE PEN ARITHMETIC LIVES HERE FOR THE SAME REASON IT IS EASY TO GET WRONG. HarfBuzz reports
188
+ * advances and offsets in FONT units, y-UP; `GlyphsView.positions` are design units, y-DOWN,
189
+ * measured from the run's origin. So each is scaled by `run.pixelsPerEm / face.upem` and y is
190
+ * negated, an offset positions its own glyph only, and the advance accumulates after it. Getting
191
+ * the scale or the y sign wrong produces text that looks entirely plausible and is subtly
192
+ * mis-spaced — which is why there is one implementation rather than one per caller.
193
+ *
194
+ * `run.pixelsPerEm` must be set BEFORE the call. Exactly three fields are written — `slots`,
195
+ * `positions` and `glyphCount` — and `run.slots` / `run.positions` are replaced by larger buffers
196
+ * if the run does not fit. Inkless glyphs (a space) are skipped rather than given a slot, so
197
+ * `run.glyphCount` can be smaller than the shaped length.
198
+ *
199
+ * `m`, the colour and `spreadPx` are LEFT ALONE, which is what makes an outlined label one shape
200
+ * and two pushes: shape once, then `pushGlyphs` with the outline colour and a spread, then again
201
+ * with the fill colour and `spreadPx = 0`. Both runs then carry pen positions that came from a
202
+ * single shaping pass and cannot drift apart.
203
+ */
204
+ fillRun(run: GlyphsView, face: GlyphFace, text: string, options?: HbGpuShapeOptions): boolean;
205
+ /** Drop shaped-run memoisation without disturbing faces or atlas residency. */
206
+ clearShapeCache(): void;
207
+ /** Re-state both sizes after the stage resized. See `HbGpuRenderer.setViewport`. */
208
+ setViewport(designWidth: number, designHeight: number, framebufferWidth?: number, framebufferHeight?: number): void;
209
+ /** Forward of `HbGpuRenderer.notifyContextLost` — wire it to the stage's `onContextLost`. */
210
+ notifyContextLost(): void;
211
+ /** Forward of `HbGpuRenderer.rebuild` — wire it to the stage's `onContextRestored`. */
212
+ rebuild(): boolean;
213
+ /** Delete the renderer's GL objects. Does NOT touch the context or the registered fonts. */
214
+ dispose(): void;
215
+ }
216
+ /**
217
+ * Build a glyph pass over a borrowed context, or `null` when the renderer will not build.
218
+ *
219
+ * `null` rather than a throw, following `createCanvasStage` and `createHbGpuRenderer`: a consumer
220
+ * that cannot have the GPU glyph path falls back to its DOM one, and only it knows whether that is
221
+ * acceptable. Pass `onError` to be told why — a pass that declined silently reports as a page with
222
+ * no words on it.
223
+ */
224
+ declare function createHbGpuGlyphPass(options: HbGpuGlyphPassOptions): HbGpuGlyphPass | null;
225
+ //#endregion
226
+ export { GLYPH_SLOT_NONE, GlyphFace, HbGpuGlyphPass, HbGpuGlyphPassOptions, HbGpuGlyphPassStats, PPEM_FIDELITY_FLOOR, createHbGpuGlyphPass };
227
+ //# sourceMappingURL=glyph-pass-hbgpu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyph-pass-hbgpu.d.ts","names":[],"sources":["../src/glyph-pass-hbgpu.ts"],"mappings":";;;;;;AAiFA;;;;AAAgC;AAShC;;cATa,mBAAA;;AASe;AAU5B;;;;;cAVa,eAAA;;;;;;AAeG;AAGhB;;UARiB,SAAA;EAAA,SACN,IAAA,EAAM,SAAA;EAAA,SACN,IAAA,EAAM,SAAS;EAUxB;EAAA,SARS,IAAA;EAAA,SACA,KAAA;AAAA;AAAA,UAGM,mBAAA;EAwBf;EAtBA,KAAA;EAyBA;EAvBA,IAAA;EAyBA;EAvBA,MAAA;EAwBY;EAtBZ,OAAA;EAyBe;;;;;;;EAjBf,SAAA;EAmF8B;;;;;EA7E9B,OAAA;EA6BA;EA3BA,kBAAA;EA8BA;EA5BA,SAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,YAAA;AAAA;AAAA,UAGe,qBAAA;EA4Ef;;;;AAIgB;AAGlB;;EA3EE,EAAA,EAAI,sBAAA;EA6Ee;EA3EnB,MAAA,EAAQ,KAAA;EAoFY;;;;;;EA7EpB,WAAA;EACA,YAAA;EAiE+C;EA/D/C,gBAAA;EACA,iBAAA;EAgES;EA9DT,WAAA;EA+DS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEF;AAiBT;;;;EA9GE,QAAA,GAAW,aAAA;EA+GX;EA7GA,iBAAA;EA8Ge;EA5Gf,OAAA,EAAS,OAAA,EAAS,YAAA;;;;;;;;;;EAUlB,kBAAA;;EAEA,iBAAA;;EAEA,gBAAA;AAAA;AAAA,UAGe,cAAA,SAAuB,SAAA;;WAE7B,QAAA,EAAU,aAAA;EAAA,SACV,KAAA,EAAO,mBAAA;;;;;;;;EAQhB,YAAA,CAAa,KAAA,EAAO,UAAA,EAAY,KAAA,YAAiB,SAAA;;;;;;;;;EASjD,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,OAAA;;EAEzB,QAAA,CAAS,IAAA,EAAM,SAAA,EAAW,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8B1B,OAAA,CACE,GAAA,EAAK,UAAA,EACL,IAAA,EAAM,SAAA,EACN,IAAA,UACA,OAAA,GAAU,iBAAA;;EAGZ,eAAA;;EAEA,WAAA,CACE,WAAA,UACA,YAAA,UACA,gBAAA,WACA,iBAAA;;EAGF,iBAAA;;EAEA,OAAA;;EAEA,OAAA;AAAA;;;;;;;;;iBAiBc,oBAAA,CACd,OAAA,EAAS,qBAAA,GACR,cAAc"}
@@ -0,0 +1,387 @@
1
+ import { createHbGpuRenderer } from "@godot-scene-web/hb-gpu/webgl";
2
+ //#region src/glyph-shape-cache.ts
3
+ /**
4
+ * Small bounded cache for HarfBuzz's scale-free font-unit output. Unknown options deliberately
5
+ * bypass it: forwarding a future shaping option must not accidentally reuse an older result.
6
+ */
7
+ var GlyphShapeCache = class {
8
+ maxEntries;
9
+ maxGlyphs;
10
+ stats;
11
+ byFace = /* @__PURE__ */ new Map();
12
+ tick = 0;
13
+ constructor(maxEntries, maxGlyphs, stats) {
14
+ this.maxEntries = maxEntries;
15
+ this.maxGlyphs = maxGlyphs;
16
+ this.stats = stats;
17
+ }
18
+ key(text, options) {
19
+ if (!options || Object.keys(options).length === 0) return text;
20
+ const allowed = new Set([
21
+ "direction",
22
+ "script",
23
+ "language",
24
+ "features"
25
+ ]);
26
+ if (Object.keys(options).some((name) => !allowed.has(name))) return null;
27
+ return JSON.stringify([
28
+ options.direction,
29
+ options.script,
30
+ options.language,
31
+ options.features,
32
+ text
33
+ ]);
34
+ }
35
+ get(faceId, key) {
36
+ if (this.maxEntries === 0 || this.maxGlyphs === 0) return null;
37
+ const entry = this.byFace.get(faceId)?.get(key);
38
+ if (!entry) return null;
39
+ entry.usedAt = ++this.tick;
40
+ this.stats.shapeHits += 1;
41
+ return entry;
42
+ }
43
+ put(faceId, key, shape) {
44
+ if (this.maxEntries === 0 || this.maxGlyphs === 0 || shape.count > this.maxGlyphs) return;
45
+ while (this.stats.shapeEntries >= this.maxEntries || this.stats.shapeGlyphs + shape.count > this.maxGlyphs) {
46
+ let oldestFace;
47
+ let oldestKey;
48
+ let oldest = Infinity;
49
+ for (const entries of this.byFace.values()) for (const [candidateKey, candidate] of entries) if (candidate.usedAt < oldest) {
50
+ oldest = candidate.usedAt;
51
+ oldestFace = entries;
52
+ oldestKey = candidateKey;
53
+ }
54
+ if (!oldestFace || oldestKey === void 0) break;
55
+ const removed = oldestFace.get(oldestKey);
56
+ oldestFace.delete(oldestKey);
57
+ this.stats.shapeEntries -= 1;
58
+ this.stats.shapeGlyphs -= removed.count;
59
+ this.stats.shapeEvicted += 1;
60
+ }
61
+ let entries = this.byFace.get(faceId);
62
+ if (!entries) {
63
+ entries = /* @__PURE__ */ new Map();
64
+ this.byFace.set(faceId, entries);
65
+ }
66
+ entries.set(key, {
67
+ ...shape,
68
+ usedAt: ++this.tick
69
+ });
70
+ this.stats.shapeEntries += 1;
71
+ this.stats.shapeGlyphs += shape.count;
72
+ }
73
+ miss() {
74
+ this.stats.shapeMisses += 1;
75
+ }
76
+ clear() {
77
+ this.byFace.clear();
78
+ this.stats.shapeEntries = 0;
79
+ this.stats.shapeGlyphs = 0;
80
+ }
81
+ };
82
+ //#endregion
83
+ //#region src/glyph-pass-hbgpu.ts
84
+ /**
85
+ * Below this ppem the outline path is measurably blurrier than the alternatives.
86
+ *
87
+ * `ppem` here is `GlyphsView.pixelsPerEm` times the scale in `GlyphsView.m` times the device-pixel
88
+ * ratio — the size in DEVICE pixels, which is the only size the shader can see, and it sees all
89
+ * three factors (it derives ppem from `fwidth`). The number is HarfBuzz's own branch point, not a
90
+ * threshold chosen here: `hb_gpu_draw` switches to a five-tap MSAA approximation under it.
91
+ */
92
+ const PPEM_FIDELITY_FLOOR = 16;
93
+ /**
94
+ * The id `slotFor` returns for a glyph with no ink, or one the atlas declined.
95
+ *
96
+ * Write it into `GlyphsView.slots` and the pass skips the glyph. It is a distinct value ON PURPOSE:
97
+ * a caller that wrote a real-looking id for a space would get a degenerate quad reading texel 0,
98
+ * which is some other glyph's header.
99
+ */
100
+ const GLYPH_SLOT_NONE = -1;
101
+ /**
102
+ * Build a glyph pass over a borrowed context, or `null` when the renderer will not build.
103
+ *
104
+ * `null` rather than a throw, following `createCanvasStage` and `createHbGpuRenderer`: a consumer
105
+ * that cannot have the GPU glyph path falls back to its DOM one, and only it knows whether that is
106
+ * acceptable. Pass `onError` to be told why — a pass that declined silently reports as a page with
107
+ * no words on it.
108
+ */
109
+ function createHbGpuGlyphPass(options) {
110
+ const report = (failure) => options.onError?.(failure);
111
+ const rendererOptions = {
112
+ gl: options.gl,
113
+ designWidth: options.designWidth,
114
+ designHeight: options.designHeight,
115
+ framebufferWidth: options.framebufferWidth,
116
+ framebufferHeight: options.framebufferHeight,
117
+ atlasTexels: options.atlasTexels,
118
+ contrast: options.contrast,
119
+ perInstanceRunState: options.batchAdjacentRuns === true,
120
+ onError: report
121
+ };
122
+ const renderer = createHbGpuRenderer(options.module, rendererOptions);
123
+ if (!renderer) return null;
124
+ const live = renderer;
125
+ const batchAdjacentRuns = options.batchAdjacentRuns === true;
126
+ const entries = [];
127
+ const idByFace = [];
128
+ const faces = [];
129
+ const stats = {
130
+ slots: 0,
131
+ runs: 0,
132
+ glyphs: 0,
133
+ inkless: 0,
134
+ reuploads: 0,
135
+ dropped: 0,
136
+ runsBelowPpemFloor: 0,
137
+ shapeHits: 0,
138
+ shapeMisses: 0,
139
+ shapeEntries: 0,
140
+ shapeGlyphs: 0,
141
+ shapeEvicted: 0
142
+ };
143
+ const shapeCacheEntries = Math.max(0, options.shapeCacheEntries ?? 512);
144
+ const shapeCache = new GlyphShapeCache(shapeCacheEntries, Math.max(0, options.shapeCacheGlyphs ?? shapeCacheEntries * 64), stats);
145
+ let warnedBelowPpemFloor = false;
146
+ let adjacentBatchOpen = false;
147
+ let designWidth = Math.max(1, options.designWidth);
148
+ let designHeight = Math.max(1, options.designHeight);
149
+ let framebufferWidth = Math.max(1, options.framebufferWidth ?? options.designWidth);
150
+ let framebufferHeight = Math.max(1, options.framebufferHeight ?? options.designHeight);
151
+ /**
152
+ * The slot table, as a plain function rather than only a method.
153
+ *
154
+ * `fillRun` needs it too, and reaching it through `this` would break the moment a caller
155
+ * destructured the pass — a shape the rest of this package supports everywhere else. `live` and
156
+ * not `renderer` because TypeScript will not carry the null narrowing above into a function
157
+ * declaration, which could in principle be called before it.
158
+ */
159
+ function slotIdFor(face, glyphId) {
160
+ let byGlyph = idByFace[face.face.id];
161
+ if (!byGlyph) {
162
+ byGlyph = /* @__PURE__ */ new Map();
163
+ idByFace[face.face.id] = byGlyph;
164
+ }
165
+ const cached = byGlyph.get(glyphId);
166
+ if (cached !== void 0) return cached;
167
+ const entry = {
168
+ face,
169
+ glyphId
170
+ };
171
+ const glyph = encode(entry);
172
+ if (!glyph) return -1;
173
+ if (glyph.texels.length === 0) return -1;
174
+ if (!live.upload(face.face, glyphId, glyph)) return -1;
175
+ const id = entries.length;
176
+ entries.push(entry);
177
+ byGlyph.set(glyphId, id);
178
+ stats.slots = entries.length;
179
+ return id;
180
+ }
181
+ /** Encode one glyph, or say why not. A zero-length blob means "no ink", not "failed". */
182
+ function encode(entry) {
183
+ const glyph = entry.face.font.encode(entry.glyphId);
184
+ if (!glyph) {
185
+ report({
186
+ reason: "encoder-unavailable",
187
+ message: `hb-gpu: hb_gpu_draw_encode refused glyph ${entry.glyphId} of face "${entry.face.label}" — the run will be drawn with a hole in it`
188
+ });
189
+ return null;
190
+ }
191
+ return glyph;
192
+ }
193
+ const pass = {
194
+ renderer,
195
+ stats,
196
+ registerFace(bytes, label) {
197
+ const name = label ?? `face${faces.length}`;
198
+ const font = options.module.createFont(bytes);
199
+ if (!font) return null;
200
+ const face = renderer.registerFace(font, name);
201
+ if (!face) {
202
+ font.destroy();
203
+ return null;
204
+ }
205
+ const registered = {
206
+ font,
207
+ face,
208
+ upem: face.upem,
209
+ label: name
210
+ };
211
+ faces.push(registered);
212
+ idByFace[face.id] = /* @__PURE__ */ new Map();
213
+ return registered;
214
+ },
215
+ glyphFor(face, codepoint) {
216
+ return face.font.glyphFor(codepoint);
217
+ },
218
+ fillRun(run, face, text, shapeOptions) {
219
+ const key = shapeCache.key(text, shapeOptions);
220
+ let cached = key === null ? null : shapeCache.get(face.face.id, key);
221
+ if (!cached) {
222
+ shapeCache.miss();
223
+ const shaped = face.font.shape(text, shapeOptions);
224
+ if (!shaped) return false;
225
+ const slots = new Int32Array(shaped.length);
226
+ const pen = new Int32Array(shaped.length * 2);
227
+ let penX = 0;
228
+ let penY = 0;
229
+ let count = 0;
230
+ for (const glyph of shaped) {
231
+ const slot = slotIdFor(face, glyph.glyphId);
232
+ if (slot !== -1) {
233
+ slots[count] = slot;
234
+ pen[count * 2] = penX + glyph.xOffset;
235
+ pen[count * 2 + 1] = penY + glyph.yOffset;
236
+ count += 1;
237
+ }
238
+ penX += glyph.xAdvance;
239
+ penY += glyph.yAdvance;
240
+ }
241
+ cached = {
242
+ count,
243
+ slots: slots.slice(0, count),
244
+ pen: pen.slice(0, count * 2),
245
+ advanceX: penX,
246
+ advanceY: penY
247
+ };
248
+ if (key !== null) shapeCache.put(face.face.id, key, cached);
249
+ }
250
+ if (run.slots.length < cached.count) run.slots = new Int32Array(cached.count);
251
+ if (run.positions.length < cached.count * 2) run.positions = new Float32Array(cached.count * 2);
252
+ const scale = run.pixelsPerEm / face.upem;
253
+ for (let i = 0; i < cached.count; i += 1) {
254
+ run.slots[i] = cached.slots[i];
255
+ run.positions[i * 2] = cached.pen[i * 2] * scale;
256
+ run.positions[i * 2 + 1] = -cached.pen[i * 2 + 1] * scale;
257
+ }
258
+ run.glyphCount = cached.count;
259
+ return true;
260
+ },
261
+ slotFor: slotIdFor,
262
+ clearShapeCache() {
263
+ shapeCache.clear();
264
+ },
265
+ drawRun(run, projection) {
266
+ const runDesignWidth = projection.designWidth;
267
+ const runDesignHeight = projection.designHeight;
268
+ if (runDesignWidth !== designWidth || runDesignHeight !== designHeight || projection.framebufferWidth !== framebufferWidth || projection.framebufferHeight !== framebufferHeight) {
269
+ designWidth = runDesignWidth;
270
+ designHeight = runDesignHeight;
271
+ framebufferWidth = projection.framebufferWidth;
272
+ framebufferHeight = projection.framebufferHeight;
273
+ renderer.setViewport(designWidth, designHeight, framebufferWidth, framebufferHeight);
274
+ }
275
+ const axis = (Math.hypot(run.m[0], run.m[1]) + Math.hypot(run.m[2], run.m[3])) / 2;
276
+ const ppem = run.pixelsPerEm * axis * (framebufferWidth / designWidth);
277
+ if (ppem < 16) {
278
+ stats.runsBelowPpemFloor += 1;
279
+ if (options.warnBelowPpemFloor !== false && !warnedBelowPpemFloor) {
280
+ warnedBelowPpemFloor = true;
281
+ console.warn(`[gsw canvas] glyph run drawn at ${ppem.toFixed(1)} device ppem (pixelsPerEm ${run.pixelsPerEm} x model scale ${axis.toFixed(3)} x ${(framebufferWidth / designWidth).toFixed(3)} device ratio), under the 16 this path wants: HarfBuzz's coverage shader falls back to a five-tap approximation below it, measured blurrier than the DOM text path (0.196 vs 0.132 distortion on Han at ppem 14). A baked atlas or DOM text is crisper at this size. Reported once; see stats.runsBelowPpemFloor for the tally.`);
282
+ }
283
+ }
284
+ if (!adjacentBatchOpen) renderer.begin();
285
+ renderer.setModel(run.m);
286
+ renderer.setSpread(run.spreadPx);
287
+ const inverse = run.a > 0 ? 1 / run.a : 0;
288
+ renderer.setColor(run.r * inverse, run.g * inverse, run.b * inverse, run.a);
289
+ const count = run.glyphCount;
290
+ for (let i = 0; i < count; i += 1) {
291
+ const id = run.slots[i];
292
+ if (id === -1) {
293
+ stats.inkless += 1;
294
+ continue;
295
+ }
296
+ const entry = entries[id];
297
+ if (!entry) {
298
+ stats.dropped += 1;
299
+ continue;
300
+ }
301
+ let slot = renderer.resolve(entry.face.face, entry.glyphId);
302
+ if (!slot) {
303
+ const glyph = encode(entry);
304
+ if (!glyph || glyph.texels.length === 0) {
305
+ stats.dropped += 1;
306
+ continue;
307
+ }
308
+ slot = renderer.upload(entry.face.face, entry.glyphId, glyph);
309
+ stats.reuploads += 1;
310
+ if (!slot) {
311
+ stats.dropped += 1;
312
+ continue;
313
+ }
314
+ }
315
+ renderer.push(slot, run.positions[i * 2], run.positions[i * 2 + 1], run.pixelsPerEm);
316
+ }
317
+ const frame = adjacentBatchOpen ? {
318
+ instances: 0,
319
+ drawCalls: 0
320
+ } : renderer.end();
321
+ stats.runs += 1;
322
+ stats.glyphs += frame.instances;
323
+ return {
324
+ glyphs: frame.instances,
325
+ drawCalls: frame.drawCalls
326
+ };
327
+ },
328
+ drawRuns(runs, projection) {
329
+ if (runs.length === 0) return {
330
+ glyphs: 0,
331
+ drawCalls: 0
332
+ };
333
+ renderer.begin();
334
+ adjacentBatchOpen = true;
335
+ let completed = false;
336
+ try {
337
+ for (const run of runs) pass.drawRun(run, projection);
338
+ completed = true;
339
+ } finally {
340
+ adjacentBatchOpen = false;
341
+ if (!completed) renderer.begin();
342
+ }
343
+ const frame = renderer.end();
344
+ stats.glyphs += frame.instances;
345
+ return {
346
+ glyphs: frame.instances,
347
+ drawCalls: frame.drawCalls
348
+ };
349
+ },
350
+ canBatchRuns(runs) {
351
+ if (!batchAdjacentRuns) return false;
352
+ for (const run of runs) for (let i = 0; i < run.glyphCount; i += 1) {
353
+ const id = run.slots[i];
354
+ if (id === -1) continue;
355
+ const entry = entries[id];
356
+ if (!entry || !renderer.resolve(entry.face.face, entry.glyphId)) return false;
357
+ }
358
+ return true;
359
+ },
360
+ setViewport(width, height, bufferWidth, bufferHeight) {
361
+ designWidth = Math.max(1, width);
362
+ designHeight = Math.max(1, height);
363
+ framebufferWidth = Math.max(1, bufferWidth ?? width);
364
+ framebufferHeight = Math.max(1, bufferHeight ?? height);
365
+ renderer.setViewport(designWidth, designHeight, framebufferWidth, framebufferHeight);
366
+ },
367
+ notifyContextLost() {
368
+ renderer.notifyContextLost();
369
+ },
370
+ rebuild() {
371
+ return renderer.rebuild();
372
+ },
373
+ dispose() {
374
+ shapeCache.clear();
375
+ renderer.dispose();
376
+ }
377
+ };
378
+ if (!batchAdjacentRuns) {
379
+ delete pass.drawRuns;
380
+ delete pass.canBatchRuns;
381
+ }
382
+ return pass;
383
+ }
384
+ //#endregion
385
+ export { GLYPH_SLOT_NONE, PPEM_FIDELITY_FLOOR, createHbGpuGlyphPass };
386
+
387
+ //# sourceMappingURL=glyph-pass-hbgpu.js.map