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.
@@ -256,7 +256,6 @@ class GlyphOutlineCollector {
256
256
  // GPU-ready packed textures + vertex attribute buffers
257
257
  const TEX_WIDTH = 4096;
258
258
  const LOG_TEX_WIDTH = 12;
259
- // Float/Uint32 reinterpretation helpers
260
259
  const _f32 = new Float32Array(1);
261
260
  const _u32 = new Uint32Array(_f32.buffer);
262
261
  function uintAsFloat(u) {
@@ -268,9 +267,7 @@ function uintAsFloat(u) {
268
267
  function packSlugData(shapes, options) {
269
268
  const bandCount = options?.bandCount ?? 16;
270
269
  const evenOdd = options?.evenOdd ?? false;
271
- // Pack all curves into curveTexture
272
270
  const allCurves = [];
273
- // Estimate max texels needed
274
271
  let totalCurves = 0;
275
272
  for (const shape of shapes) {
276
273
  totalCurves += shape.curves.length;
@@ -281,28 +278,33 @@ function packSlugData(shapes, options) {
281
278
  let curveY = 0;
282
279
  for (const shape of shapes) {
283
280
  const entries = [];
281
+ const [ox, oy] = shape.bounds;
284
282
  for (const curve of shape.curves) {
285
- // Don't let a curve span across row boundary (needs 2 consecutive texels)
286
283
  if (curveX >= TEX_WIDTH - 1) {
287
284
  curveX = 0;
288
285
  curveY++;
289
286
  }
287
+ // Glyph-local space (relative to bounds min) keeps renderCoord
288
+ // subtraction near zero, preserving float32 precision in the solver
289
+ const lp1x = curve.p1[0] - ox, lp1y = curve.p1[1] - oy;
290
+ const lp2x = curve.p2[0] - ox, lp2y = curve.p2[1] - oy;
291
+ const lp3x = curve.p3[0] - ox, lp3y = curve.p3[1] - oy;
290
292
  const base = (curveY * TEX_WIDTH + curveX) * 4;
291
- curveData[base + 0] = curve.p1[0];
292
- curveData[base + 1] = curve.p1[1];
293
- curveData[base + 2] = curve.p2[0];
294
- curveData[base + 3] = curve.p2[1];
293
+ curveData[base + 0] = lp1x;
294
+ curveData[base + 1] = lp1y;
295
+ curveData[base + 2] = lp2x;
296
+ curveData[base + 3] = lp2y;
295
297
  const base2 = base + 4;
296
- curveData[base2 + 0] = curve.p3[0];
297
- curveData[base2 + 1] = curve.p3[1];
298
- const minX = Math.min(curve.p1[0], curve.p2[0], curve.p3[0]);
299
- const minY = Math.min(curve.p1[1], curve.p2[1], curve.p3[1]);
300
- const maxX = Math.max(curve.p1[0], curve.p2[0], curve.p3[0]);
301
- const maxY = Math.max(curve.p1[1], curve.p2[1], curve.p3[1]);
298
+ curveData[base2 + 0] = lp3x;
299
+ curveData[base2 + 1] = lp3y;
300
+ const minX = Math.min(lp1x, lp2x, lp3x);
301
+ const minY = Math.min(lp1y, lp2y, lp3y);
302
+ const maxX = Math.max(lp1x, lp2x, lp3x);
303
+ const maxY = Math.max(lp1y, lp2y, lp3y);
302
304
  entries.push({
303
- p1x: curve.p1[0], p1y: curve.p1[1],
304
- p2x: curve.p2[0], p2y: curve.p2[1],
305
- p3x: curve.p3[0], p3y: curve.p3[1],
305
+ p1x: lp1x, p1y: lp1y,
306
+ p2x: lp2x, p2y: lp2y,
307
+ p3x: lp3x, p3y: lp3y,
306
308
  minX, minY, maxX, maxY,
307
309
  curveTexX: curveX,
308
310
  curveTexY: curveY
@@ -312,12 +314,10 @@ function packSlugData(shapes, options) {
312
314
  allCurves.push(entries);
313
315
  }
314
316
  const actualCurveTexHeight = curveY + 1;
315
- // Build band data for each shape and pack into bandTexture
316
- // Layout per shape in bandTexture (relative to glyphLoc):
317
- // [0 .. hBandMax] : h-band headers
318
- // [hBandMax+1 .. hBandMax+1+vBandMax] : v-band headers
319
- // [hBandMax+vBandMax+2 .. ] : curve index lists
320
- // First pass: compute total band texels needed
317
+ // Band texture layout per shape (relative to glyphLoc):
318
+ // [0 .. hBandMax] h-band headers
319
+ // [hBandMax+1 .. hBandMax+1+vBandMax] v-band headers
320
+ // [hBandMax+vBandMax+2 .. ] curve index lists
321
321
  const shapeBandData = [];
322
322
  let totalBandTexels = 0;
323
323
  for (let si = 0; si < shapes.length; si++) {
@@ -337,21 +337,19 @@ function packSlugData(shapes, options) {
337
337
  const vBandCount = Math.min(bandCount, 255);
338
338
  const bandMaxY = hBandCount - 1;
339
339
  const bandMaxX = vBandCount - 1;
340
- // Build horizontal bands (partition y-axis)
340
+ // Horizontal bands (partition y-axis)
341
341
  const hBands = [];
342
342
  const hLists = [];
343
343
  const bandH = h / hBandCount;
344
344
  for (let bi = 0; bi < hBandCount; bi++) {
345
- const bandMinY = bMinY + bi * bandH;
345
+ const bandMinY = bi * bandH;
346
346
  const bandMaxYCoord = bandMinY + bandH;
347
- // Collect curves whose y-range overlaps this band
348
347
  const list = [];
349
348
  for (const c of curves) {
350
349
  if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
351
350
  list.push({ curve: c, sortKey: c.maxX });
352
351
  }
353
352
  }
354
- // Sort by descending max-x for early exit
355
353
  list.sort((a, b) => b.sortKey - a.sortKey);
356
354
  const flatList = [];
357
355
  for (const item of list) {
@@ -360,12 +358,12 @@ function packSlugData(shapes, options) {
360
358
  hBands.push({ curveCount: list.length, listOffset: 0 });
361
359
  hLists.push(flatList);
362
360
  }
363
- // Build vertical bands (partition x-axis)
361
+ // Vertical bands (partition x-axis)
364
362
  const vBands = [];
365
363
  const vLists = [];
366
364
  const bandW = w / vBandCount;
367
365
  for (let bi = 0; bi < vBandCount; bi++) {
368
- const bandMinX = bMinX + bi * bandW;
366
+ const bandMinX = bi * bandW;
369
367
  const bandMaxXCoord = bandMinX + bandW;
370
368
  const list = [];
371
369
  for (const c of curves) {
@@ -373,7 +371,6 @@ function packSlugData(shapes, options) {
373
371
  list.push({ curve: c, sortKey: c.maxY });
374
372
  }
375
373
  }
376
- // Sort by descending max-y for early exit
377
374
  list.sort((a, b) => b.sortKey - a.sortKey);
378
375
  const flatList = [];
379
376
  for (const item of list) {
@@ -382,7 +379,6 @@ function packSlugData(shapes, options) {
382
379
  vBands.push({ curveCount: list.length, listOffset: 0 });
383
380
  vLists.push(flatList);
384
381
  }
385
- // Total texels for this shape: band headers + curve lists
386
382
  const headerTexels = hBandCount + vBandCount;
387
383
  let listTexels = 0;
388
384
  for (const l of hLists)
@@ -396,10 +392,8 @@ function packSlugData(shapes, options) {
396
392
  });
397
393
  totalBandTexels += total;
398
394
  }
399
- // Allocate bandTexture (extra rows for row-alignment padding of curve lists)
400
395
  const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
401
396
  const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
402
- // Pack band data per shape
403
397
  let bandX = 0;
404
398
  let bandY = 0;
405
399
  const glyphLocs = [];
@@ -409,11 +403,8 @@ function packSlugData(shapes, options) {
409
403
  glyphLocs.push({ x: 0, y: 0 });
410
404
  continue;
411
405
  }
412
- // Ensure glyph data doesn't start too close to row end
413
- // (need at least headerTexels contiguous... actually wrapping is handled by CalcBandLoc)
414
- // But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
415
- // must be reachable. CalcBandLoc handles wrapping for curve lists.
416
- // To be safe, start each glyph at the beginning of a row if remaining space is tight.
406
+ // Band headers are read without CalcBandLoc wrapping, so all
407
+ // headers for a glyph must fit within a single texture row
417
408
  const minContiguous = sd.hBands.length + sd.vBands.length;
418
409
  if (bandX + minContiguous > TEX_WIDTH) {
419
410
  bandX = 0;
@@ -422,11 +413,8 @@ function packSlugData(shapes, options) {
422
413
  const glyphLocX = bandX;
423
414
  const glyphLocY = bandY;
424
415
  glyphLocs.push({ x: glyphLocX, y: glyphLocY });
425
- // Curve lists start after all headers
426
416
  let listStartOffset = sd.hBands.length + sd.vBands.length;
427
- // The shader reads curve list entries at (hbandLoc.x + curveIndex, hbandLoc.y)
428
- // with NO row wrapping. Each list must fit entirely within a single texture row.
429
- // Pad the offset to the next row start when a list would cross a row boundary.
417
+ // Curve lists aren't row-wrapped by the shader, so pad to avoid crossing
430
418
  const ensureListFits = (listLen) => {
431
419
  if (listLen === 0)
432
420
  return;
@@ -435,21 +423,18 @@ function packSlugData(shapes, options) {
435
423
  listStartOffset += (TEX_WIDTH - startX);
436
424
  }
437
425
  };
438
- // Assign list offsets for h-bands
439
426
  for (let bi = 0; bi < sd.hBands.length; bi++) {
440
427
  const listLen = sd.hLists[bi].length / 2;
441
428
  ensureListFits(listLen);
442
429
  sd.hBands[bi].listOffset = listStartOffset;
443
430
  listStartOffset += listLen;
444
431
  }
445
- // Assign list offsets for v-bands
446
432
  for (let bi = 0; bi < sd.vBands.length; bi++) {
447
433
  const listLen = sd.vLists[bi].length / 2;
448
434
  ensureListFits(listLen);
449
435
  sd.vBands[bi].listOffset = listStartOffset;
450
436
  listStartOffset += listLen;
451
437
  }
452
- // Write h-band headers
453
438
  for (let bi = 0; bi < sd.hBands.length; bi++) {
454
439
  const tx = glyphLocX + bi;
455
440
  const ty = glyphLocY;
@@ -459,7 +444,6 @@ function packSlugData(shapes, options) {
459
444
  bandData[idx + 2] = 0;
460
445
  bandData[idx + 3] = 0;
461
446
  }
462
- // Write v-band headers (after h-bands)
463
447
  const vBandStart = glyphLocX + sd.hBands.length;
464
448
  for (let bi = 0; bi < sd.vBands.length; bi++) {
465
449
  const tx = vBandStart + bi;
@@ -470,9 +454,7 @@ function packSlugData(shapes, options) {
470
454
  bandData[idx + 2] = 0;
471
455
  bandData[idx + 3] = 0;
472
456
  }
473
- // Write curve lists using CalcBandLoc-style wrapping
474
457
  const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
475
- // Write h-band curve lists
476
458
  for (let bi = 0; bi < sd.hBands.length; bi++) {
477
459
  const list = sd.hLists[bi];
478
460
  const baseOffset = sd.hBands[bi].listOffset;
@@ -481,13 +463,12 @@ function packSlugData(shapes, options) {
481
463
  const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
482
464
  bx &= texWidthMask;
483
465
  const idx = (by * TEX_WIDTH + bx) * 4;
484
- bandData[idx + 0] = list[ci]; // curveTexX
485
- bandData[idx + 1] = list[ci + 1]; // curveTexY
466
+ bandData[idx + 0] = list[ci];
467
+ bandData[idx + 1] = list[ci + 1];
486
468
  bandData[idx + 2] = 0;
487
469
  bandData[idx + 3] = 0;
488
470
  }
489
471
  }
490
- // Write v-band curve lists
491
472
  for (let bi = 0; bi < sd.vBands.length; bi++) {
492
473
  const list = sd.vLists[bi];
493
474
  const baseOffset = sd.vBands[bi].listOffset;
@@ -502,24 +483,20 @@ function packSlugData(shapes, options) {
502
483
  bandData[idx + 3] = 0;
503
484
  }
504
485
  }
505
- // Advance band cursor past this shape's data
506
486
  let endBx = glyphLocX + listStartOffset;
507
487
  bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
508
488
  bandX = endBx & texWidthMask;
509
489
  }
510
490
  const actualBandTexHeight = bandY + 1;
511
- // Build vertex attributes
512
- // 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
513
491
  const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
514
492
  const VERTS_PER_SHAPE = 4;
515
493
  const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
516
494
  const indices = new Uint16Array(shapes.length * 6);
517
- // Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
518
495
  const cornerNormals = [
519
- [-1, -1], // bottom-left
520
- [1, -1], // bottom-right
521
- [1, 1], // top-right
522
- [-1, 1], // top-left
496
+ [-1, -1],
497
+ [1, -1],
498
+ [1, 1],
499
+ [-1, 1],
523
500
  ];
524
501
  for (let si = 0; si < shapes.length; si++) {
525
502
  const shape = shapes[si];
@@ -528,32 +505,25 @@ function packSlugData(shapes, options) {
528
505
  const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
529
506
  const w = bMaxX - bMinX;
530
507
  const h = bMaxY - bMinY;
531
- // Corner positions in object-space
532
508
  const corners = [
533
509
  [bMinX, bMinY],
534
510
  [bMaxX, bMinY],
535
511
  [bMaxX, bMaxY],
536
512
  [bMinX, bMaxY],
537
513
  ];
538
- // Em-space sample coords at corners (same as object-space for 1:1 mapping)
539
514
  const emCorners = [
540
- [bMinX, bMinY],
541
- [bMaxX, bMinY],
542
- [bMaxX, bMaxY],
543
- [bMinX, bMaxY],
515
+ [0, 0],
516
+ [w, 0],
517
+ [w, h],
518
+ [0, h],
544
519
  ];
545
- // Pack tex.z: glyph location in band texture
546
520
  const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
547
- // Pack tex.w: band max + flags
548
521
  let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
549
522
  if (evenOdd)
550
523
  texWBits |= 0x10000000; // E flag at bit 28
551
524
  const texW = uintAsFloat(texWBits);
552
- // Band transform: scale and offset to map em-coords to band indices
553
525
  const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
554
526
  const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
555
- const bandOffsetX = -bMinX * bandScaleX;
556
- const bandOffsetY = -bMinY * bandScaleY;
557
527
  for (let vi = 0; vi < 4; vi++) {
558
528
  const base = (si * 4 + vi) * FLOATS_PER_VERTEX;
559
529
  // pos: .xy = position, .zw = normal
@@ -566,23 +536,22 @@ function packSlugData(shapes, options) {
566
536
  vertices[base + 5] = emCorners[vi][1];
567
537
  vertices[base + 6] = texZ;
568
538
  vertices[base + 7] = texW;
569
- // jac: identity Jacobian (em-space = object-space)
539
+ // jac: identity (em-space is a translation of object-space)
570
540
  vertices[base + 8] = 1.0;
571
541
  vertices[base + 9] = 0.0;
572
542
  vertices[base + 10] = 0.0;
573
543
  vertices[base + 11] = 1.0;
574
- // bnd: band scale and offset
544
+ // bnd: band scale (offset zero in glyph-local space)
575
545
  vertices[base + 12] = bandScaleX;
576
546
  vertices[base + 13] = bandScaleY;
577
- vertices[base + 14] = bandOffsetX;
578
- vertices[base + 15] = bandOffsetY;
579
- // col: white with full alpha (caller overrides via uniform or attribute)
547
+ vertices[base + 14] = 0;
548
+ vertices[base + 15] = 0;
549
+ // col
580
550
  vertices[base + 16] = 1.0;
581
551
  vertices[base + 17] = 1.0;
582
552
  vertices[base + 18] = 1.0;
583
553
  vertices[base + 19] = 1.0;
584
554
  }
585
- // Indices: two triangles per quad
586
555
  const vBase = si * 4;
587
556
  const iBase = si * 6;
588
557
  indices[iBase + 0] = vBase + 0;
@@ -791,7 +760,6 @@ function computePlaneBounds(glyphInfos) {
791
760
  }
792
761
  return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
793
762
  }
794
- // Public API
795
763
  function buildVectorResult(layoutHandle, ctx, options) {
796
764
  const scale = layoutHandle.layoutData.pixelsPerFontUnit;
797
765
  let cachedQuery = null;
@@ -22,8 +22,6 @@ function _interopNamespaceDefault(e) {
22
22
 
23
23
  var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
24
24
 
25
- // Restructures the tightly-packed vertex buffer from SlugPacker into
26
- // separate per-attribute arrays for GPU attribute binding
27
25
  const FLOATS_PER_VERT = 20;
28
26
  function unpackSlugVertices(gpuData) {
29
27
  const vertCount = gpuData.shapeCount * 4;
@@ -45,7 +43,6 @@ function unpackSlugVertices(gpuData) {
45
43
  bandings[i * 4 + 1] = srcF[s + 13];
46
44
  bandings[i * 4 + 2] = srcF[s + 14];
47
45
  bandings[i * 4 + 3] = srcF[s + 15];
48
- // Unpack glyph location and band metadata from bit-packed fields
49
46
  const g0 = srcU[s + 6];
50
47
  const g1 = srcU[s + 7];
51
48
  glyphData[i * 4] = g0 & 0xFFFF;
@@ -57,7 +54,6 @@ function unpackSlugVertices(gpuData) {
57
54
  colors[i * 4 + 2] = srcF[s + 18];
58
55
  colors[i * 4 + 3] = srcF[s + 19];
59
56
  }
60
- // Per-glyph center: average of quad corner positions
61
57
  const glyphCenters = new Float32Array(vertCount * 3);
62
58
  const glyphIndices = new Float32Array(vertCount);
63
59
  for (let g = 0; g < gpuData.shapeCount; g++) {
@@ -82,23 +78,14 @@ function unpackSlugVertices(gpuData) {
82
78
 
83
79
  var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
84
80
 
85
- // Slug shader source re-exports
86
- // The .glsl/.wgsl files are the single source of truth, imported as strings
87
- // at build time via the glslPlugin in rollup.config.js
88
81
  // @ts-ignore - resolved by rollup glslPlugin
89
82
  const fragmentShaderGLSL300 = fragGLSL;
90
83
 
91
- // Slug GLSL adapter for Three.js, using RawShaderMaterial with the
92
- // reference GLSL shaders. Works with both WebGLRenderer and
93
- // WebGPURenderer (via GLSL-to-WGSL transpilation)
84
+ // Slug adapter using Three.js RawShaderMaterial (GLSL)
85
+ // Works with any Three.js renderer (WebGL or WebGPU via transpilation)
94
86
  //
95
- // Compared to the TSL adapter (slugTSL.ts):
96
- // - Works with any Three.js renderer (no node-material dependency)
97
- // - Uses native Uint32 band texture (no float conversion)
98
- // - Supports GLSL animation injection via animationDeclarations/animationBody
99
- // - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
100
- //
101
- // Requires peer dependency: three
87
+ // Supports GLSL animation injection and native Uint32 band textures
88
+ // No vertex dilation; may clip at sub-pixel edges under extreme zoom
102
89
  // @ts-ignore - three is a peer dependency
103
90
  // Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
104
91
  const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
@@ -149,13 +136,12 @@ function createSlugGLSLMesh(gpuData, options) {
149
136
  geo.setAttribute('glyphCenter', new THREE__namespace.Float32BufferAttribute(attrs.glyphCenters, 3));
150
137
  geo.setAttribute('glyphIndex', new THREE__namespace.Float32BufferAttribute(attrs.glyphIndices, 1));
151
138
  geo.setIndex(new THREE__namespace.BufferAttribute(gpuData.indices, 1));
152
- // Curve texture: RGBA32F
153
139
  const curveTex = new THREE__namespace.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE__namespace.RGBAFormat, THREE__namespace.FloatType);
154
140
  curveTex.minFilter = THREE__namespace.NearestFilter;
155
141
  curveTex.magFilter = THREE__namespace.NearestFilter;
156
142
  curveTex.generateMipmaps = false;
157
143
  curveTex.needsUpdate = true;
158
- // Band texture: native RGBA32UI (no float conversion needed)
144
+ // RGBA32UI (unlike TSL, GLSL supports integer textures natively)
159
145
  const bandTex = new THREE__namespace.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
160
146
  bandTex.format = THREE__namespace.RGBAIntegerFormat;
161
147
  bandTex.type = THREE__namespace.UnsignedIntType;
@@ -1,8 +1,6 @@
1
1
  import { Text as Text$1 } from './core/index.js';
2
2
  import * as THREE from 'three';
3
3
 
4
- // Restructures the tightly-packed vertex buffer from SlugPacker into
5
- // separate per-attribute arrays for GPU attribute binding
6
4
  const FLOATS_PER_VERT = 20;
7
5
  function unpackSlugVertices(gpuData) {
8
6
  const vertCount = gpuData.shapeCount * 4;
@@ -24,7 +22,6 @@ function unpackSlugVertices(gpuData) {
24
22
  bandings[i * 4 + 1] = srcF[s + 13];
25
23
  bandings[i * 4 + 2] = srcF[s + 14];
26
24
  bandings[i * 4 + 3] = srcF[s + 15];
27
- // Unpack glyph location and band metadata from bit-packed fields
28
25
  const g0 = srcU[s + 6];
29
26
  const g1 = srcU[s + 7];
30
27
  glyphData[i * 4] = g0 & 0xFFFF;
@@ -36,7 +33,6 @@ function unpackSlugVertices(gpuData) {
36
33
  colors[i * 4 + 2] = srcF[s + 18];
37
34
  colors[i * 4 + 3] = srcF[s + 19];
38
35
  }
39
- // Per-glyph center: average of quad corner positions
40
36
  const glyphCenters = new Float32Array(vertCount * 3);
41
37
  const glyphIndices = new Float32Array(vertCount);
42
38
  for (let g = 0; g < gpuData.shapeCount; g++) {
@@ -61,23 +57,14 @@ function unpackSlugVertices(gpuData) {
61
57
 
62
58
  var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
63
59
 
64
- // Slug shader source re-exports
65
- // The .glsl/.wgsl files are the single source of truth, imported as strings
66
- // at build time via the glslPlugin in rollup.config.js
67
60
  // @ts-ignore - resolved by rollup glslPlugin
68
61
  const fragmentShaderGLSL300 = fragGLSL;
69
62
 
70
- // Slug GLSL adapter for Three.js, using RawShaderMaterial with the
71
- // reference GLSL shaders. Works with both WebGLRenderer and
72
- // WebGPURenderer (via GLSL-to-WGSL transpilation)
63
+ // Slug adapter using Three.js RawShaderMaterial (GLSL)
64
+ // Works with any Three.js renderer (WebGL or WebGPU via transpilation)
73
65
  //
74
- // Compared to the TSL adapter (slugTSL.ts):
75
- // - Works with any Three.js renderer (no node-material dependency)
76
- // - Uses native Uint32 band texture (no float conversion)
77
- // - Supports GLSL animation injection via animationDeclarations/animationBody
78
- // - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
79
- //
80
- // Requires peer dependency: three
66
+ // Supports GLSL animation injection and native Uint32 band textures
67
+ // No vertex dilation; may clip at sub-pixel edges under extreme zoom
81
68
  // @ts-ignore - three is a peer dependency
82
69
  // Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
83
70
  const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
@@ -128,13 +115,12 @@ function createSlugGLSLMesh(gpuData, options) {
128
115
  geo.setAttribute('glyphCenter', new THREE.Float32BufferAttribute(attrs.glyphCenters, 3));
129
116
  geo.setAttribute('glyphIndex', new THREE.Float32BufferAttribute(attrs.glyphIndices, 1));
130
117
  geo.setIndex(new THREE.BufferAttribute(gpuData.indices, 1));
131
- // Curve texture: RGBA32F
132
118
  const curveTex = new THREE.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE.RGBAFormat, THREE.FloatType);
133
119
  curveTex.minFilter = THREE.NearestFilter;
134
120
  curveTex.magFilter = THREE.NearestFilter;
135
121
  curveTex.generateMipmaps = false;
136
122
  curveTex.needsUpdate = true;
137
- // Band texture: native RGBA32UI (no float conversion needed)
123
+ // RGBA32UI (unlike TSL, GLSL supports integer textures natively)
138
124
  const bandTex = new THREE.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
139
125
  bandTex.format = THREE.RGBAIntegerFormat;
140
126
  bandTex.type = THREE.UnsignedIntType;
@@ -25,20 +25,11 @@ function _interopNamespaceDefault(e) {
25
25
 
26
26
  var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
27
27
 
28
- // Slug TSL adapter for Three.js WebGPURenderer (and WebGL via r170+)
28
+ // Slug adapter using Three.js TSL (node materials)
29
+ // Works on both WebGPU and WebGL backends (r170+)
29
30
  //
30
- // Creates a single Three.js Mesh with a NodeMaterial that implements
31
- // the Slug algorithm: per-fragment winding number evaluation via
32
- // band-accelerated ray-curve intersection
33
- //
34
- // Works on both WebGPU and WebGL backends via Three.js TSL
35
- //
36
- // Compared to the raw GLSL/WGSL standalone renderers, this adapter
37
- // trades some features for Three.js integration:
38
- // - No vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
39
- // - No adaptive supersampling (single-sample per fragment)
40
- //
41
- // Requires peer dependencies: three, three/tsl
31
+ // No vertex dilation or adaptive supersampling (unlike the raw
32
+ // GLSL/WGSL renderers); may clip at sub-pixel edges under extreme zoom
42
33
  // @ts-ignore - three is a peer dependency
43
34
  const LOG_BAND_TEX_W = 12;
44
35
  const BAND_TEX_W_MASK = (1 << LOG_BAND_TEX_W) - 1;
@@ -100,10 +91,6 @@ const calcBandLoc = tsl.Fn(([glyphX, glyphY, offset]) => {
100
91
  bx.assign(bx.bitAnd(BAND_TEX_W_MASK));
101
92
  return tsl.ivec2(bx, by);
102
93
  });
103
- // Create a Three.js Mesh from SlugGPUData using TSL node materials.
104
- // Returns a single transparent mesh suitable for any Three.js scene.
105
- // The Slug algorithm evaluates per-fragment coverage analytically,
106
- // so no stencil buffer or multi-pass rendering is required
107
94
  function createSlugTSLMesh(gpuData, color) {
108
95
  const attrs = index.unpackSlugVertices(gpuData);
109
96
  const geo = new THREE__namespace.BufferGeometry();
@@ -130,16 +117,11 @@ function createSlugTSLMesh(gpuData, color) {
130
117
  bandTex.magFilter = THREE__namespace.NearestFilter;
131
118
  bandTex.generateMipmaps = false;
132
119
  bandTex.needsUpdate = true;
133
- // Varyings: vertex attributes interpolated to fragment stage
134
120
  const vTexcoord = tsl.varying(tsl.attribute('slugTexcoord', 'vec2'), 'v_texcoord');
135
121
  const vBanding = tsl.varying(tsl.attribute('slugBanding', 'vec4'), 'v_banding');
136
122
  const vGlyph = tsl.varying(tsl.attribute('slugGlyph', 'vec4'), 'v_glyph');
137
123
  const vColor = tsl.varying(tsl.attribute('slugColor', 'vec4'), 'v_color');
138
- // Color uniform (allows dynamic color updates)
139
124
  const textColor = tsl.uniform(new THREE__namespace.Color(color?.r ?? 1, color?.g ?? 1, color?.b ?? 1));
140
- // Main per-fragment evaluation: SlugRenderSingle ported to TSL
141
- // Evaluates horizontal and vertical band loops to compute
142
- // analytic winding-number coverage
143
125
  const slugRenderSingle = tsl.Fn(([renderCoord, emsPerPixel, bandTransform, glyphData]) => {
144
126
  const pixelsPerEm = tsl.vec2(tsl.float(1).div(emsPerPixel.x), tsl.float(1).div(emsPerPixel.y));
145
127
  const glyphLocX = glyphData.x.toInt();
@@ -148,7 +130,6 @@ function createSlugTSLMesh(gpuData, color) {
148
130
  const bandMaxY = glyphData.w.toInt().bitAnd(0xFF);
149
131
  const bandIdxX = tsl.max(tsl.min(renderCoord.x.mul(bandTransform.x).add(bandTransform.z).toInt(), bandMaxX), tsl.int(0));
150
132
  const bandIdxY = tsl.max(tsl.min(renderCoord.y.mul(bandTransform.y).add(bandTransform.w).toInt(), bandMaxY), tsl.int(0));
151
- // Horizontal band loop
152
133
  const xcov = tsl.float(0).toVar();
153
134
  const xwgt = tsl.float(0).toVar();
154
135
  const hbandData = tsl.textureLoad(bandTex, tsl.ivec2(glyphLocX.add(bandIdxY), glyphLocY));
@@ -181,7 +162,6 @@ function createSlugTSLMesh(gpuData, color) {
181
162
  });
182
163
  hIdx.addAssign(1);
183
164
  });
184
- // Vertical band loop
185
165
  const ycov = tsl.float(0).toVar();
186
166
  const ywgt = tsl.float(0).toVar();
187
167
  const vbandOffset = bandMaxY.add(1).add(bandIdxX);
@@ -215,17 +195,14 @@ function createSlugTSLMesh(gpuData, color) {
215
195
  });
216
196
  vIdx.addAssign(1);
217
197
  });
218
- // CalcCoverage (nonzero winding rule)
219
198
  const coverage = tsl.max(tsl.abs(xcov.mul(xwgt).add(ycov.mul(ywgt))).div(tsl.max(xwgt.add(ywgt), tsl.float(1.0 / 65536.0))), tsl.min(tsl.abs(xcov), tsl.abs(ycov)));
220
199
  return tsl.clamp(coverage, 0, 1);
221
200
  });
222
- // Top-level fragment node
223
201
  const fragmentNode = tsl.Fn(() => {
224
202
  const emsPerPixel = tsl.fwidth(vTexcoord);
225
203
  const coverage = slugRenderSingle(vTexcoord, emsPerPixel, vBanding, vGlyph);
226
204
  return tsl.vec4(textColor.x, textColor.y, textColor.z, vColor.w.mul(coverage));
227
205
  })();
228
- // Material & mesh
229
206
  const material = new webgpu.MeshBasicNodeMaterial();
230
207
  material.fragmentNode = fragmentNode;
231
208
  material.transparent = true;