three-text 0.6.0 → 0.6.2
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.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/vector/core/index.cjs +45 -77
- package/dist/vector/core/index.js +45 -77
- package/dist/vector/index2.cjs +5 -19
- package/dist/vector/index2.js +5 -19
- 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.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
|
@@ -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;
|
|
@@ -283,28 +280,33 @@ function packSlugData(shapes, options) {
|
|
|
283
280
|
let curveY = 0;
|
|
284
281
|
for (const shape of shapes) {
|
|
285
282
|
const entries = [];
|
|
283
|
+
const [ox, oy] = shape.bounds;
|
|
286
284
|
for (const curve of shape.curves) {
|
|
287
|
-
// Don't let a curve span across row boundary (needs 2 consecutive texels)
|
|
288
285
|
if (curveX >= TEX_WIDTH - 1) {
|
|
289
286
|
curveX = 0;
|
|
290
287
|
curveY++;
|
|
291
288
|
}
|
|
289
|
+
// Glyph-local space (relative to bounds min) keeps renderCoord
|
|
290
|
+
// subtraction near zero, preserving float32 precision in the solver
|
|
291
|
+
const lp1x = curve.p1[0] - ox, lp1y = curve.p1[1] - oy;
|
|
292
|
+
const lp2x = curve.p2[0] - ox, lp2y = curve.p2[1] - oy;
|
|
293
|
+
const lp3x = curve.p3[0] - ox, lp3y = curve.p3[1] - oy;
|
|
292
294
|
const base = (curveY * TEX_WIDTH + curveX) * 4;
|
|
293
|
-
curveData[base + 0] =
|
|
294
|
-
curveData[base + 1] =
|
|
295
|
-
curveData[base + 2] =
|
|
296
|
-
curveData[base + 3] =
|
|
295
|
+
curveData[base + 0] = lp1x;
|
|
296
|
+
curveData[base + 1] = lp1y;
|
|
297
|
+
curveData[base + 2] = lp2x;
|
|
298
|
+
curveData[base + 3] = lp2y;
|
|
297
299
|
const base2 = base + 4;
|
|
298
|
-
curveData[base2 + 0] =
|
|
299
|
-
curveData[base2 + 1] =
|
|
300
|
-
const minX = Math.min(
|
|
301
|
-
const minY = Math.min(
|
|
302
|
-
const maxX = Math.max(
|
|
303
|
-
const maxY = Math.max(
|
|
300
|
+
curveData[base2 + 0] = lp3x;
|
|
301
|
+
curveData[base2 + 1] = lp3y;
|
|
302
|
+
const minX = Math.min(lp1x, lp2x, lp3x);
|
|
303
|
+
const minY = Math.min(lp1y, lp2y, lp3y);
|
|
304
|
+
const maxX = Math.max(lp1x, lp2x, lp3x);
|
|
305
|
+
const maxY = Math.max(lp1y, lp2y, lp3y);
|
|
304
306
|
entries.push({
|
|
305
|
-
p1x:
|
|
306
|
-
p2x:
|
|
307
|
-
p3x:
|
|
307
|
+
p1x: lp1x, p1y: lp1y,
|
|
308
|
+
p2x: lp2x, p2y: lp2y,
|
|
309
|
+
p3x: lp3x, p3y: lp3y,
|
|
308
310
|
minX, minY, maxX, maxY,
|
|
309
311
|
curveTexX: curveX,
|
|
310
312
|
curveTexY: curveY
|
|
@@ -314,12 +316,10 @@ function packSlugData(shapes, options) {
|
|
|
314
316
|
allCurves.push(entries);
|
|
315
317
|
}
|
|
316
318
|
const actualCurveTexHeight = curveY + 1;
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// [
|
|
320
|
-
// [hBandMax+
|
|
321
|
-
// [hBandMax+vBandMax+2 .. ] : curve index lists
|
|
322
|
-
// 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
|
|
323
323
|
const shapeBandData = [];
|
|
324
324
|
let totalBandTexels = 0;
|
|
325
325
|
for (let si = 0; si < shapes.length; si++) {
|
|
@@ -339,21 +339,19 @@ function packSlugData(shapes, options) {
|
|
|
339
339
|
const vBandCount = Math.min(bandCount, 255);
|
|
340
340
|
const bandMaxY = hBandCount - 1;
|
|
341
341
|
const bandMaxX = vBandCount - 1;
|
|
342
|
-
//
|
|
342
|
+
// Horizontal bands (partition y-axis)
|
|
343
343
|
const hBands = [];
|
|
344
344
|
const hLists = [];
|
|
345
345
|
const bandH = h / hBandCount;
|
|
346
346
|
for (let bi = 0; bi < hBandCount; bi++) {
|
|
347
|
-
const bandMinY =
|
|
347
|
+
const bandMinY = bi * bandH;
|
|
348
348
|
const bandMaxYCoord = bandMinY + bandH;
|
|
349
|
-
// Collect curves whose y-range overlaps this band
|
|
350
349
|
const list = [];
|
|
351
350
|
for (const c of curves) {
|
|
352
351
|
if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
|
|
353
352
|
list.push({ curve: c, sortKey: c.maxX });
|
|
354
353
|
}
|
|
355
354
|
}
|
|
356
|
-
// Sort by descending max-x for early exit
|
|
357
355
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
358
356
|
const flatList = [];
|
|
359
357
|
for (const item of list) {
|
|
@@ -362,12 +360,12 @@ function packSlugData(shapes, options) {
|
|
|
362
360
|
hBands.push({ curveCount: list.length, listOffset: 0 });
|
|
363
361
|
hLists.push(flatList);
|
|
364
362
|
}
|
|
365
|
-
//
|
|
363
|
+
// Vertical bands (partition x-axis)
|
|
366
364
|
const vBands = [];
|
|
367
365
|
const vLists = [];
|
|
368
366
|
const bandW = w / vBandCount;
|
|
369
367
|
for (let bi = 0; bi < vBandCount; bi++) {
|
|
370
|
-
const bandMinX =
|
|
368
|
+
const bandMinX = bi * bandW;
|
|
371
369
|
const bandMaxXCoord = bandMinX + bandW;
|
|
372
370
|
const list = [];
|
|
373
371
|
for (const c of curves) {
|
|
@@ -375,7 +373,6 @@ function packSlugData(shapes, options) {
|
|
|
375
373
|
list.push({ curve: c, sortKey: c.maxY });
|
|
376
374
|
}
|
|
377
375
|
}
|
|
378
|
-
// Sort by descending max-y for early exit
|
|
379
376
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
380
377
|
const flatList = [];
|
|
381
378
|
for (const item of list) {
|
|
@@ -384,7 +381,6 @@ function packSlugData(shapes, options) {
|
|
|
384
381
|
vBands.push({ curveCount: list.length, listOffset: 0 });
|
|
385
382
|
vLists.push(flatList);
|
|
386
383
|
}
|
|
387
|
-
// Total texels for this shape: band headers + curve lists
|
|
388
384
|
const headerTexels = hBandCount + vBandCount;
|
|
389
385
|
let listTexels = 0;
|
|
390
386
|
for (const l of hLists)
|
|
@@ -398,10 +394,8 @@ function packSlugData(shapes, options) {
|
|
|
398
394
|
});
|
|
399
395
|
totalBandTexels += total;
|
|
400
396
|
}
|
|
401
|
-
// Allocate bandTexture (extra rows for row-alignment padding of curve lists)
|
|
402
397
|
const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
|
|
403
398
|
const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
|
|
404
|
-
// Pack band data per shape
|
|
405
399
|
let bandX = 0;
|
|
406
400
|
let bandY = 0;
|
|
407
401
|
const glyphLocs = [];
|
|
@@ -411,11 +405,8 @@ function packSlugData(shapes, options) {
|
|
|
411
405
|
glyphLocs.push({ x: 0, y: 0 });
|
|
412
406
|
continue;
|
|
413
407
|
}
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
// But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
|
|
417
|
-
// must be reachable. CalcBandLoc handles wrapping for curve lists.
|
|
418
|
-
// 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
|
|
419
410
|
const minContiguous = sd.hBands.length + sd.vBands.length;
|
|
420
411
|
if (bandX + minContiguous > TEX_WIDTH) {
|
|
421
412
|
bandX = 0;
|
|
@@ -424,11 +415,8 @@ function packSlugData(shapes, options) {
|
|
|
424
415
|
const glyphLocX = bandX;
|
|
425
416
|
const glyphLocY = bandY;
|
|
426
417
|
glyphLocs.push({ x: glyphLocX, y: glyphLocY });
|
|
427
|
-
// Curve lists start after all headers
|
|
428
418
|
let listStartOffset = sd.hBands.length + sd.vBands.length;
|
|
429
|
-
//
|
|
430
|
-
// with NO row wrapping. Each list must fit entirely within a single texture row.
|
|
431
|
-
// 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
|
|
432
420
|
const ensureListFits = (listLen) => {
|
|
433
421
|
if (listLen === 0)
|
|
434
422
|
return;
|
|
@@ -437,21 +425,18 @@ function packSlugData(shapes, options) {
|
|
|
437
425
|
listStartOffset += (TEX_WIDTH - startX);
|
|
438
426
|
}
|
|
439
427
|
};
|
|
440
|
-
// Assign list offsets for h-bands
|
|
441
428
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
442
429
|
const listLen = sd.hLists[bi].length / 2;
|
|
443
430
|
ensureListFits(listLen);
|
|
444
431
|
sd.hBands[bi].listOffset = listStartOffset;
|
|
445
432
|
listStartOffset += listLen;
|
|
446
433
|
}
|
|
447
|
-
// Assign list offsets for v-bands
|
|
448
434
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
449
435
|
const listLen = sd.vLists[bi].length / 2;
|
|
450
436
|
ensureListFits(listLen);
|
|
451
437
|
sd.vBands[bi].listOffset = listStartOffset;
|
|
452
438
|
listStartOffset += listLen;
|
|
453
439
|
}
|
|
454
|
-
// Write h-band headers
|
|
455
440
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
456
441
|
const tx = glyphLocX + bi;
|
|
457
442
|
const ty = glyphLocY;
|
|
@@ -461,7 +446,6 @@ function packSlugData(shapes, options) {
|
|
|
461
446
|
bandData[idx + 2] = 0;
|
|
462
447
|
bandData[idx + 3] = 0;
|
|
463
448
|
}
|
|
464
|
-
// Write v-band headers (after h-bands)
|
|
465
449
|
const vBandStart = glyphLocX + sd.hBands.length;
|
|
466
450
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
467
451
|
const tx = vBandStart + bi;
|
|
@@ -472,9 +456,7 @@ function packSlugData(shapes, options) {
|
|
|
472
456
|
bandData[idx + 2] = 0;
|
|
473
457
|
bandData[idx + 3] = 0;
|
|
474
458
|
}
|
|
475
|
-
// Write curve lists using CalcBandLoc-style wrapping
|
|
476
459
|
const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
|
|
477
|
-
// Write h-band curve lists
|
|
478
460
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
479
461
|
const list = sd.hLists[bi];
|
|
480
462
|
const baseOffset = sd.hBands[bi].listOffset;
|
|
@@ -483,13 +465,12 @@ function packSlugData(shapes, options) {
|
|
|
483
465
|
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
484
466
|
bx &= texWidthMask;
|
|
485
467
|
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
486
|
-
bandData[idx + 0] = list[ci];
|
|
487
|
-
bandData[idx + 1] = list[ci + 1];
|
|
468
|
+
bandData[idx + 0] = list[ci];
|
|
469
|
+
bandData[idx + 1] = list[ci + 1];
|
|
488
470
|
bandData[idx + 2] = 0;
|
|
489
471
|
bandData[idx + 3] = 0;
|
|
490
472
|
}
|
|
491
473
|
}
|
|
492
|
-
// Write v-band curve lists
|
|
493
474
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
494
475
|
const list = sd.vLists[bi];
|
|
495
476
|
const baseOffset = sd.vBands[bi].listOffset;
|
|
@@ -504,24 +485,20 @@ function packSlugData(shapes, options) {
|
|
|
504
485
|
bandData[idx + 3] = 0;
|
|
505
486
|
}
|
|
506
487
|
}
|
|
507
|
-
// Advance band cursor past this shape's data
|
|
508
488
|
let endBx = glyphLocX + listStartOffset;
|
|
509
489
|
bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
|
|
510
490
|
bandX = endBx & texWidthMask;
|
|
511
491
|
}
|
|
512
492
|
const actualBandTexHeight = bandY + 1;
|
|
513
|
-
// Build vertex attributes
|
|
514
|
-
// 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
|
|
515
493
|
const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
|
|
516
494
|
const VERTS_PER_SHAPE = 4;
|
|
517
495
|
const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
|
|
518
496
|
const indices = new Uint16Array(shapes.length * 6);
|
|
519
|
-
// Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
|
|
520
497
|
const cornerNormals = [
|
|
521
|
-
[-1, -1],
|
|
522
|
-
[1, -1],
|
|
523
|
-
[1, 1],
|
|
524
|
-
[-1, 1],
|
|
498
|
+
[-1, -1],
|
|
499
|
+
[1, -1],
|
|
500
|
+
[1, 1],
|
|
501
|
+
[-1, 1],
|
|
525
502
|
];
|
|
526
503
|
for (let si = 0; si < shapes.length; si++) {
|
|
527
504
|
const shape = shapes[si];
|
|
@@ -530,32 +507,25 @@ function packSlugData(shapes, options) {
|
|
|
530
507
|
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
531
508
|
const w = bMaxX - bMinX;
|
|
532
509
|
const h = bMaxY - bMinY;
|
|
533
|
-
// Corner positions in object-space
|
|
534
510
|
const corners = [
|
|
535
511
|
[bMinX, bMinY],
|
|
536
512
|
[bMaxX, bMinY],
|
|
537
513
|
[bMaxX, bMaxY],
|
|
538
514
|
[bMinX, bMaxY],
|
|
539
515
|
];
|
|
540
|
-
// Em-space sample coords at corners (same as object-space for 1:1 mapping)
|
|
541
516
|
const emCorners = [
|
|
542
|
-
[
|
|
543
|
-
[
|
|
544
|
-
[
|
|
545
|
-
[
|
|
517
|
+
[0, 0],
|
|
518
|
+
[w, 0],
|
|
519
|
+
[w, h],
|
|
520
|
+
[0, h],
|
|
546
521
|
];
|
|
547
|
-
// Pack tex.z: glyph location in band texture
|
|
548
522
|
const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
|
|
549
|
-
// Pack tex.w: band max + flags
|
|
550
523
|
let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
|
|
551
524
|
if (evenOdd)
|
|
552
525
|
texWBits |= 0x10000000; // E flag at bit 28
|
|
553
526
|
const texW = uintAsFloat(texWBits);
|
|
554
|
-
// Band transform: scale and offset to map em-coords to band indices
|
|
555
527
|
const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
|
|
556
528
|
const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
|
|
557
|
-
const bandOffsetX = -bMinX * bandScaleX;
|
|
558
|
-
const bandOffsetY = -bMinY * bandScaleY;
|
|
559
529
|
for (let vi = 0; vi < 4; vi++) {
|
|
560
530
|
const base = (si * 4 + vi) * FLOATS_PER_VERTEX;
|
|
561
531
|
// pos: .xy = position, .zw = normal
|
|
@@ -568,23 +538,22 @@ function packSlugData(shapes, options) {
|
|
|
568
538
|
vertices[base + 5] = emCorners[vi][1];
|
|
569
539
|
vertices[base + 6] = texZ;
|
|
570
540
|
vertices[base + 7] = texW;
|
|
571
|
-
// jac: identity
|
|
541
|
+
// jac: identity (em-space is a translation of object-space)
|
|
572
542
|
vertices[base + 8] = 1.0;
|
|
573
543
|
vertices[base + 9] = 0.0;
|
|
574
544
|
vertices[base + 10] = 0.0;
|
|
575
545
|
vertices[base + 11] = 1.0;
|
|
576
|
-
// bnd: band scale
|
|
546
|
+
// bnd: band scale (offset zero in glyph-local space)
|
|
577
547
|
vertices[base + 12] = bandScaleX;
|
|
578
548
|
vertices[base + 13] = bandScaleY;
|
|
579
|
-
vertices[base + 14] =
|
|
580
|
-
vertices[base + 15] =
|
|
581
|
-
// col
|
|
549
|
+
vertices[base + 14] = 0;
|
|
550
|
+
vertices[base + 15] = 0;
|
|
551
|
+
// col
|
|
582
552
|
vertices[base + 16] = 1.0;
|
|
583
553
|
vertices[base + 17] = 1.0;
|
|
584
554
|
vertices[base + 18] = 1.0;
|
|
585
555
|
vertices[base + 19] = 1.0;
|
|
586
556
|
}
|
|
587
|
-
// Indices: two triangles per quad
|
|
588
557
|
const vBase = si * 4;
|
|
589
558
|
const iBase = si * 6;
|
|
590
559
|
indices[iBase + 0] = vBase + 0;
|
|
@@ -793,7 +762,6 @@ function computePlaneBounds(glyphInfos) {
|
|
|
793
762
|
}
|
|
794
763
|
return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
|
|
795
764
|
}
|
|
796
|
-
// Public API
|
|
797
765
|
function buildVectorResult(layoutHandle, ctx, options) {
|
|
798
766
|
const scale = layoutHandle.layoutData.pixelsPerFontUnit;
|
|
799
767
|
let cachedQuery = null;
|