three-text 0.6.1 → 0.6.3
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 +22 -25
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/three/react.d.ts +0 -1
- package/dist/types/core/types.d.ts +0 -1
- package/dist/types/vector/index.d.ts +1 -0
- package/dist/vector/core/index.cjs +20 -56
- package/dist/vector/core/index.js +20 -56
- package/dist/vector/index.d.ts +1 -0
- package/dist/vector/index2.cjs +6 -19
- package/dist/vector/index2.js +6 -19
- package/dist/vector/react.d.ts +0 -1
- package/dist/vector/slugTSL.cjs +4 -27
- package/dist/vector/slugTSL.js +4 -27
- package/dist/vector/webgl/index.cjs +1 -4
- package/dist/vector/webgl/index.js +1 -4
- package/dist/vector/webgpu/index.cjs +0 -3
- package/dist/vector/webgpu/index.js +0 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,8 +19,6 @@ High fidelity 3D text rendering and layout for the web
|
|
|
19
19
|
|
|
20
20
|
The library has a framework-agnostic core with lightweight adapters for [Three.js](https://threejs.org), [React Three Fiber](https://docs.pmnd.rs/react-three-fiber), [p5.js](https://p5js.org), [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API)
|
|
21
21
|
|
|
22
|
-
Under the hood, three-text relies on a core of [harfbuzzjs](https://github.com/harfbuzz/harfbuzzjs) (based on [HarfBuzz](https://github.com/harfbuzz/harfbuzz) by Behdad Esfahbod et al) for text shaping, [Knuth-Plass](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) line breaking (with [SILE](https://github.com/sile-typesetter/sile/blob/master/core/break.lua) and LuaTex being the closest modern references), [Liang](https://tug.org/docs/liang/liang-thesis.pdf) hyphenation and the [TeX hyphenation patterns](https://github.com/hyphenation/tex-hyphen), and [woff-lib](https://github.com/countertype/woff-lib) for optional WOFF2 support. The mesh text pipeline uses [libtess-ts](https://github.com/countertype/libtess-ts) (a port of the [GLU tessellator](https://www.songho.ca/opengl/gl_tessellation.html) by Eric Veach) for removing overlaps and triangulation, adaptive curve polygonization from Maxim Shemanarev's [Anti-Grain Geometry](https://web.archive.org/web/20060128212843/http://www.antigrain.com/research/adaptive_bezier/index.html), and [Visvalingam-Whyatt](https://hull-repository.worktribe.com/preview/376364/000870493786962263.pdf) [line simplification](https://bost.ocks.org/mike/simplify/). The vector pipeline uses [Slug](https://github.com/EricLengyel/Slug) by Eric Lengyel for resolution-independent curve rendering
|
|
23
|
-
|
|
24
22
|
## Table of contents
|
|
25
23
|
|
|
26
24
|
- [Overview](#overview)
|
|
@@ -41,6 +39,7 @@ Under the hood, three-text relies on a core of [harfbuzzjs](https://github.com/h
|
|
|
41
39
|
- [Browser compatibility](#browser-compatibility)
|
|
42
40
|
- [Testing](#testing)
|
|
43
41
|
- [Build system](#build-system)
|
|
42
|
+
- [Entry points](#entry-points)
|
|
44
43
|
- [Build outputs](#build-outputs)
|
|
45
44
|
- [Acknowledgements](#acknowledgements)
|
|
46
45
|
- [License](#license)
|
|
@@ -60,34 +59,14 @@ npm install three
|
|
|
60
59
|
|
|
61
60
|
## Architecture
|
|
62
61
|
|
|
63
|
-
three-text has a framework-agnostic core that processes fonts and generates geometry data. Lightweight adapters convert this data to framework-specific formats
|
|
64
|
-
|
|
65
|
-
- **`three-text`** - Three.js adapter (default export, returns `BufferGeometry`)
|
|
66
|
-
- **`three-text/mesh`** - Same as above (explicit alias)
|
|
67
|
-
- **`three-text/mesh/react`** - React Three Fiber component for extruded mesh text
|
|
68
|
-
- **`three-text/three`** - Deprecated, use `three-text/mesh`
|
|
69
|
-
- **`three-text/three/react`** - Deprecated, use `three-text/mesh/react`
|
|
70
|
-
- **`three-text/mesh/webgl`** - WebGL mesh buffer utility
|
|
71
|
-
- **`three-text/mesh/webgpu`** - WebGPU mesh buffer utility
|
|
72
|
-
- **`three-text/mesh/p5`** - p5.js adapter
|
|
73
|
-
- **`three-text/core`** - Framework-agnostic core (returns raw arrays)
|
|
74
|
-
- **`three-text/vector`** - Vector rendering (Slug per-fragment curve evaluation), `Text.create()` returns a `THREE.Group`
|
|
75
|
-
- **`three-text/vector/react`** - React Three Fiber component for vector text
|
|
76
|
-
- **`three-text/vector/core`** - Framework-agnostic vector core (returns raw `SlugGPUData`, no Three.js dependency)
|
|
77
|
-
- **`three-text/vector/webgl`** - Raw WebGL2 vector renderer (no Three.js dependency)
|
|
78
|
-
- **`three-text/vector/webgpu`** - Raw WebGPU vector renderer (no Three.js dependency)
|
|
79
|
-
- **`three-text/webgl`** - Deprecated, use `three-text/mesh/webgl`
|
|
80
|
-
- **`three-text/webgpu`** - Deprecated, use `three-text/mesh/webgpu`
|
|
81
|
-
- **`three-text/p5`** - Deprecated, use `three-text/mesh/p5`
|
|
82
|
-
|
|
83
|
-
Most users will just `import { Text } from 'three-text'` for Three.js projects with mesh, or `import { Text } from 'three-text/vector'` for vector text
|
|
62
|
+
three-text has a framework-agnostic core that processes fonts and generates geometry data. Lightweight adapters convert this data to framework-specific formats. Most users will just `import { Text } from 'three-text'` for Three.js projects with mesh, or `import { Text } from 'three-text/vector'` for vector text. See [Entry points](#entry-points) for the full list of adapters
|
|
84
63
|
|
|
85
64
|
### Mesh vs vector
|
|
86
65
|
|
|
87
66
|
The library offers two rendering modes that share the same core (HarfBuzz shaping, Knuth-Plass justification, glyph caching):
|
|
88
67
|
|
|
89
68
|
- **Mesh** (`three-text` (default) / `three-text/mesh`): triangulated geometry you can extrude, light, and shade. Use for 3D text, text in a scene graph, or anywhere you need depth
|
|
90
|
-
- **Vector** (`three-text/vector`): resolution-independent rendering on the GPU via per-fragment curve evaluation.
|
|
69
|
+
- **Vector** (`three-text/vector`): resolution-independent rendering on the GPU via per-fragment curve evaluation. Use for flat text that needs to stay sharp at arbitrary zoom
|
|
91
70
|
|
|
92
71
|
Both can be used in the same project from separate entry points
|
|
93
72
|
|
|
@@ -483,6 +462,8 @@ Existing solutions take different approaches:
|
|
|
483
462
|
|
|
484
463
|
three-text produces actual geometry from font files, sharper at close distances than bitmap approaches, with control over typesetting and paragraph justification via TeX-based parameters
|
|
485
464
|
|
|
465
|
+
Under the hood, three-text relies on a core of [harfbuzzjs](https://github.com/harfbuzz/harfbuzzjs) (based on [HarfBuzz](https://github.com/harfbuzz/harfbuzz) by Behdad Esfahbod et al) for text shaping, [Knuth-Plass](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) line breaking (with [SILE](https://github.com/sile-typesetter/sile/blob/master/core/break.lua) and LuaTex being the closest modern references), [Liang](https://tug.org/docs/liang/liang-thesis.pdf) hyphenation and the [TeX hyphenation patterns](https://github.com/hyphenation/tex-hyphen), and [woff-lib](https://github.com/countertype/woff-lib) for optional WOFF2 support. The mesh text pipeline uses [libtess-ts](https://github.com/countertype/libtess-ts) (a port of the [GLU tessellator](https://www.songho.ca/opengl/gl_tessellation.html) by Eric Veach) for removing overlaps and triangulation, adaptive curve polygonization from Maxim Shemanarev's [Anti-Grain Geometry](https://web.archive.org/web/20060128212843/http://www.antigrain.com/research/adaptive_bezier/index.html), and [Visvalingam-Whyatt](https://hull-repository.worktribe.com/preview/376364/000870493786962263.pdf) [line simplification](https://bost.ocks.org/mike/simplify/). The vector pipeline uses [Slug](https://github.com/EricLengyel/Slug) by Eric Lengyel for resolution-independent curve rendering
|
|
466
|
+
|
|
486
467
|
### Why Slug
|
|
487
468
|
|
|
488
469
|
The vector path uses the Slug algorithm by Eric Lengyel. Each glyph is a single quad; the fragment shader evaluates curve coverage analytically to compute a winding number. Because it operates on winding rather than geometry, it naturally handles the self-intersecting contours that variable fonts produce when axes like weight push outlines into each other
|
|
@@ -1256,6 +1237,22 @@ git submodule update --init --recursive
|
|
|
1256
1237
|
|
|
1257
1238
|
The script then processes the TeX hyphenation data into optimized trie structures. The process is slow for the complete set of languages (~1 minute on an M2 Max), so using `--languages` for development is recommended
|
|
1258
1239
|
|
|
1240
|
+
## Entry points
|
|
1241
|
+
|
|
1242
|
+
- **`three-text`** - Three.js adapter (default export, returns mesh `BufferGeometry`)
|
|
1243
|
+
- **`three-text/mesh`** - Same as above (explicit alias)
|
|
1244
|
+
- **`three-text/mesh/react`** - React Three Fiber component for extruded mesh text
|
|
1245
|
+
- **`three-text/three/react`** - Deprecated, use `three-text/mesh/react`
|
|
1246
|
+
- **`three-text/mesh/webgl`** - WebGL mesh buffer utility
|
|
1247
|
+
- **`three-text/mesh/webgpu`** - WebGPU mesh buffer utility
|
|
1248
|
+
- **`three-text/mesh/p5`** - p5.js adapter
|
|
1249
|
+
- **`three-text/core`** - Framework-agnostic core (returns raw arrays)
|
|
1250
|
+
- **`three-text/vector`** - Vector rendering (Slug per-fragment curve evaluation), `Text.create()` returns a `THREE.Group`
|
|
1251
|
+
- **`three-text/vector/react`** - React Three Fiber component for vector text
|
|
1252
|
+
- **`three-text/vector/core`** - Framework-agnostic vector core (returns raw `SlugGPUData`, no Three.js dependency)
|
|
1253
|
+
- **`three-text/vector/webgl`** - Raw WebGL2 vector renderer (no Three.js dependency)
|
|
1254
|
+
- **`three-text/vector/webgpu`** - Raw WebGPU vector renderer (no Three.js dependency)
|
|
1255
|
+
|
|
1259
1256
|
## Build outputs
|
|
1260
1257
|
|
|
1261
1258
|
The build generates multiple module formats for core and all adapters:
|
|
@@ -1289,4 +1286,4 @@ The build generates multiple module formats for core and all adapters:
|
|
|
1289
1286
|
|
|
1290
1287
|
`three-text` was written by Jeremy Tribby ([@jpt](https://github.com/jpt)) and is licensed under the MIT License. See the [LICENSE](LICENSE) file for details
|
|
1291
1288
|
|
|
1292
|
-
This software includes code from third-party libraries under compatible permissive licenses. For full license details, see the [LICENSE_THIRD_PARTY](LICENSE_THIRD_PARTY) file
|
|
1289
|
+
This software includes code from third-party libraries under compatible permissive licenses. For full license details, see the [LICENSE_THIRD_PARTY](LICENSE_THIRD_PARTY) file
|
package/dist/index.cjs
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -410,7 +410,6 @@ interface TextOptions {
|
|
|
410
410
|
geometryOptimization?: GeometryOptimizationOptions;
|
|
411
411
|
layout?: LayoutOptions;
|
|
412
412
|
color?: [number, number, number] | ColorOptions;
|
|
413
|
-
/** Enable rotated RGSS-4 adaptive supersampling (4 samples per pixel). Takes effect when the GLSL rendering path is active. */
|
|
414
413
|
adaptiveSupersampling?: boolean;
|
|
415
414
|
}
|
|
416
415
|
interface HyphenationPatternsMap {
|
package/dist/index.js
CHANGED
package/dist/index.min.cjs
CHANGED
package/dist/index.min.js
CHANGED
package/dist/index.umd.js
CHANGED
package/dist/index.umd.min.js
CHANGED
package/dist/three/react.d.ts
CHANGED
|
@@ -208,7 +208,6 @@ interface TextOptions {
|
|
|
208
208
|
geometryOptimization?: GeometryOptimizationOptions;
|
|
209
209
|
layout?: LayoutOptions;
|
|
210
210
|
color?: [number, number, number] | ColorOptions;
|
|
211
|
-
/** Enable rotated RGSS-4 adaptive supersampling (4 samples per pixel). Takes effect when the GLSL rendering path is active. */
|
|
212
211
|
adaptiveSupersampling?: boolean;
|
|
213
212
|
}
|
|
214
213
|
interface HyphenationPatternsMap {
|
|
@@ -370,7 +370,6 @@ export interface TextOptions {
|
|
|
370
370
|
geometryOptimization?: GeometryOptimizationOptions;
|
|
371
371
|
layout?: LayoutOptions;
|
|
372
372
|
color?: [number, number, number] | ColorOptions;
|
|
373
|
-
/** Enable rotated RGSS-4 adaptive supersampling (4 samples per pixel). Takes effect when the GLSL rendering path is active. */
|
|
374
373
|
adaptiveSupersampling?: boolean;
|
|
375
374
|
}
|
|
376
375
|
export interface HyphenationPatternsMap {
|
|
@@ -12,6 +12,7 @@ export interface VectorTextResult {
|
|
|
12
12
|
query(options: TextQueryOptions): TextRange[];
|
|
13
13
|
getLoadedFont(): LoadedFont | undefined;
|
|
14
14
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
15
|
+
setColor(r: number, g: number, b: number): void;
|
|
15
16
|
update(options: Partial<TextOptions>): Promise<VectorTextResult>;
|
|
16
17
|
dispose(): void;
|
|
17
18
|
}
|
|
@@ -258,7 +258,6 @@ class GlyphOutlineCollector {
|
|
|
258
258
|
// GPU-ready packed textures + vertex attribute buffers
|
|
259
259
|
const TEX_WIDTH = 4096;
|
|
260
260
|
const LOG_TEX_WIDTH = 12;
|
|
261
|
-
// Float/Uint32 reinterpretation helpers
|
|
262
261
|
const _f32 = new Float32Array(1);
|
|
263
262
|
const _u32 = new Uint32Array(_f32.buffer);
|
|
264
263
|
function uintAsFloat(u) {
|
|
@@ -270,9 +269,7 @@ function uintAsFloat(u) {
|
|
|
270
269
|
function packSlugData(shapes, options) {
|
|
271
270
|
const bandCount = options?.bandCount ?? 16;
|
|
272
271
|
const evenOdd = options?.evenOdd ?? false;
|
|
273
|
-
// Pack all curves into curveTexture
|
|
274
272
|
const allCurves = [];
|
|
275
|
-
// Estimate max texels needed
|
|
276
273
|
let totalCurves = 0;
|
|
277
274
|
for (const shape of shapes) {
|
|
278
275
|
totalCurves += shape.curves.length;
|
|
@@ -285,13 +282,12 @@ function packSlugData(shapes, options) {
|
|
|
285
282
|
const entries = [];
|
|
286
283
|
const [ox, oy] = shape.bounds;
|
|
287
284
|
for (const curve of shape.curves) {
|
|
288
|
-
// Don't let a curve span across row boundary (needs 2 consecutive texels)
|
|
289
285
|
if (curveX >= TEX_WIDTH - 1) {
|
|
290
286
|
curveX = 0;
|
|
291
287
|
curveY++;
|
|
292
288
|
}
|
|
293
|
-
//
|
|
294
|
-
//
|
|
289
|
+
// Glyph-local space (relative to bounds min) keeps renderCoord
|
|
290
|
+
// subtraction near zero, preserving float32 precision in the solver
|
|
295
291
|
const lp1x = curve.p1[0] - ox, lp1y = curve.p1[1] - oy;
|
|
296
292
|
const lp2x = curve.p2[0] - ox, lp2y = curve.p2[1] - oy;
|
|
297
293
|
const lp3x = curve.p3[0] - ox, lp3y = curve.p3[1] - oy;
|
|
@@ -320,12 +316,10 @@ function packSlugData(shapes, options) {
|
|
|
320
316
|
allCurves.push(entries);
|
|
321
317
|
}
|
|
322
318
|
const actualCurveTexHeight = curveY + 1;
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
// [
|
|
326
|
-
// [hBandMax+
|
|
327
|
-
// [hBandMax+vBandMax+2 .. ] : curve index lists
|
|
328
|
-
// First pass: compute total band texels needed
|
|
319
|
+
// Band texture layout per shape (relative to glyphLoc):
|
|
320
|
+
// [0 .. hBandMax] h-band headers
|
|
321
|
+
// [hBandMax+1 .. hBandMax+1+vBandMax] v-band headers
|
|
322
|
+
// [hBandMax+vBandMax+2 .. ] curve index lists
|
|
329
323
|
const shapeBandData = [];
|
|
330
324
|
let totalBandTexels = 0;
|
|
331
325
|
for (let si = 0; si < shapes.length; si++) {
|
|
@@ -345,21 +339,19 @@ function packSlugData(shapes, options) {
|
|
|
345
339
|
const vBandCount = Math.min(bandCount, 255);
|
|
346
340
|
const bandMaxY = hBandCount - 1;
|
|
347
341
|
const bandMaxX = vBandCount - 1;
|
|
348
|
-
//
|
|
342
|
+
// Horizontal bands (partition y-axis)
|
|
349
343
|
const hBands = [];
|
|
350
344
|
const hLists = [];
|
|
351
345
|
const bandH = h / hBandCount;
|
|
352
346
|
for (let bi = 0; bi < hBandCount; bi++) {
|
|
353
347
|
const bandMinY = bi * bandH;
|
|
354
348
|
const bandMaxYCoord = bandMinY + bandH;
|
|
355
|
-
// Collect curves whose y-range overlaps this band
|
|
356
349
|
const list = [];
|
|
357
350
|
for (const c of curves) {
|
|
358
351
|
if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
|
|
359
352
|
list.push({ curve: c, sortKey: c.maxX });
|
|
360
353
|
}
|
|
361
354
|
}
|
|
362
|
-
// Sort by descending max-x for early exit
|
|
363
355
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
364
356
|
const flatList = [];
|
|
365
357
|
for (const item of list) {
|
|
@@ -368,7 +360,7 @@ function packSlugData(shapes, options) {
|
|
|
368
360
|
hBands.push({ curveCount: list.length, listOffset: 0 });
|
|
369
361
|
hLists.push(flatList);
|
|
370
362
|
}
|
|
371
|
-
//
|
|
363
|
+
// Vertical bands (partition x-axis)
|
|
372
364
|
const vBands = [];
|
|
373
365
|
const vLists = [];
|
|
374
366
|
const bandW = w / vBandCount;
|
|
@@ -381,7 +373,6 @@ function packSlugData(shapes, options) {
|
|
|
381
373
|
list.push({ curve: c, sortKey: c.maxY });
|
|
382
374
|
}
|
|
383
375
|
}
|
|
384
|
-
// Sort by descending max-y for early exit
|
|
385
376
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
386
377
|
const flatList = [];
|
|
387
378
|
for (const item of list) {
|
|
@@ -390,7 +381,6 @@ function packSlugData(shapes, options) {
|
|
|
390
381
|
vBands.push({ curveCount: list.length, listOffset: 0 });
|
|
391
382
|
vLists.push(flatList);
|
|
392
383
|
}
|
|
393
|
-
// Total texels for this shape: band headers + curve lists
|
|
394
384
|
const headerTexels = hBandCount + vBandCount;
|
|
395
385
|
let listTexels = 0;
|
|
396
386
|
for (const l of hLists)
|
|
@@ -404,10 +394,8 @@ function packSlugData(shapes, options) {
|
|
|
404
394
|
});
|
|
405
395
|
totalBandTexels += total;
|
|
406
396
|
}
|
|
407
|
-
// Allocate bandTexture (extra rows for row-alignment padding of curve lists)
|
|
408
397
|
const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
|
|
409
398
|
const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
|
|
410
|
-
// Pack band data per shape
|
|
411
399
|
let bandX = 0;
|
|
412
400
|
let bandY = 0;
|
|
413
401
|
const glyphLocs = [];
|
|
@@ -417,11 +405,8 @@ function packSlugData(shapes, options) {
|
|
|
417
405
|
glyphLocs.push({ x: 0, y: 0 });
|
|
418
406
|
continue;
|
|
419
407
|
}
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
// But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
|
|
423
|
-
// must be reachable. CalcBandLoc handles wrapping for curve lists.
|
|
424
|
-
// To be safe, start each glyph at the beginning of a row if remaining space is tight.
|
|
408
|
+
// Band headers are read without CalcBandLoc wrapping, so all
|
|
409
|
+
// headers for a glyph must fit within a single texture row
|
|
425
410
|
const minContiguous = sd.hBands.length + sd.vBands.length;
|
|
426
411
|
if (bandX + minContiguous > TEX_WIDTH) {
|
|
427
412
|
bandX = 0;
|
|
@@ -430,11 +415,8 @@ function packSlugData(shapes, options) {
|
|
|
430
415
|
const glyphLocX = bandX;
|
|
431
416
|
const glyphLocY = bandY;
|
|
432
417
|
glyphLocs.push({ x: glyphLocX, y: glyphLocY });
|
|
433
|
-
// Curve lists start after all headers
|
|
434
418
|
let listStartOffset = sd.hBands.length + sd.vBands.length;
|
|
435
|
-
//
|
|
436
|
-
// with NO row wrapping. Each list must fit entirely within a single texture row.
|
|
437
|
-
// Pad the offset to the next row start when a list would cross a row boundary.
|
|
419
|
+
// Curve lists aren't row-wrapped by the shader, so pad to avoid crossing
|
|
438
420
|
const ensureListFits = (listLen) => {
|
|
439
421
|
if (listLen === 0)
|
|
440
422
|
return;
|
|
@@ -443,21 +425,18 @@ function packSlugData(shapes, options) {
|
|
|
443
425
|
listStartOffset += (TEX_WIDTH - startX);
|
|
444
426
|
}
|
|
445
427
|
};
|
|
446
|
-
// Assign list offsets for h-bands
|
|
447
428
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
448
429
|
const listLen = sd.hLists[bi].length / 2;
|
|
449
430
|
ensureListFits(listLen);
|
|
450
431
|
sd.hBands[bi].listOffset = listStartOffset;
|
|
451
432
|
listStartOffset += listLen;
|
|
452
433
|
}
|
|
453
|
-
// Assign list offsets for v-bands
|
|
454
434
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
455
435
|
const listLen = sd.vLists[bi].length / 2;
|
|
456
436
|
ensureListFits(listLen);
|
|
457
437
|
sd.vBands[bi].listOffset = listStartOffset;
|
|
458
438
|
listStartOffset += listLen;
|
|
459
439
|
}
|
|
460
|
-
// Write h-band headers
|
|
461
440
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
462
441
|
const tx = glyphLocX + bi;
|
|
463
442
|
const ty = glyphLocY;
|
|
@@ -467,7 +446,6 @@ function packSlugData(shapes, options) {
|
|
|
467
446
|
bandData[idx + 2] = 0;
|
|
468
447
|
bandData[idx + 3] = 0;
|
|
469
448
|
}
|
|
470
|
-
// Write v-band headers (after h-bands)
|
|
471
449
|
const vBandStart = glyphLocX + sd.hBands.length;
|
|
472
450
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
473
451
|
const tx = vBandStart + bi;
|
|
@@ -478,9 +456,7 @@ function packSlugData(shapes, options) {
|
|
|
478
456
|
bandData[idx + 2] = 0;
|
|
479
457
|
bandData[idx + 3] = 0;
|
|
480
458
|
}
|
|
481
|
-
// Write curve lists using CalcBandLoc-style wrapping
|
|
482
459
|
const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
|
|
483
|
-
// Write h-band curve lists
|
|
484
460
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
485
461
|
const list = sd.hLists[bi];
|
|
486
462
|
const baseOffset = sd.hBands[bi].listOffset;
|
|
@@ -489,13 +465,12 @@ function packSlugData(shapes, options) {
|
|
|
489
465
|
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
490
466
|
bx &= texWidthMask;
|
|
491
467
|
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
492
|
-
bandData[idx + 0] = list[ci];
|
|
493
|
-
bandData[idx + 1] = list[ci + 1];
|
|
468
|
+
bandData[idx + 0] = list[ci];
|
|
469
|
+
bandData[idx + 1] = list[ci + 1];
|
|
494
470
|
bandData[idx + 2] = 0;
|
|
495
471
|
bandData[idx + 3] = 0;
|
|
496
472
|
}
|
|
497
473
|
}
|
|
498
|
-
// Write v-band curve lists
|
|
499
474
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
500
475
|
const list = sd.vLists[bi];
|
|
501
476
|
const baseOffset = sd.vBands[bi].listOffset;
|
|
@@ -510,24 +485,20 @@ function packSlugData(shapes, options) {
|
|
|
510
485
|
bandData[idx + 3] = 0;
|
|
511
486
|
}
|
|
512
487
|
}
|
|
513
|
-
// Advance band cursor past this shape's data
|
|
514
488
|
let endBx = glyphLocX + listStartOffset;
|
|
515
489
|
bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
|
|
516
490
|
bandX = endBx & texWidthMask;
|
|
517
491
|
}
|
|
518
492
|
const actualBandTexHeight = bandY + 1;
|
|
519
|
-
// Build vertex attributes
|
|
520
|
-
// 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
|
|
521
493
|
const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
|
|
522
494
|
const VERTS_PER_SHAPE = 4;
|
|
523
495
|
const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
|
|
524
496
|
const indices = new Uint16Array(shapes.length * 6);
|
|
525
|
-
// Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
|
|
526
497
|
const cornerNormals = [
|
|
527
|
-
[-1, -1],
|
|
528
|
-
[1, -1],
|
|
529
|
-
[1, 1],
|
|
530
|
-
[-1, 1],
|
|
498
|
+
[-1, -1],
|
|
499
|
+
[1, -1],
|
|
500
|
+
[1, 1],
|
|
501
|
+
[-1, 1],
|
|
531
502
|
];
|
|
532
503
|
for (let si = 0; si < shapes.length; si++) {
|
|
533
504
|
const shape = shapes[si];
|
|
@@ -536,28 +507,23 @@ function packSlugData(shapes, options) {
|
|
|
536
507
|
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
537
508
|
const w = bMaxX - bMinX;
|
|
538
509
|
const h = bMaxY - bMinY;
|
|
539
|
-
// Corner positions in object-space
|
|
540
510
|
const corners = [
|
|
541
511
|
[bMinX, bMinY],
|
|
542
512
|
[bMaxX, bMinY],
|
|
543
513
|
[bMaxX, bMaxY],
|
|
544
514
|
[bMinX, bMaxY],
|
|
545
515
|
];
|
|
546
|
-
// Em-space sample coords in glyph-local space (origin at bounds min)
|
|
547
516
|
const emCorners = [
|
|
548
517
|
[0, 0],
|
|
549
518
|
[w, 0],
|
|
550
519
|
[w, h],
|
|
551
520
|
[0, h],
|
|
552
521
|
];
|
|
553
|
-
// Pack tex.z: glyph location in band texture
|
|
554
522
|
const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
|
|
555
|
-
// Pack tex.w: band max + flags
|
|
556
523
|
let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
|
|
557
524
|
if (evenOdd)
|
|
558
525
|
texWBits |= 0x10000000; // E flag at bit 28
|
|
559
526
|
const texW = uintAsFloat(texWBits);
|
|
560
|
-
// Band transform: scale to map glyph-local em-coords to band indices
|
|
561
527
|
const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
|
|
562
528
|
const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
|
|
563
529
|
for (let vi = 0; vi < 4; vi++) {
|
|
@@ -572,23 +538,22 @@ function packSlugData(shapes, options) {
|
|
|
572
538
|
vertices[base + 5] = emCorners[vi][1];
|
|
573
539
|
vertices[base + 6] = texZ;
|
|
574
540
|
vertices[base + 7] = texW;
|
|
575
|
-
// jac: identity
|
|
541
|
+
// jac: identity (em-space is a translation of object-space)
|
|
576
542
|
vertices[base + 8] = 1.0;
|
|
577
543
|
vertices[base + 9] = 0.0;
|
|
578
544
|
vertices[base + 10] = 0.0;
|
|
579
545
|
vertices[base + 11] = 1.0;
|
|
580
|
-
// bnd: band scale (offset
|
|
546
|
+
// bnd: band scale (offset zero in glyph-local space)
|
|
581
547
|
vertices[base + 12] = bandScaleX;
|
|
582
548
|
vertices[base + 13] = bandScaleY;
|
|
583
549
|
vertices[base + 14] = 0;
|
|
584
550
|
vertices[base + 15] = 0;
|
|
585
|
-
// col
|
|
551
|
+
// col
|
|
586
552
|
vertices[base + 16] = 1.0;
|
|
587
553
|
vertices[base + 17] = 1.0;
|
|
588
554
|
vertices[base + 18] = 1.0;
|
|
589
555
|
vertices[base + 19] = 1.0;
|
|
590
556
|
}
|
|
591
|
-
// Indices: two triangles per quad
|
|
592
557
|
const vBase = si * 4;
|
|
593
558
|
const iBase = si * 6;
|
|
594
559
|
indices[iBase + 0] = vBase + 0;
|
|
@@ -797,7 +762,6 @@ function computePlaneBounds(glyphInfos) {
|
|
|
797
762
|
}
|
|
798
763
|
return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
|
|
799
764
|
}
|
|
800
|
-
// Public API
|
|
801
765
|
function buildVectorResult(layoutHandle, ctx, options) {
|
|
802
766
|
const scale = layoutHandle.layoutData.pixelsPerFontUnit;
|
|
803
767
|
let cachedQuery = null;
|