three-text 0.5.0 → 0.5.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.
@@ -1,1095 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var Text$1 = require('../index.cjs');
4
- var sharedCaches = require('../index.cjs');
5
- var DrawCallbacks = require('../index.cjs');
6
- var TextRangeQuery = require('../index.cjs');
7
- var THREE = require('three');
8
- var tsl = require('three/tsl');
9
-
10
- function _interopNamespaceDefault(e) {
11
- var n = Object.create(null);
12
- if (e) {
13
- Object.keys(e).forEach(function (k) {
14
- if (k !== 'default') {
15
- var d = Object.getOwnPropertyDescriptor(e, k);
16
- Object.defineProperty(n, k, d.get ? d : {
17
- enumerable: true,
18
- get: function () { return e[k]; }
19
- });
20
- }
21
- });
22
- }
23
- n.default = e;
24
- return Object.freeze(n);
25
- }
26
-
27
- var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
28
-
29
- // 2D Vector
30
- class Vec2 {
31
- constructor(x = 0, y = 0) {
32
- this.x = x;
33
- this.y = y;
34
- }
35
- set(x, y) {
36
- this.x = x;
37
- this.y = y;
38
- return this;
39
- }
40
- clone() {
41
- return new Vec2(this.x, this.y);
42
- }
43
- copy(v) {
44
- this.x = v.x;
45
- this.y = v.y;
46
- return this;
47
- }
48
- add(v) {
49
- this.x += v.x;
50
- this.y += v.y;
51
- return this;
52
- }
53
- sub(v) {
54
- this.x -= v.x;
55
- this.y -= v.y;
56
- return this;
57
- }
58
- multiply(scalar) {
59
- this.x *= scalar;
60
- this.y *= scalar;
61
- return this;
62
- }
63
- divide(scalar) {
64
- this.x /= scalar;
65
- this.y /= scalar;
66
- return this;
67
- }
68
- length() {
69
- return Math.sqrt(this.x * this.x + this.y * this.y);
70
- }
71
- lengthSq() {
72
- return this.x * this.x + this.y * this.y;
73
- }
74
- normalize() {
75
- const len = this.length();
76
- if (len > 0) {
77
- this.divide(len);
78
- }
79
- return this;
80
- }
81
- dot(v) {
82
- return this.x * v.x + this.y * v.y;
83
- }
84
- distanceTo(v) {
85
- const dx = this.x - v.x;
86
- const dy = this.y - v.y;
87
- return Math.sqrt(dx * dx + dy * dy);
88
- }
89
- distanceToSquared(v) {
90
- const dx = this.x - v.x;
91
- const dy = this.y - v.y;
92
- return dx * dx + dy * dy;
93
- }
94
- equals(v) {
95
- return this.x === v.x && this.y === v.y;
96
- }
97
- angle() {
98
- return Math.atan2(this.y, this.x);
99
- }
100
- }
101
-
102
- class GlyphOutlineCollector {
103
- constructor() {
104
- this.currentGlyphId = 0;
105
- this.currentTextIndex = 0;
106
- this.inGlyph = false;
107
- this.currentSegments = [];
108
- this.currentPoint = null;
109
- this.contourStartPoint = null;
110
- this.contourId = 0;
111
- this.currentGlyphBounds = {
112
- min: new Vec2(Infinity, Infinity),
113
- max: new Vec2(-Infinity, -Infinity)
114
- };
115
- this.collectedGlyphs = [];
116
- this.currentPosition = new Vec2(0, 0);
117
- }
118
- setPosition(x, y) {
119
- this.currentPosition.set(x, y);
120
- }
121
- updatePosition(dx, dy) {
122
- this.currentPosition.x += dx;
123
- this.currentPosition.y += dy;
124
- }
125
- beginGlyph(glyphId, textIndex) {
126
- if (this.currentSegments.length > 0) {
127
- this.finishGlyph();
128
- }
129
- this.currentGlyphId = glyphId;
130
- this.currentTextIndex = textIndex;
131
- this.inGlyph = true;
132
- this.currentSegments = [];
133
- this.currentPoint = null;
134
- this.contourStartPoint = null;
135
- this.contourId = 0;
136
- this.currentGlyphBounds.min.set(Infinity, Infinity);
137
- this.currentGlyphBounds.max.set(-Infinity, -Infinity);
138
- }
139
- finishGlyph() {
140
- if (this.currentSegments.length > 0) {
141
- this.collectedGlyphs.push({
142
- glyphId: this.currentGlyphId,
143
- textIndex: this.currentTextIndex,
144
- segments: this.currentSegments,
145
- bounds: {
146
- min: {
147
- x: this.currentGlyphBounds.min.x,
148
- y: this.currentGlyphBounds.min.y
149
- },
150
- max: {
151
- x: this.currentGlyphBounds.max.x,
152
- y: this.currentGlyphBounds.max.y
153
- }
154
- }
155
- });
156
- }
157
- else {
158
- this.collectedGlyphs.push({
159
- glyphId: this.currentGlyphId,
160
- textIndex: this.currentTextIndex,
161
- segments: [],
162
- bounds: {
163
- min: { x: 0, y: 0 },
164
- max: { x: 0, y: 0 }
165
- }
166
- });
167
- }
168
- this.currentSegments = [];
169
- this.currentPoint = null;
170
- this.contourStartPoint = null;
171
- this.inGlyph = false;
172
- this.currentGlyphId = 0;
173
- this.currentTextIndex = 0;
174
- }
175
- onMoveTo(x, y) {
176
- const p = new Vec2(x, y);
177
- this.updateBounds(p);
178
- this.currentPoint = p;
179
- this.contourStartPoint = p;
180
- this.contourId++;
181
- }
182
- onLineTo(x, y) {
183
- if (!this.currentPoint)
184
- return;
185
- const p1 = new Vec2(x, y);
186
- const p0 = this.currentPoint;
187
- this.updateBounds(p1);
188
- this.currentSegments.push({
189
- type: 0,
190
- contourId: this.contourId,
191
- p0,
192
- p1
193
- });
194
- this.currentPoint = p1;
195
- }
196
- onQuadTo(cx, cy, x, y) {
197
- if (!this.currentPoint)
198
- return;
199
- const p0 = this.currentPoint;
200
- const p1 = new Vec2(cx, cy);
201
- const p2 = new Vec2(x, y);
202
- this.updateBounds(p1);
203
- this.updateBounds(p2);
204
- this.currentSegments.push({
205
- type: 1,
206
- contourId: this.contourId,
207
- p0,
208
- p1,
209
- p2
210
- });
211
- this.currentPoint = p2;
212
- }
213
- onCubicTo(c1x, c1y, c2x, c2y, x, y) {
214
- if (!this.currentPoint)
215
- return;
216
- const p0 = this.currentPoint;
217
- const p1 = new Vec2(c1x, c1y);
218
- const p2 = new Vec2(c2x, c2y);
219
- const p3 = new Vec2(x, y);
220
- this.updateBounds(p1);
221
- this.updateBounds(p2);
222
- this.updateBounds(p3);
223
- this.currentSegments.push({
224
- type: 2,
225
- contourId: this.contourId,
226
- p0,
227
- p1,
228
- p2,
229
- p3
230
- });
231
- this.currentPoint = p3;
232
- }
233
- onClosePath() {
234
- if (!this.currentPoint || !this.contourStartPoint)
235
- return;
236
- const p0 = this.currentPoint;
237
- const p1 = this.contourStartPoint;
238
- if (p0.x !== p1.x || p0.y !== p1.y) {
239
- this.currentSegments.push({
240
- type: 0,
241
- contourId: this.contourId,
242
- p0,
243
- p1
244
- });
245
- }
246
- this.currentPoint = p1;
247
- this.contourStartPoint = null;
248
- }
249
- getCollectedGlyphs() {
250
- if (this.inGlyph) {
251
- this.finishGlyph();
252
- }
253
- return this.collectedGlyphs;
254
- }
255
- reset() {
256
- this.collectedGlyphs = [];
257
- this.currentGlyphId = 0;
258
- this.currentTextIndex = 0;
259
- this.currentSegments = [];
260
- this.currentPoint = null;
261
- this.contourStartPoint = null;
262
- this.contourId = 0;
263
- this.currentPosition.set(0, 0);
264
- this.currentGlyphBounds.min.set(Infinity, Infinity);
265
- this.currentGlyphBounds.max.set(-Infinity, -Infinity);
266
- }
267
- updateBounds(p) {
268
- this.currentGlyphBounds.min.x = Math.min(this.currentGlyphBounds.min.x, p.x);
269
- this.currentGlyphBounds.min.y = Math.min(this.currentGlyphBounds.min.y, p.y);
270
- this.currentGlyphBounds.max.x = Math.max(this.currentGlyphBounds.max.x, p.x);
271
- this.currentGlyphBounds.max.y = Math.max(this.currentGlyphBounds.max.y, p.y);
272
- }
273
- }
274
-
275
- function ceilDiv(a, b) {
276
- return Math.floor((a + b - 1) / b);
277
- }
278
- function createPackedTexture(texelCount, width) {
279
- const height = Math.max(1, ceilDiv(texelCount, width));
280
- return { width, height, data: new Float32Array(width * height * 4) };
281
- }
282
- // All cubic-to-quadratic work uses scalar x/y to avoid per-recursion
283
- // Vec2 allocations. Only the final emitted QuadSegs create Vec2s
284
- function cubicToQuadraticsAdaptive(contourId, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, tol2, depth, out) {
285
- // Approximate quad control points
286
- const c0x = p0x + 0.75 * (p1x - p0x);
287
- const c0y = p0y + 0.75 * (p1y - p0y);
288
- const c1x = p3x + 0.75 * (p2x - p3x);
289
- const c1y = p3y + 0.75 * (p2y - p3y);
290
- const mx = (c0x + c1x) * 0.5;
291
- const my = (c0y + c1y) * 0.5;
292
- // Error: sample cubic vs piecewise quads at t=0.25, 0.5, 0.75
293
- let maxErr2 = 0;
294
- for (let si = 0; si < 3; si++) {
295
- const t = 0.25 + si * 0.25;
296
- const u = 1 - t;
297
- const uu = u * u;
298
- const uuu = uu * u;
299
- const tt = t * t;
300
- const ttt = tt * t;
301
- const cx = uuu * p0x + 3 * uu * t * p1x + 3 * u * tt * p2x + ttt * p3x;
302
- const cy = uuu * p0y + 3 * uu * t * p1y + 3 * u * tt * p2y + ttt * p3y;
303
- let qx, qy;
304
- if (t < 0.5) {
305
- const qt = t * 2;
306
- const qu = 1 - qt;
307
- qx = qu * qu * p0x + 2 * qu * qt * c0x + qt * qt * mx;
308
- qy = qu * qu * p0y + 2 * qu * qt * c0y + qt * qt * my;
309
- }
310
- else {
311
- const qt = (t - 0.5) * 2;
312
- const qu = 1 - qt;
313
- qx = qu * qu * mx + 2 * qu * qt * c1x + qt * qt * p3x;
314
- qy = qu * qu * my + 2 * qu * qt * c1y + qt * qt * p3y;
315
- }
316
- const dx = cx - qx, dy = cy - qy;
317
- const e2 = dx * dx + dy * dy;
318
- if (e2 > maxErr2)
319
- maxErr2 = e2;
320
- }
321
- if (maxErr2 <= tol2 || depth <= 0) {
322
- out.push({ type: 1, contourId, p0: new Vec2(p0x, p0y), p1: new Vec2(c0x, c0y), p2: new Vec2(mx, my) }, { type: 1, contourId, p0: new Vec2(mx, my), p1: new Vec2(c1x, c1y), p2: new Vec2(p3x, p3y) });
323
- return;
324
- }
325
- // De Casteljau split at t=0.5, pure scalar
326
- const p01x = (p0x + p1x) * 0.5;
327
- const p01y = (p0y + p1y) * 0.5;
328
- const p12x = (p1x + p2x) * 0.5;
329
- const p12y = (p1y + p2y) * 0.5;
330
- const p23x = (p2x + p3x) * 0.5;
331
- const p23y = (p2y + p3y) * 0.5;
332
- const p012x = (p01x + p12x) * 0.5;
333
- const p012y = (p01y + p12y) * 0.5;
334
- const p123x = (p12x + p23x) * 0.5;
335
- const p123y = (p12y + p23y) * 0.5;
336
- const p0123x = (p012x + p123x) * 0.5;
337
- const p0123y = (p012y + p123y) * 0.5;
338
- cubicToQuadraticsAdaptive(contourId, p0x, p0y, p01x, p01y, p012x, p012y, p0123x, p0123y, tol2, depth - 1, out);
339
- cubicToQuadraticsAdaptive(contourId, p0123x, p0123y, p123x, p123y, p23x, p23y, p3x, p3y, tol2, depth - 1, out);
340
- }
341
- function toQuadratics(segments) {
342
- const out = [];
343
- // tol=0.25 font units; pre-squared for the scalar subdivision
344
- const tol2 = 0.25 * 0.25;
345
- const maxDepth = 4;
346
- for (let i = 0; i < segments.length; i++) {
347
- const s = segments[i];
348
- const type = s.type;
349
- if (type === 0) {
350
- const p0 = s.p0;
351
- const p2 = s.p1;
352
- out.push({
353
- type: 1,
354
- contourId: s.contourId,
355
- p0,
356
- p1: new Vec2((p0.x + p2.x) * 0.5, (p0.y + p2.y) * 0.5),
357
- p2
358
- });
359
- continue;
360
- }
361
- if (type === 1) {
362
- out.push({
363
- type: 1,
364
- contourId: s.contourId,
365
- p0: s.p0,
366
- p1: s.p1,
367
- p2: s.p2
368
- });
369
- continue;
370
- }
371
- cubicToQuadraticsAdaptive(s.contourId, s.p0.x, s.p0.y, s.p1.x, s.p1.y, s.p2.x, s.p2.y, s.p3.x, s.p3.y, tol2, maxDepth, out);
372
- }
373
- return out;
374
- }
375
- // Packs glyph outlines into GPU-friendly textures and instance
376
- // attributes for vector rendering without tessellation
377
- class GlyphVectorGeometryBuilder {
378
- constructor(loadedFont, cache = sharedCaches.globalOutlineCache) {
379
- this.fontId = 'default';
380
- this.cacheKeyPrefix = 'default';
381
- this.emptyGlyphs = new Set();
382
- this.loadedFont = loadedFont;
383
- this.outlineCache = cache;
384
- this.collector = new GlyphOutlineCollector();
385
- this.drawCallbacks = DrawCallbacks.getSharedDrawCallbackHandler(this.loadedFont);
386
- this.drawCallbacks.createDrawFuncs(this.loadedFont, this.collector);
387
- }
388
- setFontId(fontId) {
389
- this.fontId = fontId;
390
- this.cacheKeyPrefix = `${this.fontId}__outline`;
391
- }
392
- clearCache() {
393
- this.outlineCache.clear();
394
- this.emptyGlyphs.clear();
395
- }
396
- getCacheStats() {
397
- return this.outlineCache.getStats();
398
- }
399
- collectUniqueGlyphIds(clustersByLine) {
400
- const ids = [];
401
- const seen = new Set();
402
- for (const line of clustersByLine) {
403
- for (const cluster of line) {
404
- for (const g of cluster.glyphs) {
405
- if (!seen.has(g.g)) {
406
- seen.add(g.g);
407
- ids.push(g.g);
408
- }
409
- }
410
- }
411
- }
412
- return ids;
413
- }
414
- computePlaneBounds(glyphInfos) {
415
- if (glyphInfos.length === 0) {
416
- return { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } };
417
- }
418
- let minX = Infinity, minY = Infinity;
419
- let maxX = -Infinity, maxY = -Infinity;
420
- for (const g of glyphInfos) {
421
- if (g.bounds.min.x < minX)
422
- minX = g.bounds.min.x;
423
- if (g.bounds.min.y < minY)
424
- minY = g.bounds.min.y;
425
- if (g.bounds.max.x > maxX)
426
- maxX = g.bounds.max.x;
427
- if (g.bounds.max.y > maxY)
428
- maxY = g.bounds.max.y;
429
- }
430
- return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
431
- }
432
- buildForLoopBlinn(clustersByLine, scale) {
433
- const uniqueGlyphIds = this.collectUniqueGlyphIds(clustersByLine);
434
- const scaledSegsByGlyph = new Map();
435
- const glyphBoundsByGlyph = new Map();
436
- for (const glyphId of uniqueGlyphIds) {
437
- const outline = this.getOutlineForGlyph(glyphId);
438
- const quadSegs = toQuadratics(outline.segments);
439
- const scaledSegs = [];
440
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
441
- for (const seg of quadSegs) {
442
- const p0x = seg.p0.x * scale, p0y = seg.p0.y * scale;
443
- const p1x = seg.p1.x * scale, p1y = seg.p1.y * scale;
444
- const p2x = seg.p2.x * scale, p2y = seg.p2.y * scale;
445
- scaledSegs.push({ p0x, p0y, p1x, p1y, p2x, p2y });
446
- if (p0x < minX)
447
- minX = p0x;
448
- if (p0y < minY)
449
- minY = p0y;
450
- if (p0x > maxX)
451
- maxX = p0x;
452
- if (p0y > maxY)
453
- maxY = p0y;
454
- if (p1x < minX)
455
- minX = p1x;
456
- if (p1y < minY)
457
- minY = p1y;
458
- if (p1x > maxX)
459
- maxX = p1x;
460
- if (p1y > maxY)
461
- maxY = p1y;
462
- if (p2x < minX)
463
- minX = p2x;
464
- if (p2y < minY)
465
- minY = p2y;
466
- if (p2x > maxX)
467
- maxX = p2x;
468
- if (p2y > maxY)
469
- maxY = p2y;
470
- }
471
- scaledSegsByGlyph.set(glyphId, scaledSegs);
472
- glyphBoundsByGlyph.set(glyphId, scaledSegs.length > 0
473
- ? { minX, minY, maxX, maxY }
474
- : { minX: 0, minY: 0, maxX: 0, maxY: 0 });
475
- }
476
- const loopBlinnGlyphs = [];
477
- const glyphInfos = [];
478
- for (const line of clustersByLine) {
479
- for (const cluster of line) {
480
- for (const g of cluster.glyphs) {
481
- const segments = scaledSegsByGlyph.get(g.g);
482
- if (!segments || segments.length === 0)
483
- continue;
484
- const bounds = glyphBoundsByGlyph.get(g.g);
485
- const px = (cluster.position.x + (g.x ?? 0)) * scale;
486
- const py = (cluster.position.y + (g.y ?? 0)) * scale;
487
- loopBlinnGlyphs.push({
488
- offsetX: px,
489
- offsetY: py,
490
- segments,
491
- bounds,
492
- lineIndex: g.lineIndex,
493
- baselineY: (cluster.position.y + (g.y ?? 0)) * scale
494
- });
495
- glyphInfos.push({
496
- textIndex: g.absoluteTextIndex,
497
- lineIndex: g.lineIndex,
498
- vertexStart: 0,
499
- vertexCount: 0,
500
- segmentStart: 0,
501
- segmentCount: segments.length,
502
- bounds: {
503
- min: { x: px + bounds.minX, y: py + bounds.minY, z: 0 },
504
- max: { x: px + bounds.maxX, y: py + bounds.maxY, z: 0 }
505
- }
506
- });
507
- }
508
- }
509
- }
510
- const planeBounds = this.computePlaneBounds(glyphInfos);
511
- return {
512
- loopBlinnInput: { glyphs: loopBlinnGlyphs, planeBounds },
513
- glyphs: glyphInfos
514
- };
515
- }
516
- getOutlineForGlyph(glyphId) {
517
- if (this.emptyGlyphs.has(glyphId)) {
518
- return {
519
- glyphId,
520
- textIndex: 0,
521
- segments: [],
522
- bounds: { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }
523
- };
524
- }
525
- const key = `${this.cacheKeyPrefix}_${glyphId}`;
526
- const cached = this.outlineCache.get(key);
527
- if (cached)
528
- return cached;
529
- this.drawCallbacks.setCollector(this.collector);
530
- this.collector.reset();
531
- this.collector.beginGlyph(glyphId, 0);
532
- this.loadedFont.module.exports.hb_font_draw_glyph(this.loadedFont.font.ptr, glyphId, this.drawCallbacks.getDrawFuncsPtr(), 0);
533
- this.collector.finishGlyph();
534
- const collected = this.collector.getCollectedGlyphs()[0];
535
- const outline = collected ?? {
536
- glyphId,
537
- textIndex: 0,
538
- segments: [],
539
- bounds: { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }
540
- };
541
- if (outline.segments.length === 0) {
542
- this.emptyGlyphs.add(glyphId);
543
- }
544
- this.outlineCache.set(key, outline);
545
- return outline;
546
- }
547
- writeSegmentToTexture(tex, texelIndex, a, b, c, d) {
548
- const i = texelIndex * 4;
549
- tex.data[i] = a;
550
- tex.data[i + 1] = b;
551
- tex.data[i + 2] = c;
552
- tex.data[i + 3] = d;
553
- }
554
- segmentAABB(seg) {
555
- let minX = seg.p0.x;
556
- let minY = seg.p0.y;
557
- let maxX = seg.p0.x;
558
- let maxY = seg.p0.y;
559
- const p1 = seg.p1;
560
- if (p1.x < minX)
561
- minX = p1.x;
562
- if (p1.y < minY)
563
- minY = p1.y;
564
- if (p1.x > maxX)
565
- maxX = p1.x;
566
- if (p1.y > maxY)
567
- maxY = p1.y;
568
- const p2 = seg.p2;
569
- if (p2) {
570
- if (p2.x < minX)
571
- minX = p2.x;
572
- if (p2.y < minY)
573
- minY = p2.y;
574
- if (p2.x > maxX)
575
- maxX = p2.x;
576
- if (p2.y > maxY)
577
- maxY = p2.y;
578
- }
579
- const p3 = seg.p3;
580
- if (p3) {
581
- if (p3.x < minX)
582
- minX = p3.x;
583
- if (p3.y < minY)
584
- minY = p3.y;
585
- if (p3.x > maxX)
586
- maxX = p3.x;
587
- if (p3.y > maxY)
588
- maxY = p3.y;
589
- }
590
- return { minX, minY, maxX, maxY };
591
- }
592
- buildVectorGeometry(clustersByLine, scale, segmentTextureWidth = 1024, bandCount = 0, tileCountX = 0, tileCountY = 0) {
593
- // Base quad: positions in [-1,1] with UV in [0,1]
594
- const quadVertices = new Float32Array([
595
- -1, -1, 0, 0,
596
- -1, 1, 0, 1,
597
- 1, 1, 1, 1,
598
- 1, -1, 1, 0
599
- ]);
600
- const quadIndices = new Uint16Array([0, 1, 2, 0, 2, 3]);
601
- const uniqueGlyphIds = [];
602
- const seen = new Set();
603
- for (const line of clustersByLine) {
604
- for (const cluster of line) {
605
- for (const g of cluster.glyphs) {
606
- if (!seen.has(g.g)) {
607
- seen.add(g.g);
608
- uniqueGlyphIds.push(g.g);
609
- }
610
- }
611
- }
612
- }
613
- const outlinesByGlyph = new Map();
614
- const rangesByGlyph = new Map();
615
- const glyphDataIndexByGlyphId = new Map();
616
- const quadSegsByGlyph = new Map();
617
- let totalSegments = 0;
618
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
619
- const glyphId = uniqueGlyphIds[gi];
620
- const outline = this.getOutlineForGlyph(glyphId);
621
- outlinesByGlyph.set(glyphId, outline);
622
- glyphDataIndexByGlyphId.set(glyphId, gi);
623
- const quadSegs = toQuadratics(outline.segments);
624
- quadSegsByGlyph.set(glyphId, quadSegs);
625
- totalSegments += quadSegs.length;
626
- }
627
- // Pack segments into RGBA32F texels: 3 texels per segment (12 floats)
628
- const segmentTexelsPerSegment = 3;
629
- const segmentTexelCount = totalSegments * segmentTexelsPerSegment;
630
- const segmentsTex = createPackedTexture(segmentTexelCount, segmentTextureWidth);
631
- // Segment bounds: 1 texel per segment (minX,minY,maxX,maxY), conservative hull AABB
632
- const boundsTex = createPackedTexture(totalSegments, segmentTextureWidth);
633
- // Keep bounds in CPU arrays for banding/tiling calculations (scaled, local coords)
634
- const segMinX = new Float32Array(totalSegments);
635
- const segMaxX = new Float32Array(totalSegments);
636
- const segMinY = new Float32Array(totalSegments);
637
- const segMaxY = new Float32Array(totalSegments);
638
- let segmentCursor = 0;
639
- for (const glyphId of uniqueGlyphIds) {
640
- outlinesByGlyph.get(glyphId);
641
- const start = segmentCursor;
642
- const quadSegs = quadSegsByGlyph.get(glyphId);
643
- const count = quadSegs.length;
644
- rangesByGlyph.set(glyphId, { start, count });
645
- for (let i = 0; i < count; i++) {
646
- const seg = quadSegs[i];
647
- // texel base for this segment
648
- const tBase = (segmentCursor + i) * segmentTexelsPerSegment;
649
- // Scale to match the mesh pipeline's output units
650
- const p0x = seg.p0.x * scale;
651
- const p0y = seg.p0.y * scale;
652
- const p1x = seg.p1.x * scale;
653
- const p1y = seg.p1.y * scale;
654
- const p2x = seg.p2.x * scale;
655
- const p2y = seg.p2.y * scale;
656
- const p3x = 0;
657
- const p3y = 0;
658
- // Texel 0: p0.xy, p1.xy
659
- this.writeSegmentToTexture(segmentsTex, tBase + 0, p0x, p0y, p1x, p1y);
660
- // Texel 1: p2.xy, p3.xy
661
- this.writeSegmentToTexture(segmentsTex, tBase + 1, p2x, p2y, p3x, p3y);
662
- // Texel 2: type, contourId, reserved, reserved
663
- this.writeSegmentToTexture(segmentsTex, tBase + 2, 1, // quadratic-only
664
- seg.contourId, 0, 0);
665
- // Bounds texel (conservative AABB from control points, scaled)
666
- const aabb = this.segmentAABB(seg);
667
- const sMinX = aabb.minX * scale;
668
- const sMaxX = aabb.maxX * scale;
669
- const sMinY = aabb.minY * scale;
670
- const sMaxY = aabb.maxY * scale;
671
- this.writeSegmentToTexture(boundsTex, segmentCursor + i, sMinX, sMinY, sMaxX, sMaxY);
672
- segMinX[segmentCursor + i] = sMinX;
673
- segMaxX[segmentCursor + i] = sMaxX;
674
- segMinY[segmentCursor + i] = sMinY;
675
- segMaxY[segmentCursor + i] = sMaxY;
676
- }
677
- segmentCursor += count;
678
- }
679
- // Per-glyph bounds from the packed segments, not the original outline,
680
- // since cubic->quadratic approximation can slightly overshoot
681
- const glyphMinX = new Float32Array(uniqueGlyphIds.length);
682
- const glyphMinY = new Float32Array(uniqueGlyphIds.length);
683
- const glyphMaxX = new Float32Array(uniqueGlyphIds.length);
684
- const glyphMaxY = new Float32Array(uniqueGlyphIds.length);
685
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
686
- const glyphId = uniqueGlyphIds[gi];
687
- const range = rangesByGlyph.get(glyphId);
688
- if (range.count === 0) {
689
- glyphMinX[gi] = 0;
690
- glyphMinY[gi] = 0;
691
- glyphMaxX[gi] = 0;
692
- glyphMaxY[gi] = 0;
693
- continue;
694
- }
695
- let minX = Infinity;
696
- let minY = Infinity;
697
- let maxX = -Infinity;
698
- let maxY = -Infinity;
699
- for (let si = 0; si < range.count; si++) {
700
- const segIndex = range.start + si;
701
- const x0 = segMinX[segIndex];
702
- const x1 = segMaxX[segIndex];
703
- const y0 = segMinY[segIndex];
704
- const y1 = segMaxY[segIndex];
705
- if (x0 < minX)
706
- minX = x0;
707
- if (y0 < minY)
708
- minY = y0;
709
- if (x1 > maxX)
710
- maxX = x1;
711
- if (y1 > maxY)
712
- maxY = y1;
713
- }
714
- glyphMinX[gi] = minX;
715
- glyphMinY[gi] = minY;
716
- glyphMaxX[gi] = maxX;
717
- glyphMaxY[gi] = maxY;
718
- }
719
- // Y-bands: partition each glyph into horizontal bands so the shader
720
- // only tests segments overlapping the current fragment's Y range
721
- const useBands = bandCount > 0;
722
- const bandRangesTex = useBands
723
- ? createPackedTexture(uniqueGlyphIds.length * bandCount, segmentTextureWidth)
724
- : undefined;
725
- let bandIndexRefsTotal = 0;
726
- let bandListsByGlyph = [];
727
- if (useBands) {
728
- bandListsByGlyph = new Array(uniqueGlyphIds.length);
729
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
730
- const glyphId = uniqueGlyphIds[gi];
731
- const range = rangesByGlyph.get(glyphId);
732
- const gMinY = glyphMinY[gi];
733
- const gMaxY = glyphMaxY[gi];
734
- const height = gMaxY - gMinY;
735
- const invBandH = height > 1e-8 ? bandCount / height : 0;
736
- const bands = new Array(bandCount);
737
- for (let bi = 0; bi < bandCount; bi++)
738
- bands[bi] = [];
739
- for (let si = 0; si < range.count; si++) {
740
- const segIndex = range.start + si;
741
- const s0 = segMinY[segIndex];
742
- const s1 = segMaxY[segIndex];
743
- let b0 = 0;
744
- let b1 = bandCount - 1;
745
- if (invBandH > 0) {
746
- b0 = Math.floor((s0 - gMinY) * invBandH);
747
- b1 = Math.floor((s1 - gMinY) * invBandH);
748
- if (b0 < 0)
749
- b0 = 0;
750
- if (b1 < 0)
751
- b1 = 0;
752
- if (b0 >= bandCount)
753
- b0 = bandCount - 1;
754
- if (b1 >= bandCount)
755
- b1 = bandCount - 1;
756
- }
757
- if (b1 < b0) {
758
- const tmp = b0;
759
- b0 = b1;
760
- b1 = tmp;
761
- }
762
- for (let bi = b0; bi <= b1; bi++) {
763
- bands[bi].push(segIndex);
764
- }
765
- }
766
- // Sort by maxX descending so the shader can early-exit leftward
767
- for (let bi = 0; bi < bandCount; bi++) {
768
- bands[bi].sort((a, b) => segMaxX[b] - segMaxX[a]);
769
- }
770
- bandListsByGlyph[gi] = bands;
771
- for (let bi = 0; bi < bandCount; bi++) {
772
- bandIndexRefsTotal += bands[bi].length;
773
- }
774
- }
775
- }
776
- const bandIndicesTex = useBands
777
- ? createPackedTexture(bandIndexRefsTotal, segmentTextureWidth)
778
- : undefined;
779
- if (useBands && bandRangesTex && bandIndicesTex) {
780
- let bandIndexCursor = 0;
781
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
782
- const bands = bandListsByGlyph[gi];
783
- for (let bi = 0; bi < bandCount; bi++) {
784
- const list = bands[bi];
785
- const start = bandIndexCursor;
786
- const count = list.length;
787
- // bandRanges texel: (start, count, 0, 0)
788
- this.writeSegmentToTexture(bandRangesTex, gi * bandCount + bi, start, count, 0, 0);
789
- for (let k = 0; k < count; k++) {
790
- const segIndex = list[k];
791
- // bandIndices texel.x = segIndex
792
- this.writeSegmentToTexture(bandIndicesTex, bandIndexCursor++, segIndex, 0, 0, 0);
793
- }
794
- }
795
- }
796
- }
797
- // X-bands: same idea along the horizontal axis for vertical ray tests
798
- const xBandCount = bandCount; // Use same count for both axes
799
- const xBandRangesTex = useBands
800
- ? createPackedTexture(uniqueGlyphIds.length * xBandCount, segmentTextureWidth)
801
- : undefined;
802
- let xBandIndexRefsTotal = 0;
803
- let xBandListsByGlyph = [];
804
- if (useBands) {
805
- xBandListsByGlyph = new Array(uniqueGlyphIds.length);
806
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
807
- const glyphId = uniqueGlyphIds[gi];
808
- const range = rangesByGlyph.get(glyphId);
809
- const gMinX = glyphMinX[gi];
810
- const gMaxX = glyphMaxX[gi];
811
- const width = gMaxX - gMinX;
812
- const invBandW = width > 1e-8 ? xBandCount / width : 0;
813
- const bands = new Array(xBandCount);
814
- for (let bi = 0; bi < xBandCount; bi++)
815
- bands[bi] = [];
816
- for (let si = 0; si < range.count; si++) {
817
- const segIndex = range.start + si;
818
- const s0 = segMinX[segIndex];
819
- const s1 = segMaxX[segIndex];
820
- let b0 = 0;
821
- let b1 = xBandCount - 1;
822
- if (invBandW > 0) {
823
- b0 = Math.floor((s0 - gMinX) * invBandW);
824
- b1 = Math.floor((s1 - gMinX) * invBandW);
825
- if (b0 < 0)
826
- b0 = 0;
827
- if (b1 < 0)
828
- b1 = 0;
829
- if (b0 >= xBandCount)
830
- b0 = xBandCount - 1;
831
- if (b1 >= xBandCount)
832
- b1 = xBandCount - 1;
833
- }
834
- if (b1 < b0) {
835
- const tmp = b0;
836
- b0 = b1;
837
- b1 = tmp;
838
- }
839
- for (let bi = b0; bi <= b1; bi++) {
840
- bands[bi].push(segIndex);
841
- }
842
- }
843
- // Sort by maxY descending so the shader can early-exit downward
844
- for (let bi = 0; bi < xBandCount; bi++) {
845
- bands[bi].sort((a, b) => segMaxY[b] - segMaxY[a]);
846
- }
847
- xBandListsByGlyph[gi] = bands;
848
- for (let bi = 0; bi < xBandCount; bi++) {
849
- xBandIndexRefsTotal += bands[bi].length;
850
- }
851
- }
852
- }
853
- const xBandIndicesTex = useBands
854
- ? createPackedTexture(xBandIndexRefsTotal, segmentTextureWidth)
855
- : undefined;
856
- if (useBands && xBandRangesTex && xBandIndicesTex) {
857
- let xBandIndexCursor = 0;
858
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
859
- const bands = xBandListsByGlyph[gi];
860
- for (let bi = 0; bi < xBandCount; bi++) {
861
- const list = bands[bi];
862
- const start = xBandIndexCursor;
863
- const count = list.length;
864
- // xBandRanges texel: (start, count, 0, 0)
865
- this.writeSegmentToTexture(xBandRangesTex, gi * xBandCount + bi, start, count, 0, 0);
866
- for (let k = 0; k < count; k++) {
867
- const segIndex = list[k];
868
- // xBandIndices texel.x = segIndex
869
- this.writeSegmentToTexture(xBandIndicesTex, xBandIndexCursor++, segIndex, 0, 0, 0);
870
- }
871
- }
872
- }
873
- }
874
- // Build 2D tile grid (optional acceleration, Pathfinder-style)
875
- const useTiles = tileCountX > 0 && tileCountY > 0;
876
- const tileCount = useTiles ? tileCountX * tileCountY : 0;
877
- const tileRangesTex = useTiles
878
- ? createPackedTexture(uniqueGlyphIds.length * tileCount, segmentTextureWidth)
879
- : undefined;
880
- let tileIndexRefsTotal = 0;
881
- let tileListsByGlyph = [];
882
- if (useTiles) {
883
- tileListsByGlyph = new Array(uniqueGlyphIds.length);
884
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
885
- const glyphId = uniqueGlyphIds[gi];
886
- const range = rangesByGlyph.get(glyphId);
887
- const gMinX = glyphMinX[gi];
888
- const gMaxX = glyphMaxX[gi];
889
- const gMinY = glyphMinY[gi];
890
- const gMaxY = glyphMaxY[gi];
891
- const width = gMaxX - gMinX;
892
- const height = gMaxY - gMinY;
893
- const invTileW = width > 1e-8 ? tileCountX / width : 0;
894
- const invTileH = height > 1e-8 ? tileCountY / height : 0;
895
- const tiles = new Array(tileCount);
896
- for (let ti = 0; ti < tileCount; ti++)
897
- tiles[ti] = [];
898
- for (let si = 0; si < range.count; si++) {
899
- const segIndex = range.start + si;
900
- const x0 = segMinX[segIndex];
901
- const x1 = segMaxX[segIndex];
902
- const y0 = segMinY[segIndex];
903
- const y1 = segMaxY[segIndex];
904
- let tx0 = 0;
905
- let tx1 = tileCountX - 1;
906
- let ty0 = 0;
907
- let ty1 = tileCountY - 1;
908
- if (invTileW > 0) {
909
- tx0 = Math.floor((x0 - gMinX) * invTileW);
910
- tx1 = Math.floor((x1 - gMinX) * invTileW);
911
- if (tx0 < 0)
912
- tx0 = 0;
913
- if (tx1 < 0)
914
- tx1 = 0;
915
- if (tx0 >= tileCountX)
916
- tx0 = tileCountX - 1;
917
- if (tx1 >= tileCountX)
918
- tx1 = tileCountX - 1;
919
- }
920
- if (invTileH > 0) {
921
- ty0 = Math.floor((y0 - gMinY) * invTileH);
922
- ty1 = Math.floor((y1 - gMinY) * invTileH);
923
- if (ty0 < 0)
924
- ty0 = 0;
925
- if (ty1 < 0)
926
- ty1 = 0;
927
- if (ty0 >= tileCountY)
928
- ty0 = tileCountY - 1;
929
- if (ty1 >= tileCountY)
930
- ty1 = tileCountY - 1;
931
- }
932
- if (tx1 < tx0) {
933
- const t = tx0;
934
- tx0 = tx1;
935
- tx1 = t;
936
- }
937
- if (ty1 < ty0) {
938
- const t = ty0;
939
- ty0 = ty1;
940
- ty1 = t;
941
- }
942
- for (let ty = ty0; ty <= ty1; ty++) {
943
- const rowBase = ty * tileCountX;
944
- for (let tx = tx0; tx <= tx1; tx++) {
945
- tiles[rowBase + tx].push(segIndex);
946
- }
947
- }
948
- }
949
- tileListsByGlyph[gi] = tiles;
950
- for (let ti = 0; ti < tileCount; ti++) {
951
- tileIndexRefsTotal += tiles[ti].length;
952
- }
953
- }
954
- }
955
- const tileIndicesTex = useTiles
956
- ? createPackedTexture(tileIndexRefsTotal, segmentTextureWidth)
957
- : undefined;
958
- if (useTiles && tileRangesTex && tileIndicesTex) {
959
- let tileIndexCursor = 0;
960
- for (let gi = 0; gi < uniqueGlyphIds.length; gi++) {
961
- const tiles = tileListsByGlyph[gi];
962
- for (let ti = 0; ti < tileCount; ti++) {
963
- const list = tiles[ti];
964
- const start = tileIndexCursor;
965
- const count = list.length;
966
- // tileRanges texel: (start, count, 0, 0)
967
- this.writeSegmentToTexture(tileRangesTex, gi * tileCount + ti, start, count, 0, 0);
968
- for (let k = 0; k < count; k++) {
969
- const segIndex = list[k];
970
- // tileIndices texel.x = segIndex
971
- this.writeSegmentToTexture(tileIndicesTex, tileIndexCursor++, segIndex, 0, 0, 0);
972
- }
973
- }
974
- }
975
- }
976
- let glyphInstanceCount = 0;
977
- for (const line of clustersByLine) {
978
- for (const cluster of line) {
979
- for (const g of cluster.glyphs) {
980
- const range = rangesByGlyph.get(g.g);
981
- if (range && range.count > 0)
982
- glyphInstanceCount++;
983
- }
984
- }
985
- }
986
- const instances = {
987
- position: new Float32Array(glyphInstanceCount * 3),
988
- bounds: new Float32Array(glyphInstanceCount * 4),
989
- segmentRange: new Uint32Array(glyphInstanceCount * 2),
990
- glyphDataIndex: new Uint32Array(glyphInstanceCount),
991
- glyphIndex: new Uint32Array(glyphInstanceCount),
992
- textIndex: new Uint32Array(glyphInstanceCount),
993
- lineIndex: new Uint32Array(glyphInstanceCount)
994
- };
995
- const glyphInfos = [];
996
- const planeBounds = {
997
- min: { x: Infinity, y: Infinity, z: 0 },
998
- max: { x: -Infinity, y: -Infinity, z: 0 }
999
- };
1000
- let instanceCursor = 0;
1001
- for (const line of clustersByLine) {
1002
- for (const cluster of line) {
1003
- const clusterX = cluster.position.x;
1004
- const clusterY = cluster.position.y;
1005
- const clusterZ = cluster.position.z;
1006
- for (const g of cluster.glyphs) {
1007
- const range = rangesByGlyph.get(g.g);
1008
- if (!range || range.count === 0)
1009
- continue;
1010
- const px = (clusterX + (g.x ?? 0)) * scale;
1011
- const py = (clusterY + (g.y ?? 0)) * scale;
1012
- const pz = clusterZ * scale;
1013
- const gi = glyphDataIndexByGlyphId.get(g.g) ?? 0;
1014
- const bMinX = glyphMinX[gi];
1015
- const bMinY = glyphMinY[gi];
1016
- const bMaxX = glyphMaxX[gi];
1017
- const bMaxY = glyphMaxY[gi];
1018
- // Instance attributes
1019
- instances.position[instanceCursor * 3 + 0] = px;
1020
- instances.position[instanceCursor * 3 + 1] = py;
1021
- instances.position[instanceCursor * 3 + 2] = pz;
1022
- instances.bounds[instanceCursor * 4 + 0] = bMinX;
1023
- instances.bounds[instanceCursor * 4 + 1] = bMinY;
1024
- instances.bounds[instanceCursor * 4 + 2] = bMaxX;
1025
- instances.bounds[instanceCursor * 4 + 3] = bMaxY;
1026
- instances.segmentRange[instanceCursor * 2 + 0] = range.start;
1027
- instances.segmentRange[instanceCursor * 2 + 1] = range.count;
1028
- instances.glyphDataIndex[instanceCursor] =
1029
- glyphDataIndexByGlyphId.get(g.g) ?? 0;
1030
- instances.glyphIndex[instanceCursor] = g.g;
1031
- instances.textIndex[instanceCursor] = g.absoluteTextIndex;
1032
- instances.lineIndex[instanceCursor] = g.lineIndex;
1033
- // Glyph info for querying/selection (world bounds)
1034
- const glyphInfo = {
1035
- textIndex: g.absoluteTextIndex,
1036
- lineIndex: g.lineIndex,
1037
- vertexStart: 0,
1038
- vertexCount: 0,
1039
- segmentStart: range.start,
1040
- segmentCount: range.count,
1041
- bounds: {
1042
- min: { x: px + bMinX, y: py + bMinY, z: pz },
1043
- max: { x: px + bMaxX, y: py + bMaxY, z: pz }
1044
- }
1045
- };
1046
- glyphInfos.push(glyphInfo);
1047
- if (glyphInfo.bounds.min.x < planeBounds.min.x)
1048
- planeBounds.min.x = glyphInfo.bounds.min.x;
1049
- if (glyphInfo.bounds.min.y < planeBounds.min.y)
1050
- planeBounds.min.y = glyphInfo.bounds.min.y;
1051
- if (glyphInfo.bounds.min.z < planeBounds.min.z)
1052
- planeBounds.min.z = glyphInfo.bounds.min.z;
1053
- if (glyphInfo.bounds.max.x > planeBounds.max.x)
1054
- planeBounds.max.x = glyphInfo.bounds.max.x;
1055
- if (glyphInfo.bounds.max.y > planeBounds.max.y)
1056
- planeBounds.max.y = glyphInfo.bounds.max.y;
1057
- if (glyphInfo.bounds.max.z > planeBounds.max.z)
1058
- planeBounds.max.z = glyphInfo.bounds.max.z;
1059
- instanceCursor++;
1060
- }
1061
- }
1062
- }
1063
- if (glyphInfos.length === 0) {
1064
- planeBounds.min.x = 0;
1065
- planeBounds.min.y = 0;
1066
- planeBounds.min.z = 0;
1067
- planeBounds.max.x = 0;
1068
- planeBounds.max.y = 0;
1069
- planeBounds.max.z = 0;
1070
- }
1071
- return {
1072
- quadVertices,
1073
- quadIndices,
1074
- instances,
1075
- segmentTexelsPerSegment,
1076
- segments: segmentsTex,
1077
- segmentBounds: boundsTex,
1078
- bandCount: useBands ? bandCount : undefined,
1079
- bandRanges: bandRangesTex,
1080
- bandIndices: bandIndicesTex,
1081
- xBandCount: useBands ? xBandCount : undefined,
1082
- xBandRanges: xBandRangesTex,
1083
- xBandIndices: xBandIndicesTex,
1084
- tileCountX: useTiles ? tileCountX : undefined,
1085
- tileCountY: useTiles ? tileCountY : undefined,
1086
- tileRanges: tileRangesTex,
1087
- tileIndices: tileIndicesTex,
1088
- glyphs: glyphInfos,
1089
- planeBounds
1090
- };
1091
- }
1092
- }
3
+ var core = require('./core.cjs');
4
+ var loopBlinnTSL = require('./loopBlinnTSL.cjs');
1093
5
 
