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.
- package/README.md +15 -16
- package/dist/index.cjs +66 -20
- package/dist/index.d.ts +6 -0
- package/dist/index.js +66 -20
- package/dist/index.min.cjs +310 -307
- package/dist/index.min.js +265 -262
- package/dist/index.umd.js +68 -21
- package/dist/index.umd.min.js +268 -265
- package/dist/three/index.cjs +2 -1
- package/dist/three/index.d.ts +1 -0
- package/dist/three/index.js +2 -1
- package/dist/three/react.cjs +35 -17
- package/dist/three/react.d.ts +6 -0
- package/dist/three/react.js +35 -17
- package/dist/types/core/Text.d.ts +6 -0
- package/dist/types/three/index.d.ts +1 -0
- package/dist/types/vector/core.d.ts +27 -0
- package/dist/types/vector/index.d.ts +30 -24
- package/dist/types/vector/loopBlinnTSL.d.ts +14 -3
- package/dist/types/vector/react.d.ts +4 -6
- package/dist/vector/all.cjs +21 -0
- package/dist/vector/all.d.ts +134 -0
- package/dist/vector/all.js +2 -0
- package/dist/vector/core.cjs +1310 -0
- package/dist/vector/core.d.ts +112 -0
- package/dist/vector/core.js +1306 -0
- package/dist/vector/index.cjs +41 -1265
- package/dist/vector/index.d.ts +335 -20
- package/dist/vector/index.js +28 -1239
- package/dist/vector/loopBlinnTSL.cjs +229 -0
- package/dist/vector/loopBlinnTSL.d.ts +69 -0
- package/dist/vector/loopBlinnTSL.js +207 -0
- package/dist/vector/react.cjs +45 -60
- package/dist/vector/react.d.ts +9 -3
- package/dist/vector/react.js +46 -42
- package/dist/vector/webgl/index.cjs +3 -1
- package/dist/vector/webgl/index.js +3 -1
- package/package.json +1 -1
package/dist/vector/index.cjs
CHANGED
|
@@ -1,1095 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
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
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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
|
-
|
|
1387
|
-
|
|
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 =
|
|
1441
|
-
static { this.setHarfBuzzBuffer =
|
|
1442
|
-
static { this.init =
|
|
1443
|
-
static { this.registerPattern =
|
|
1444
|
-
static { this.preloadPatterns =
|
|
1445
|
-
static { this.setMaxFontCacheMemoryMB =
|
|
1446
|
-
static { this.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
|
|
1449
|
-
|
|
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;
|