1094
6
  const CONTOUR_EPSILON = 0.001;
1095
7
  const CURVE_LINEARITY_EPSILON = 1e-5;
@@ -1267,192 +179,56 @@ function buildVectorGeometry(input) {
1267
179
  };
1268
180
  }
1269
181
 
1270
- // Loop-Blinn TSL adapter for Three.js WebGPURenderer.
1271
- // Creates meshes with TSL node materials for Loop-Blinn curve evaluation
1272
- // and Kokojima stencil fill. Works on WebGL (r170+) and WebGPU (r182+).
1273
- // Requires peer dependencies: three, three/tsl
1274
- // @ts-ignore - three is a peer dependency
1275
- // TSL fragment node: evaluates u^2 - v = 0 per fragment with
1276
- // screen-space derivative antialiasing. Discards outside fragments.
1277
- // UV convention per triangle: p0=(0,0), p1=(0.5,0), p2=(1,1)
1278
- const loopBlinnFragment = tsl.Fn(() => {
1279
- const curveUV = tsl.uv();
1280
- const px = tsl.dFdx(curveUV);
1281
- const py = tsl.dFdy(curveUV);
1282
- const fx = tsl.float(2.0).mul(curveUV.x).mul(px.x).sub(px.y);
1283
- const fy = tsl.float(2.0).mul(curveUV.x).mul(py.x).sub(py.y);
1284
- const denom = tsl.sqrt(fx.mul(fx).add(fy.mul(fy)));
1285
- const f = curveUV.x.mul(curveUV.x).sub(curveUV.y);
1286
- const sd = f.div(denom.max(1e-6));
1287
- const alpha = tsl.clamp(tsl.float(0.5).sub(sd), 0.0, 1.0);
1288
- tsl.Discard(alpha.lessThanEqual(0.0));
1289
- return tsl.vec4(1.0, 1.0, 1.0, alpha);
1290
- });
1291
- function setGlyphAttrsOnGeometry(geo, attrs, offsetX = 0, offsetY = 0) {
1292
- if (!attrs)
1293
- return;
1294
- const glyphCenter = new Float32Array(attrs.glyphCenter);
1295
- if (offsetX !== 0 || offsetY !== 0) {
1296
- for (let i = 0; i < glyphCenter.length; i += 3) {
1297
- glyphCenter[i] += offsetX;
1298
- glyphCenter[i + 1] += offsetY;
1299
- }
1300
- }
1301
- geo.setAttribute('glyphCenter', new THREE__namespace.Float32BufferAttribute(glyphCenter, 3));
1302
- geo.setAttribute('glyphIndex', new THREE__namespace.Float32BufferAttribute(attrs.glyphIndex, 1));
1303
- geo.setAttribute('glyphProgress', new THREE__namespace.Float32BufferAttribute(attrs.glyphProgress, 1));
1304
- geo.setAttribute('glyphLineIndex', new THREE__namespace.Float32BufferAttribute(attrs.glyphLineIndex, 1));
1305
- geo.setAttribute('glyphBaselineY', new THREE__namespace.Float32BufferAttribute(attrs.glyphBaselineY, 1));
1306
- }
1307
- function applyStencilXOR(mat) {
1308
- mat.depthTest = false;
1309
- mat.depthWrite = false;
1310
- mat.side = THREE__namespace.DoubleSide;
1311
- mat.stencilWrite = true;
1312
- mat.stencilFunc = THREE__namespace.AlwaysStencilFunc;
1313
- mat.stencilRef = 0;
1314
- mat.stencilFuncMask = 0xFF;
1315
- mat.stencilWriteMask = 0xFF;
1316
- mat.stencilFail = THREE__namespace.KeepStencilOp;
1317
- mat.stencilZFail = THREE__namespace.KeepStencilOp;
1318
- mat.stencilZPass = THREE__namespace.InvertStencilOp;
1319
- }
1320
- // Three meshes that must render in order (via renderOrder or sequential draws):
1321
- // 1. interiorMesh - stencil XOR (fan triangulated interiors, no color output)
1322
- // 2. curveMesh - stencil XOR (Loop-Blinn curve eval, alpha-to-coverage)
1323
- // 3. fillMesh - color where stencil != 0, then zeros stencil
1324
- function createVectorMeshes(data, color) {
1325
- const interiorGeo = new THREE__namespace.BufferGeometry();
1326
- interiorGeo.setAttribute('position', new THREE__namespace.Float32BufferAttribute(data.interiorPositions, 3));
1327
- interiorGeo.setIndex(new THREE__namespace.BufferAttribute(data.interiorIndices, 1));
1328
- setGlyphAttrsOnGeometry(interiorGeo, data.interiorGlyphAttrs);
1329
- const curveGeo = new THREE__namespace.BufferGeometry();
1330
- curveGeo.setAttribute('position', new THREE__namespace.Float32BufferAttribute(data.curvePositions, 3));
1331
- const curveVertCount = data.curvePositions.length / 3;
1332
- const curveUVs = new Float32Array(curveVertCount * 2);
1333
- for (let i = 0; i < curveVertCount; i += 3) {
1334
- curveUVs[i * 2] = 0;
1335
- curveUVs[i * 2 + 1] = 0; // p0
1336
- curveUVs[i * 2 + 2] = 0.5;
1337
- curveUVs[i * 2 + 3] = 0; // p1
1338
- curveUVs[i * 2 + 4] = 1;
1339
- curveUVs[i * 2 + 5] = 1; // p2
1340
- }
1341
- curveGeo.setAttribute('uv', new THREE__namespace.Float32BufferAttribute(curveUVs, 2));
1342
- setGlyphAttrsOnGeometry(curveGeo, data.curveGlyphAttrs);
1343
- const fillGeo = new THREE__namespace.BufferGeometry();
1344
- fillGeo.setAttribute('position', new THREE__namespace.Float32BufferAttribute(data.fillPositions, 3));
1345
- fillGeo.setIndex(new THREE__namespace.BufferAttribute(data.fillIndices, 1));
1346
- setGlyphAttrsOnGeometry(fillGeo, data.fillGlyphAttrs);
1347
- // 1) Interior stencil material - no color output
1348
- const stencilInteriorMat = new THREE__namespace.MeshBasicNodeMaterial();
1349
- applyStencilXOR(stencilInteriorMat);
1350
- stencilInteriorMat.colorWrite = false;
1351
- // 2) Curve stencil material - Loop-Blinn fragment evaluation
1352
- const stencilCurveMat = new THREE__namespace.MeshBasicNodeMaterial();
1353
- applyStencilXOR(stencilCurveMat);
1354
- stencilCurveMat.colorWrite = false;
1355
- stencilCurveMat.alphaToCoverage = true;
1356
- stencilCurveMat.fragmentNode = loopBlinnFragment();
1357
- // 3) Color fill material - renders where stencil != 0
1358
- const colorMat = new THREE__namespace.MeshBasicNodeMaterial();
1359
- colorMat.depthTest = false;
1360
- colorMat.depthWrite = false;
1361
- colorMat.side = THREE__namespace.DoubleSide;
1362
- colorMat.stencilWrite = true;
1363
- colorMat.stencilFunc = THREE__namespace.NotEqualStencilFunc;
1364
- colorMat.stencilRef = 0;
1365
- colorMat.stencilFuncMask = 0xFF;
1366
- colorMat.stencilWriteMask = 0xFF;
1367
- colorMat.stencilFail = THREE__namespace.KeepStencilOp;
1368
- colorMat.stencilZFail = THREE__namespace.KeepStencilOp;
1369
- colorMat.stencilZPass = THREE__namespace.ZeroStencilOp;
1370
- if (color !== undefined) {
1371
- colorMat.color = new THREE__namespace.Color(color);
1372
- }
1373
- const interiorMesh = new THREE__namespace.Mesh(interiorGeo, stencilInteriorMat);
1374
- const curveMesh = new THREE__namespace.Mesh(curveGeo, stencilCurveMat);
1375
- const fillMesh = new THREE__namespace.Mesh(fillGeo, colorMat);
182
+ function wrapResult(coreResult, opts) {
183
+ const meshes = loopBlinnTSL.createVectorMeshes(coreResult.geometryData, {
184
+ color: opts.color,
185
+ positionNode: opts.positionNode,
186
+ colorNode: opts.colorNode,
187
+ center: opts.center,
188
+ });
1376
189
  return {
1377
- interiorMesh,
1378
- curveMesh,
1379
- fillMesh,
1380
- setOffset(x, y, z = 0) {
1381
- interiorMesh.position.set(x, y, z);
1382
- curveMesh.position.set(x, y, z);
1383
- fillMesh.position.set(x, y, z);
190
+ glyphs: coreResult.glyphs,
191
+ geometryData: coreResult.geometryData,
192
+ group: meshes.group,
193
+ interiorGeometry: meshes.interiorGeometry,
194
+ curveGeometry: meshes.curveGeometry,
195
+ fillGeometry: meshes.fillGeometry,
196
+ query: coreResult.query,
197
+ getLoadedFont: coreResult.getLoadedFont,
198
+ measureTextWidth: coreResult.measureTextWidth,
199
+ updateMaterials: meshes.updateMaterials,
200
+ async update(newOptions) {
201
+ const newCore = await coreResult.update(newOptions);
202
+ return wrapResult(newCore, { ...opts, ...newOptions });
1384
203
  },
1385
204
  dispose() {
1386
- interiorGeo.dispose();
1387
- curveGeo.dispose();
1388
- fillGeo.dispose();
1389
- stencilInteriorMat.dispose();
1390
- stencilCurveMat.dispose();
1391
- colorMat.dispose();
205
+ meshes.dispose();
206
+ coreResult.dispose();
1392
207
  }
1393
208
  };
1394
209
  }
1395
-
1396
- function buildVectorResult(layoutHandle, vectorBuilder, options) {
1397
- const scale = layoutHandle.layoutData.pixelsPerFontUnit;
1398
- const { loopBlinnInput, glyphs } = vectorBuilder.buildForLoopBlinn(layoutHandle.clustersByLine, scale);
1399
- const geometryData = buildVectorGeometry(loopBlinnInput);
1400
- let cachedQuery = null;
1401
- const update = async (newOptions) => {
1402
- const mergedOptions = { ...options };
1403
- for (const key in newOptions) {
1404
- const value = newOptions[key];
1405
- if (value !== undefined) {
1406
- mergedOptions[key] = value;
1407
- }
1408
- }
1409
- if (newOptions.font !== undefined ||
1410
- newOptions.fontVariations !== undefined ||
1411
- newOptions.fontFeatures !== undefined) {
1412
- const newLayout = await layoutHandle.update(mergedOptions);
1413
- const newBuilder = new GlyphVectorGeometryBuilder(newLayout.loadedFont, sharedCaches.globalOutlineCache);
1414
- newBuilder.setFontId(newLayout.fontId);
1415
- layoutHandle = newLayout;
1416
- options = mergedOptions;
1417
- return buildVectorResult(layoutHandle, newBuilder, options);
1418
- }
1419
- const newLayout = await layoutHandle.update(mergedOptions);
1420
- layoutHandle = newLayout;
1421
- options = mergedOptions;
1422
- return buildVectorResult(layoutHandle, vectorBuilder, options);
1423
- };
1424
- return {
1425
- glyphs,
1426
- geometryData,
1427
- query: (queryOptions) => {
1428
- if (!cachedQuery) {
1429
- cachedQuery = new TextRangeQuery.TextRangeQuery(options.text, glyphs);
1430
- }
1431
- return cachedQuery.execute(queryOptions);
1432
- },
1433
- getLoadedFont: () => layoutHandle.getLoadedFont(),
1434
- measureTextWidth: (text, letterSpacing) => layoutHandle.measureTextWidth(text, letterSpacing),
1435
- update,
1436
- dispose: () => layoutHandle.dispose()
1437
- };
1438
- }
1439
210
  class Text {
1440
- static { this.setHarfBuzzPath = Text$1.Text.setHarfBuzzPath; }
1441
- static { this.setHarfBuzzBuffer = Text$1.Text.setHarfBuzzBuffer; }
1442
- static { this.init = Text$1.Text.init; }
1443
- static { this.registerPattern = Text$1.Text.registerPattern; }
1444
- static { this.preloadPatterns = Text$1.Text.preloadPatterns; }
1445
- static { this.setMaxFontCacheMemoryMB = Text$1.Text.setMaxFontCacheMemoryMB; }
1446
- static { this.enableWoff2 = Text$1.Text.enableWoff2; }
211
+ static { this.setHarfBuzzPath = core.Text.setHarfBuzzPath; }
212
+ static { this.setHarfBuzzBuffer = core.Text.setHarfBuzzBuffer; }
213
+ static { this.init = core.Text.init; }
214
+ static { this.registerPattern = core.Text.registerPattern; }
215
+ static { this.preloadPatterns = core.Text.preloadPatterns; }
216
+ static { this.setMaxFontCacheMemoryMB = core.Text.setMaxFontCacheMemoryMB; }
217
+ static { this.enableWoff2 = core.Text.enableWoff2; }
1447
218
  static async create(options) {
1448
- const layoutHandle = await Text$1.Text.create(options);
1449
- const vectorBuilder = new GlyphVectorGeometryBuilder(layoutHandle.loadedFont, sharedCaches.globalOutlineCache);
1450
- vectorBuilder.setFontId(layoutHandle.fontId);
1451
- return buildVectorResult(layoutHandle, vectorBuilder, options);
219
+ const coreResult = await core.Text.create(options);
220
+ return wrapResult(coreResult, options);
1452
221
  }
1453
222
  }
1454
223
 
224
+ Object.defineProperty(exports, 'createVectorMeshes', {
225
+ enumerable: true,
226
+ get: function () { return loopBlinnTSL.createVectorMeshes; }
227
+ });
228
+ Object.defineProperty(exports, 'loopBlinnFragment', {
229
+ enumerable: true,
230
+ get: function () { return loopBlinnTSL.loopBlinnFragment; }
231
+ });
1455
232
  exports.Text = Text;
1456
233
  exports.buildVectorGeometry = buildVectorGeometry;
1457
- exports.createVectorMeshes = createVectorMeshes;
1458
234
  exports.extractContours = extractContours;