three-text 0.5.2 → 0.6.0
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/LICENSE_THIRD_PARTY +15 -0
- package/README.md +73 -42
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/three/react.d.ts +2 -0
- package/dist/types/core/types.d.ts +2 -33
- package/dist/types/vector/{core.d.ts → core/index.d.ts} +8 -7
- package/dist/types/vector/index.d.ts +22 -25
- package/dist/types/vector/react.d.ts +4 -3
- package/dist/types/vector/slug/SlugPacker.d.ts +2 -0
- package/dist/types/vector/slug/curveUtils.d.ts +6 -0
- package/dist/types/vector/slug/index.d.ts +8 -0
- package/dist/types/vector/slug/shaderStrings.d.ts +4 -0
- package/dist/types/vector/slug/slugGLSL.d.ts +21 -0
- package/dist/types/vector/slug/slugTSL.d.ts +13 -0
- package/dist/types/vector/slug/types.d.ts +30 -0
- package/dist/types/vector/slug/unpackVertices.d.ts +11 -0
- package/dist/types/vector/webgl/index.d.ts +7 -3
- package/dist/types/vector/webgpu/index.d.ts +4 -4
- package/dist/vector/core/index.cjs +856 -0
- package/dist/vector/core/index.d.ts +63 -0
- package/dist/vector/core/index.js +854 -0
- package/dist/vector/core.cjs +4419 -240
- package/dist/vector/core.d.ts +361 -71
- package/dist/vector/core.js +4406 -226
- package/dist/vector/index.cjs +5 -229
- package/dist/vector/index.d.ts +45 -396
- package/dist/vector/index.js +3 -223
- package/dist/vector/index2.cjs +287 -0
- package/dist/vector/index2.js +264 -0
- package/dist/vector/react.cjs +37 -8
- package/dist/vector/react.d.ts +6 -3
- package/dist/vector/react.js +18 -8
- package/dist/vector/slugTSL.cjs +252 -0
- package/dist/vector/slugTSL.js +231 -0
- package/dist/vector/webgl/index.cjs +131 -201
- package/dist/vector/webgl/index.d.ts +19 -44
- package/dist/vector/webgl/index.js +131 -201
- package/dist/vector/webgpu/index.cjs +100 -283
- package/dist/vector/webgpu/index.d.ts +16 -45
- package/dist/vector/webgpu/index.js +100 -283
- package/package.json +6 -1
- package/dist/types/vector/GlyphVectorGeometryBuilder.d.ts +0 -26
- package/dist/types/vector/LoopBlinnGeometry.d.ts +0 -68
- package/dist/types/vector/loopBlinnTSL.d.ts +0 -22
- package/dist/vector/loopBlinnTSL.cjs +0 -229
- package/dist/vector/loopBlinnTSL.js +0 -207
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
import { Text as Text$1 } from '../../index.js';
|
|
2
|
+
import { globalOutlineCache } from '../../index.js';
|
|
3
|
+
import { getSharedDrawCallbackHandler } from '../../index.js';
|
|
4
|
+
import { TextRangeQuery } from '../../index.js';
|
|
5
|
+
|
|
6
|
+
// 2D Vector
|
|
7
|
+
class Vec2 {
|
|
8
|
+
constructor(x = 0, y = 0) {
|
|
9
|
+
this.x = x;
|
|
10
|
+
this.y = y;
|
|
11
|
+
}
|
|
12
|
+
set(x, y) {
|
|
13
|
+
this.x = x;
|
|
14
|
+
this.y = y;
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
clone() {
|
|
18
|
+
return new Vec2(this.x, this.y);
|
|
19
|
+
}
|
|
20
|
+
copy(v) {
|
|
21
|
+
this.x = v.x;
|
|
22
|
+
this.y = v.y;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
add(v) {
|
|
26
|
+
this.x += v.x;
|
|
27
|
+
this.y += v.y;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
sub(v) {
|
|
31
|
+
this.x -= v.x;
|
|
32
|
+
this.y -= v.y;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
multiply(scalar) {
|
|
36
|
+
this.x *= scalar;
|
|
37
|
+
this.y *= scalar;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
divide(scalar) {
|
|
41
|
+
this.x /= scalar;
|
|
42
|
+
this.y /= scalar;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
length() {
|
|
46
|
+
return Math.sqrt(this.x * this.x + this.y * this.y);
|
|
47
|
+
}
|
|
48
|
+
lengthSq() {
|
|
49
|
+
return this.x * this.x + this.y * this.y;
|
|
50
|
+
}
|
|
51
|
+
normalize() {
|
|
52
|
+
const len = this.length();
|
|
53
|
+
if (len > 0) {
|
|
54
|
+
this.divide(len);
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
dot(v) {
|
|
59
|
+
return this.x * v.x + this.y * v.y;
|
|
60
|
+
}
|
|
61
|
+
distanceTo(v) {
|
|
62
|
+
const dx = this.x - v.x;
|
|
63
|
+
const dy = this.y - v.y;
|
|
64
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
65
|
+
}
|
|
66
|
+
distanceToSquared(v) {
|
|
67
|
+
const dx = this.x - v.x;
|
|
68
|
+
const dy = this.y - v.y;
|
|
69
|
+
return dx * dx + dy * dy;
|
|
70
|
+
}
|
|
71
|
+
equals(v) {
|
|
72
|
+
return this.x === v.x && this.y === v.y;
|
|
73
|
+
}
|
|
74
|
+
angle() {
|
|
75
|
+
return Math.atan2(this.y, this.x);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class GlyphOutlineCollector {
|
|
80
|
+
constructor() {
|
|
81
|
+
this.currentGlyphId = 0;
|
|
82
|
+
this.currentTextIndex = 0;
|
|
83
|
+
this.inGlyph = false;
|
|
84
|
+
this.currentSegments = [];
|
|
85
|
+
this.currentPoint = null;
|
|
86
|
+
this.contourStartPoint = null;
|
|
87
|
+
this.contourId = 0;
|
|
88
|
+
this.currentGlyphBounds = {
|
|
89
|
+
min: new Vec2(Infinity, Infinity),
|
|
90
|
+
max: new Vec2(-Infinity, -Infinity)
|
|
91
|
+
};
|
|
92
|
+
this.collectedGlyphs = [];
|
|
93
|
+
this.currentPosition = new Vec2(0, 0);
|
|
94
|
+
}
|
|
95
|
+
setPosition(x, y) {
|
|
96
|
+
this.currentPosition.set(x, y);
|
|
97
|
+
}
|
|
98
|
+
updatePosition(dx, dy) {
|
|
99
|
+
this.currentPosition.x += dx;
|
|
100
|
+
this.currentPosition.y += dy;
|
|
101
|
+
}
|
|
102
|
+
beginGlyph(glyphId, textIndex) {
|
|
103
|
+
if (this.currentSegments.length > 0) {
|
|
104
|
+
this.finishGlyph();
|
|
105
|
+
}
|
|
106
|
+
this.currentGlyphId = glyphId;
|
|
107
|
+
this.currentTextIndex = textIndex;
|
|
108
|
+
this.inGlyph = true;
|
|
109
|
+
this.currentSegments = [];
|
|
110
|
+
this.currentPoint = null;
|
|
111
|
+
this.contourStartPoint = null;
|
|
112
|
+
this.contourId = 0;
|
|
113
|
+
this.currentGlyphBounds.min.set(Infinity, Infinity);
|
|
114
|
+
this.currentGlyphBounds.max.set(-Infinity, -Infinity);
|
|
115
|
+
}
|
|
116
|
+
finishGlyph() {
|
|
117
|
+
if (this.currentSegments.length > 0) {
|
|
118
|
+
this.collectedGlyphs.push({
|
|
119
|
+
glyphId: this.currentGlyphId,
|
|
120
|
+
textIndex: this.currentTextIndex,
|
|
121
|
+
segments: this.currentSegments,
|
|
122
|
+
bounds: {
|
|
123
|
+
min: {
|
|
124
|
+
x: this.currentGlyphBounds.min.x,
|
|
125
|
+
y: this.currentGlyphBounds.min.y
|
|
126
|
+
},
|
|
127
|
+
max: {
|
|
128
|
+
x: this.currentGlyphBounds.max.x,
|
|
129
|
+
y: this.currentGlyphBounds.max.y
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
this.collectedGlyphs.push({
|
|
136
|
+
glyphId: this.currentGlyphId,
|
|
137
|
+
textIndex: this.currentTextIndex,
|
|
138
|
+
segments: [],
|
|
139
|
+
bounds: {
|
|
140
|
+
min: { x: 0, y: 0 },
|
|
141
|
+
max: { x: 0, y: 0 }
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
this.currentSegments = [];
|
|
146
|
+
this.currentPoint = null;
|
|
147
|
+
this.contourStartPoint = null;
|
|
148
|
+
this.inGlyph = false;
|
|
149
|
+
this.currentGlyphId = 0;
|
|
150
|
+
this.currentTextIndex = 0;
|
|
151
|
+
}
|
|
152
|
+
onMoveTo(x, y) {
|
|
153
|
+
const p = new Vec2(x, y);
|
|
154
|
+
this.updateBounds(p);
|
|
155
|
+
this.currentPoint = p;
|
|
156
|
+
this.contourStartPoint = p;
|
|
157
|
+
this.contourId++;
|
|
158
|
+
}
|
|
159
|
+
onLineTo(x, y) {
|
|
160
|
+
if (!this.currentPoint)
|
|
161
|
+
return;
|
|
162
|
+
const p1 = new Vec2(x, y);
|
|
163
|
+
const p0 = this.currentPoint;
|
|
164
|
+
this.updateBounds(p1);
|
|
165
|
+
this.currentSegments.push({
|
|
166
|
+
type: 0,
|
|
167
|
+
contourId: this.contourId,
|
|
168
|
+
p0,
|
|
169
|
+
p1
|
|
170
|
+
});
|
|
171
|
+
this.currentPoint = p1;
|
|
172
|
+
}
|
|
173
|
+
onQuadTo(cx, cy, x, y) {
|
|
174
|
+
if (!this.currentPoint)
|
|
175
|
+
return;
|
|
176
|
+
const p0 = this.currentPoint;
|
|
177
|
+
const p1 = new Vec2(cx, cy);
|
|
178
|
+
const p2 = new Vec2(x, y);
|
|
179
|
+
this.updateBounds(p1);
|
|
180
|
+
this.updateBounds(p2);
|
|
181
|
+
this.currentSegments.push({
|
|
182
|
+
type: 1,
|
|
183
|
+
contourId: this.contourId,
|
|
184
|
+
p0,
|
|
185
|
+
p1,
|
|
186
|
+
p2
|
|
187
|
+
});
|
|
188
|
+
this.currentPoint = p2;
|
|
189
|
+
}
|
|
190
|
+
onCubicTo(c1x, c1y, c2x, c2y, x, y) {
|
|
191
|
+
if (!this.currentPoint)
|
|
192
|
+
return;
|
|
193
|
+
const p0 = this.currentPoint;
|
|
194
|
+
const p1 = new Vec2(c1x, c1y);
|
|
195
|
+
const p2 = new Vec2(c2x, c2y);
|
|
196
|
+
const p3 = new Vec2(x, y);
|
|
197
|
+
this.updateBounds(p1);
|
|
198
|
+
this.updateBounds(p2);
|
|
199
|
+
this.updateBounds(p3);
|
|
200
|
+
this.currentSegments.push({
|
|
201
|
+
type: 2,
|
|
202
|
+
contourId: this.contourId,
|
|
203
|
+
p0,
|
|
204
|
+
p1,
|
|
205
|
+
p2,
|
|
206
|
+
p3
|
|
207
|
+
});
|
|
208
|
+
this.currentPoint = p3;
|
|
209
|
+
}
|
|
210
|
+
onClosePath() {
|
|
211
|
+
if (!this.currentPoint || !this.contourStartPoint)
|
|
212
|
+
return;
|
|
213
|
+
const p0 = this.currentPoint;
|
|
214
|
+
const p1 = this.contourStartPoint;
|
|
215
|
+
if (p0.x !== p1.x || p0.y !== p1.y) {
|
|
216
|
+
this.currentSegments.push({
|
|
217
|
+
type: 0,
|
|
218
|
+
contourId: this.contourId,
|
|
219
|
+
p0,
|
|
220
|
+
p1
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
this.currentPoint = p1;
|
|
224
|
+
this.contourStartPoint = null;
|
|
225
|
+
}
|
|
226
|
+
getCollectedGlyphs() {
|
|
227
|
+
if (this.inGlyph) {
|
|
228
|
+
this.finishGlyph();
|
|
229
|
+
}
|
|
230
|
+
return this.collectedGlyphs;
|
|
231
|
+
}
|
|
232
|
+
reset() {
|
|
233
|
+
this.collectedGlyphs = [];
|
|
234
|
+
this.currentGlyphId = 0;
|
|
235
|
+
this.currentTextIndex = 0;
|
|
236
|
+
this.currentSegments = [];
|
|
237
|
+
this.currentPoint = null;
|
|
238
|
+
this.contourStartPoint = null;
|
|
239
|
+
this.contourId = 0;
|
|
240
|
+
this.currentPosition.set(0, 0);
|
|
241
|
+
this.currentGlyphBounds.min.set(Infinity, Infinity);
|
|
242
|
+
this.currentGlyphBounds.max.set(-Infinity, -Infinity);
|
|
243
|
+
}
|
|
244
|
+
updateBounds(p) {
|
|
245
|
+
this.currentGlyphBounds.min.x = Math.min(this.currentGlyphBounds.min.x, p.x);
|
|
246
|
+
this.currentGlyphBounds.min.y = Math.min(this.currentGlyphBounds.min.y, p.y);
|
|
247
|
+
this.currentGlyphBounds.max.x = Math.max(this.currentGlyphBounds.max.x, p.x);
|
|
248
|
+
this.currentGlyphBounds.max.y = Math.max(this.currentGlyphBounds.max.y, p.y);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// CPU-side data packer for the Slug algorithm
|
|
253
|
+
// Faithful to Eric Lengyel's reference layout (MIT License, 2017)
|
|
254
|
+
//
|
|
255
|
+
// Takes generic quadratic Bezier shapes (not text-specific) and produces
|
|
256
|
+
// GPU-ready packed textures + vertex attribute buffers
|
|
257
|
+
const TEX_WIDTH = 4096;
|
|
258
|
+
const LOG_TEX_WIDTH = 12;
|
|
259
|
+
// Float/Uint32 reinterpretation helpers
|
|
260
|
+
const _f32 = new Float32Array(1);
|
|
261
|
+
const _u32 = new Uint32Array(_f32.buffer);
|
|
262
|
+
function uintAsFloat(u) {
|
|
263
|
+
_u32[0] = u;
|
|
264
|
+
return _f32[0];
|
|
265
|
+
}
|
|
266
|
+
// Pack an array of shapes into Slug's GPU data layout
|
|
267
|
+
// Each shape is a closed region defined by quadratic Bezier curves
|
|
268
|
+
function packSlugData(shapes, options) {
|
|
269
|
+
const bandCount = options?.bandCount ?? 16;
|
|
270
|
+
const evenOdd = options?.evenOdd ?? false;
|
|
271
|
+
// Pack all curves into curveTexture
|
|
272
|
+
const allCurves = [];
|
|
273
|
+
// Estimate max texels needed
|
|
274
|
+
let totalCurves = 0;
|
|
275
|
+
for (const shape of shapes) {
|
|
276
|
+
totalCurves += shape.curves.length;
|
|
277
|
+
}
|
|
278
|
+
const curveTexHeight = Math.ceil((totalCurves * 2) / TEX_WIDTH) + 1;
|
|
279
|
+
const curveData = new Float32Array(TEX_WIDTH * curveTexHeight * 4);
|
|
280
|
+
let curveX = 0;
|
|
281
|
+
let curveY = 0;
|
|
282
|
+
for (const shape of shapes) {
|
|
283
|
+
const entries = [];
|
|
284
|
+
for (const curve of shape.curves) {
|
|
285
|
+
// Don't let a curve span across row boundary (needs 2 consecutive texels)
|
|
286
|
+
if (curveX >= TEX_WIDTH - 1) {
|
|
287
|
+
curveX = 0;
|
|
288
|
+
curveY++;
|
|
289
|
+
}
|
|
290
|
+
const base = (curveY * TEX_WIDTH + curveX) * 4;
|
|
291
|
+
curveData[base + 0] = curve.p1[0];
|
|
292
|
+
curveData[base + 1] = curve.p1[1];
|
|
293
|
+
curveData[base + 2] = curve.p2[0];
|
|
294
|
+
curveData[base + 3] = curve.p2[1];
|
|
295
|
+
const base2 = base + 4;
|
|
296
|
+
curveData[base2 + 0] = curve.p3[0];
|
|
297
|
+
curveData[base2 + 1] = curve.p3[1];
|
|
298
|
+
const minX = Math.min(curve.p1[0], curve.p2[0], curve.p3[0]);
|
|
299
|
+
const minY = Math.min(curve.p1[1], curve.p2[1], curve.p3[1]);
|
|
300
|
+
const maxX = Math.max(curve.p1[0], curve.p2[0], curve.p3[0]);
|
|
301
|
+
const maxY = Math.max(curve.p1[1], curve.p2[1], curve.p3[1]);
|
|
302
|
+
entries.push({
|
|
303
|
+
p1x: curve.p1[0], p1y: curve.p1[1],
|
|
304
|
+
p2x: curve.p2[0], p2y: curve.p2[1],
|
|
305
|
+
p3x: curve.p3[0], p3y: curve.p3[1],
|
|
306
|
+
minX, minY, maxX, maxY,
|
|
307
|
+
curveTexX: curveX,
|
|
308
|
+
curveTexY: curveY
|
|
309
|
+
});
|
|
310
|
+
curveX += 2;
|
|
311
|
+
}
|
|
312
|
+
allCurves.push(entries);
|
|
313
|
+
}
|
|
314
|
+
const actualCurveTexHeight = curveY + 1;
|
|
315
|
+
// Build band data for each shape and pack into bandTexture
|
|
316
|
+
// Layout per shape in bandTexture (relative to glyphLoc):
|
|
317
|
+
// [0 .. hBandMax] : h-band headers
|
|
318
|
+
// [hBandMax+1 .. hBandMax+1+vBandMax] : v-band headers
|
|
319
|
+
// [hBandMax+vBandMax+2 .. ] : curve index lists
|
|
320
|
+
// First pass: compute total band texels needed
|
|
321
|
+
const shapeBandData = [];
|
|
322
|
+
let totalBandTexels = 0;
|
|
323
|
+
for (let si = 0; si < shapes.length; si++) {
|
|
324
|
+
const shape = shapes[si];
|
|
325
|
+
const curves = allCurves[si];
|
|
326
|
+
if (curves.length === 0) {
|
|
327
|
+
shapeBandData.push({
|
|
328
|
+
hBands: [], vBands: [], hLists: [], vLists: [],
|
|
329
|
+
totalTexels: 0, bandMaxX: 0, bandMaxY: 0
|
|
330
|
+
});
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
334
|
+
const w = bMaxX - bMinX;
|
|
335
|
+
const h = bMaxY - bMinY;
|
|
336
|
+
const hBandCount = Math.min(bandCount, 255); // max 255 (fits in 8 bits)
|
|
337
|
+
const vBandCount = Math.min(bandCount, 255);
|
|
338
|
+
const bandMaxY = hBandCount - 1;
|
|
339
|
+
const bandMaxX = vBandCount - 1;
|
|
340
|
+
// Build horizontal bands (partition y-axis)
|
|
341
|
+
const hBands = [];
|
|
342
|
+
const hLists = [];
|
|
343
|
+
const bandH = h / hBandCount;
|
|
344
|
+
for (let bi = 0; bi < hBandCount; bi++) {
|
|
345
|
+
const bandMinY = bMinY + bi * bandH;
|
|
346
|
+
const bandMaxYCoord = bandMinY + bandH;
|
|
347
|
+
// Collect curves whose y-range overlaps this band
|
|
348
|
+
const list = [];
|
|
349
|
+
for (const c of curves) {
|
|
350
|
+
if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
|
|
351
|
+
list.push({ curve: c, sortKey: c.maxX });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Sort by descending max-x for early exit
|
|
355
|
+
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
356
|
+
const flatList = [];
|
|
357
|
+
for (const item of list) {
|
|
358
|
+
flatList.push(item.curve.curveTexX, item.curve.curveTexY);
|
|
359
|
+
}
|
|
360
|
+
hBands.push({ curveCount: list.length, listOffset: 0 });
|
|
361
|
+
hLists.push(flatList);
|
|
362
|
+
}
|
|
363
|
+
// Build vertical bands (partition x-axis)
|
|
364
|
+
const vBands = [];
|
|
365
|
+
const vLists = [];
|
|
366
|
+
const bandW = w / vBandCount;
|
|
367
|
+
for (let bi = 0; bi < vBandCount; bi++) {
|
|
368
|
+
const bandMinX = bMinX + bi * bandW;
|
|
369
|
+
const bandMaxXCoord = bandMinX + bandW;
|
|
370
|
+
const list = [];
|
|
371
|
+
for (const c of curves) {
|
|
372
|
+
if (c.maxX >= bandMinX && c.minX <= bandMaxXCoord) {
|
|
373
|
+
list.push({ curve: c, sortKey: c.maxY });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Sort by descending max-y for early exit
|
|
377
|
+
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
378
|
+
const flatList = [];
|
|
379
|
+
for (const item of list) {
|
|
380
|
+
flatList.push(item.curve.curveTexX, item.curve.curveTexY);
|
|
381
|
+
}
|
|
382
|
+
vBands.push({ curveCount: list.length, listOffset: 0 });
|
|
383
|
+
vLists.push(flatList);
|
|
384
|
+
}
|
|
385
|
+
// Total texels for this shape: band headers + curve lists
|
|
386
|
+
const headerTexels = hBandCount + vBandCount;
|
|
387
|
+
let listTexels = 0;
|
|
388
|
+
for (const l of hLists)
|
|
389
|
+
listTexels += l.length / 2;
|
|
390
|
+
for (const l of vLists)
|
|
391
|
+
listTexels += l.length / 2;
|
|
392
|
+
const total = headerTexels + listTexels;
|
|
393
|
+
shapeBandData.push({
|
|
394
|
+
hBands, vBands, hLists, vLists,
|
|
395
|
+
totalTexels: total, bandMaxX, bandMaxY
|
|
396
|
+
});
|
|
397
|
+
totalBandTexels += total;
|
|
398
|
+
}
|
|
399
|
+
// Allocate bandTexture (extra rows for row-alignment padding of curve lists)
|
|
400
|
+
const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
|
|
401
|
+
const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
|
|
402
|
+
// Pack band data per shape
|
|
403
|
+
let bandX = 0;
|
|
404
|
+
let bandY = 0;
|
|
405
|
+
const glyphLocs = [];
|
|
406
|
+
for (let si = 0; si < shapes.length; si++) {
|
|
407
|
+
const sd = shapeBandData[si];
|
|
408
|
+
if (sd.totalTexels === 0) {
|
|
409
|
+
glyphLocs.push({ x: 0, y: 0 });
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
// Ensure glyph data doesn't start too close to row end
|
|
413
|
+
// (need at least headerTexels contiguous... actually wrapping is handled by CalcBandLoc)
|
|
414
|
+
// But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
|
|
415
|
+
// must be reachable. CalcBandLoc handles wrapping for curve lists.
|
|
416
|
+
// To be safe, start each glyph at the beginning of a row if remaining space is tight.
|
|
417
|
+
const minContiguous = sd.hBands.length + sd.vBands.length;
|
|
418
|
+
if (bandX + minContiguous > TEX_WIDTH) {
|
|
419
|
+
bandX = 0;
|
|
420
|
+
bandY++;
|
|
421
|
+
}
|
|
422
|
+
const glyphLocX = bandX;
|
|
423
|
+
const glyphLocY = bandY;
|
|
424
|
+
glyphLocs.push({ x: glyphLocX, y: glyphLocY });
|
|
425
|
+
// Curve lists start after all headers
|
|
426
|
+
let listStartOffset = sd.hBands.length + sd.vBands.length;
|
|
427
|
+
// The shader reads curve list entries at (hbandLoc.x + curveIndex, hbandLoc.y)
|
|
428
|
+
// with NO row wrapping. Each list must fit entirely within a single texture row.
|
|
429
|
+
// Pad the offset to the next row start when a list would cross a row boundary.
|
|
430
|
+
const ensureListFits = (listLen) => {
|
|
431
|
+
if (listLen === 0)
|
|
432
|
+
return;
|
|
433
|
+
const startX = (glyphLocX + listStartOffset) & ((1 << LOG_TEX_WIDTH) - 1);
|
|
434
|
+
if (startX + listLen > TEX_WIDTH) {
|
|
435
|
+
listStartOffset += (TEX_WIDTH - startX);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
// Assign list offsets for h-bands
|
|
439
|
+
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
440
|
+
const listLen = sd.hLists[bi].length / 2;
|
|
441
|
+
ensureListFits(listLen);
|
|
442
|
+
sd.hBands[bi].listOffset = listStartOffset;
|
|
443
|
+
listStartOffset += listLen;
|
|
444
|
+
}
|
|
445
|
+
// Assign list offsets for v-bands
|
|
446
|
+
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
447
|
+
const listLen = sd.vLists[bi].length / 2;
|
|
448
|
+
ensureListFits(listLen);
|
|
449
|
+
sd.vBands[bi].listOffset = listStartOffset;
|
|
450
|
+
listStartOffset += listLen;
|
|
451
|
+
}
|
|
452
|
+
// Write h-band headers
|
|
453
|
+
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
454
|
+
const tx = glyphLocX + bi;
|
|
455
|
+
const ty = glyphLocY;
|
|
456
|
+
const idx = (ty * TEX_WIDTH + tx) * 4;
|
|
457
|
+
bandData[idx + 0] = sd.hBands[bi].curveCount;
|
|
458
|
+
bandData[idx + 1] = sd.hBands[bi].listOffset;
|
|
459
|
+
bandData[idx + 2] = 0;
|
|
460
|
+
bandData[idx + 3] = 0;
|
|
461
|
+
}
|
|
462
|
+
// Write v-band headers (after h-bands)
|
|
463
|
+
const vBandStart = glyphLocX + sd.hBands.length;
|
|
464
|
+
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
465
|
+
const tx = vBandStart + bi;
|
|
466
|
+
const ty = glyphLocY;
|
|
467
|
+
const idx = (ty * TEX_WIDTH + tx) * 4;
|
|
468
|
+
bandData[idx + 0] = sd.vBands[bi].curveCount;
|
|
469
|
+
bandData[idx + 1] = sd.vBands[bi].listOffset;
|
|
470
|
+
bandData[idx + 2] = 0;
|
|
471
|
+
bandData[idx + 3] = 0;
|
|
472
|
+
}
|
|
473
|
+
// Write curve lists using CalcBandLoc-style wrapping
|
|
474
|
+
const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
|
|
475
|
+
// Write h-band curve lists
|
|
476
|
+
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
477
|
+
const list = sd.hLists[bi];
|
|
478
|
+
const baseOffset = sd.hBands[bi].listOffset;
|
|
479
|
+
for (let ci = 0; ci < list.length; ci += 2) {
|
|
480
|
+
let bx = glyphLocX + baseOffset + (ci >> 1);
|
|
481
|
+
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
482
|
+
bx &= texWidthMask;
|
|
483
|
+
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
484
|
+
bandData[idx + 0] = list[ci]; // curveTexX
|
|
485
|
+
bandData[idx + 1] = list[ci + 1]; // curveTexY
|
|
486
|
+
bandData[idx + 2] = 0;
|
|
487
|
+
bandData[idx + 3] = 0;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// Write v-band curve lists
|
|
491
|
+
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
492
|
+
const list = sd.vLists[bi];
|
|
493
|
+
const baseOffset = sd.vBands[bi].listOffset;
|
|
494
|
+
for (let ci = 0; ci < list.length; ci += 2) {
|
|
495
|
+
let bx = glyphLocX + baseOffset + (ci >> 1);
|
|
496
|
+
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
497
|
+
bx &= texWidthMask;
|
|
498
|
+
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
499
|
+
bandData[idx + 0] = list[ci];
|
|
500
|
+
bandData[idx + 1] = list[ci + 1];
|
|
501
|
+
bandData[idx + 2] = 0;
|
|
502
|
+
bandData[idx + 3] = 0;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Advance band cursor past this shape's data
|
|
506
|
+
let endBx = glyphLocX + listStartOffset;
|
|
507
|
+
bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
|
|
508
|
+
bandX = endBx & texWidthMask;
|
|
509
|
+
}
|
|
510
|
+
const actualBandTexHeight = bandY + 1;
|
|
511
|
+
// Build vertex attributes
|
|
512
|
+
// 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
|
|
513
|
+
const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
|
|
514
|
+
const VERTS_PER_SHAPE = 4;
|
|
515
|
+
const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
|
|
516
|
+
const indices = new Uint16Array(shapes.length * 6);
|
|
517
|
+
// Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
|
|
518
|
+
const cornerNormals = [
|
|
519
|
+
[-1, -1], // bottom-left
|
|
520
|
+
[1, -1], // bottom-right
|
|
521
|
+
[1, 1], // top-right
|
|
522
|
+
[-1, 1], // top-left
|
|
523
|
+
];
|
|
524
|
+
for (let si = 0; si < shapes.length; si++) {
|
|
525
|
+
const shape = shapes[si];
|
|
526
|
+
const sd = shapeBandData[si];
|
|
527
|
+
const glyph = glyphLocs[si];
|
|
528
|
+
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
529
|
+
const w = bMaxX - bMinX;
|
|
530
|
+
const h = bMaxY - bMinY;
|
|
531
|
+
// Corner positions in object-space
|
|
532
|
+
const corners = [
|
|
533
|
+
[bMinX, bMinY],
|
|
534
|
+
[bMaxX, bMinY],
|
|
535
|
+
[bMaxX, bMaxY],
|
|
536
|
+
[bMinX, bMaxY],
|
|
537
|
+
];
|
|
538
|
+
// Em-space sample coords at corners (same as object-space for 1:1 mapping)
|
|
539
|
+
const emCorners = [
|
|
540
|
+
[bMinX, bMinY],
|
|
541
|
+
[bMaxX, bMinY],
|
|
542
|
+
[bMaxX, bMaxY],
|
|
543
|
+
[bMinX, bMaxY],
|
|
544
|
+
];
|
|
545
|
+
// Pack tex.z: glyph location in band texture
|
|
546
|
+
const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
|
|
547
|
+
// Pack tex.w: band max + flags
|
|
548
|
+
let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
|
|
549
|
+
if (evenOdd)
|
|
550
|
+
texWBits |= 0x10000000; // E flag at bit 28
|
|
551
|
+
const texW = uintAsFloat(texWBits);
|
|
552
|
+
// Band transform: scale and offset to map em-coords to band indices
|
|
553
|
+
const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
|
|
554
|
+
const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
|
|
555
|
+
const bandOffsetX = -bMinX * bandScaleX;
|
|
556
|
+
const bandOffsetY = -bMinY * bandScaleY;
|
|
557
|
+
for (let vi = 0; vi < 4; vi++) {
|
|
558
|
+
const base = (si * 4 + vi) * FLOATS_PER_VERTEX;
|
|
559
|
+
// pos: .xy = position, .zw = normal
|
|
560
|
+
vertices[base + 0] = corners[vi][0];
|
|
561
|
+
vertices[base + 1] = corners[vi][1];
|
|
562
|
+
vertices[base + 2] = cornerNormals[vi][0];
|
|
563
|
+
vertices[base + 3] = cornerNormals[vi][1];
|
|
564
|
+
// tex: .xy = em-space coords, .z = packed glyph loc, .w = packed band max
|
|
565
|
+
vertices[base + 4] = emCorners[vi][0];
|
|
566
|
+
vertices[base + 5] = emCorners[vi][1];
|
|
567
|
+
vertices[base + 6] = texZ;
|
|
568
|
+
vertices[base + 7] = texW;
|
|
569
|
+
// jac: identity Jacobian (em-space = object-space)
|
|
570
|
+
vertices[base + 8] = 1.0;
|
|
571
|
+
vertices[base + 9] = 0.0;
|
|
572
|
+
vertices[base + 10] = 0.0;
|
|
573
|
+
vertices[base + 11] = 1.0;
|
|
574
|
+
// bnd: band scale and offset
|
|
575
|
+
vertices[base + 12] = bandScaleX;
|
|
576
|
+
vertices[base + 13] = bandScaleY;
|
|
577
|
+
vertices[base + 14] = bandOffsetX;
|
|
578
|
+
vertices[base + 15] = bandOffsetY;
|
|
579
|
+
// col: white with full alpha (caller overrides via uniform or attribute)
|
|
580
|
+
vertices[base + 16] = 1.0;
|
|
581
|
+
vertices[base + 17] = 1.0;
|
|
582
|
+
vertices[base + 18] = 1.0;
|
|
583
|
+
vertices[base + 19] = 1.0;
|
|
584
|
+
}
|
|
585
|
+
// Indices: two triangles per quad
|
|
586
|
+
const vBase = si * 4;
|
|
587
|
+
const iBase = si * 6;
|
|
588
|
+
indices[iBase + 0] = vBase + 0;
|
|
589
|
+
indices[iBase + 1] = vBase + 1;
|
|
590
|
+
indices[iBase + 2] = vBase + 2;
|
|
591
|
+
indices[iBase + 3] = vBase + 0;
|
|
592
|
+
indices[iBase + 4] = vBase + 2;
|
|
593
|
+
indices[iBase + 5] = vBase + 3;
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
curveTexture: {
|
|
597
|
+
data: curveData.slice(0, TEX_WIDTH * actualCurveTexHeight * 4),
|
|
598
|
+
width: TEX_WIDTH,
|
|
599
|
+
height: actualCurveTexHeight
|
|
600
|
+
},
|
|
601
|
+
bandTexture: {
|
|
602
|
+
data: bandData.slice(0, TEX_WIDTH * actualBandTexHeight * 4),
|
|
603
|
+
width: TEX_WIDTH,
|
|
604
|
+
height: actualBandTexHeight
|
|
605
|
+
},
|
|
606
|
+
vertices,
|
|
607
|
+
indices,
|
|
608
|
+
shapeCount: shapes.length
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const DEFAULT_MAX_ERROR = 0.01;
|
|
613
|
+
const DEFAULT_MAX_DEPTH = 8;
|
|
614
|
+
function cloneVec2(p) {
|
|
615
|
+
return [p[0], p[1]];
|
|
616
|
+
}
|
|
617
|
+
function cubicToQuadratics(p0, p1, p2, p3, options) {
|
|
618
|
+
const maxError = options?.maxError ?? DEFAULT_MAX_ERROR;
|
|
619
|
+
const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
620
|
+
const out = [];
|
|
621
|
+
const recurse = (rp0, rp1, rp2, rp3, depth) => {
|
|
622
|
+
// Error bound for best-fit quadratic approximation of this cubic:
|
|
623
|
+
// |P3 - 3*P2 + 3*P1 - P0| / 6
|
|
624
|
+
const dx = rp3[0] - 3 * rp2[0] + 3 * rp1[0] - rp0[0];
|
|
625
|
+
const dy = rp3[1] - 3 * rp2[1] + 3 * rp1[1] - rp0[1];
|
|
626
|
+
const err = dx * dx + dy * dy;
|
|
627
|
+
const threshold = maxError * maxError * 36;
|
|
628
|
+
if (err <= threshold || depth >= maxDepth) {
|
|
629
|
+
const qx = (3 * (rp1[0] + rp2[0]) - rp0[0] - rp3[0]) * 0.25;
|
|
630
|
+
const qy = (3 * (rp1[1] + rp2[1]) - rp0[1] - rp3[1]) * 0.25;
|
|
631
|
+
out.push({ p1: cloneVec2(rp0), p2: [qx, qy], p3: cloneVec2(rp3) });
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const m01x = (rp0[0] + rp1[0]) * 0.5, m01y = (rp0[1] + rp1[1]) * 0.5;
|
|
635
|
+
const m12x = (rp1[0] + rp2[0]) * 0.5, m12y = (rp1[1] + rp2[1]) * 0.5;
|
|
636
|
+
const m23x = (rp2[0] + rp3[0]) * 0.5, m23y = (rp2[1] + rp3[1]) * 0.5;
|
|
637
|
+
const m012x = (m01x + m12x) * 0.5, m012y = (m01y + m12y) * 0.5;
|
|
638
|
+
const m123x = (m12x + m23x) * 0.5, m123y = (m12y + m23y) * 0.5;
|
|
639
|
+
const midx = (m012x + m123x) * 0.5, midy = (m012y + m123y) * 0.5;
|
|
640
|
+
recurse(rp0, [m01x, m01y], [m012x, m012y], [midx, midy], depth + 1);
|
|
641
|
+
recurse([midx, midy], [m123x, m123y], [m23x, m23y], rp3, depth + 1);
|
|
642
|
+
};
|
|
643
|
+
recurse(p0, p1, p2, p3, 0);
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function createOutlineContext(loadedFont, fontId, cache = globalOutlineCache) {
|
|
648
|
+
const collector = new GlyphOutlineCollector();
|
|
649
|
+
const drawCallbacks = getSharedDrawCallbackHandler(loadedFont);
|
|
650
|
+
drawCallbacks.createDrawFuncs(loadedFont, collector);
|
|
651
|
+
return {
|
|
652
|
+
loadedFont,
|
|
653
|
+
outlineCache: cache,
|
|
654
|
+
drawCallbacks,
|
|
655
|
+
collector,
|
|
656
|
+
cacheKeyPrefix: `${fontId}__outline`,
|
|
657
|
+
emptyGlyphs: new Set(),
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function getOutlineForGlyph(ctx, glyphId) {
|
|
661
|
+
if (ctx.emptyGlyphs.has(glyphId)) {
|
|
662
|
+
return {
|
|
663
|
+
glyphId,
|
|
664
|
+
textIndex: 0,
|
|
665
|
+
segments: [],
|
|
666
|
+
bounds: { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
const key = `${ctx.cacheKeyPrefix}_${glyphId}`;
|
|
670
|
+
const cached = ctx.outlineCache.get(key);
|
|
671
|
+
if (cached)
|
|
672
|
+
return cached;
|
|
673
|
+
ctx.drawCallbacks.setCollector(ctx.collector);
|
|
674
|
+
ctx.collector.reset();
|
|
675
|
+
ctx.collector.beginGlyph(glyphId, 0);
|
|
676
|
+
ctx.loadedFont.module.exports.hb_font_draw_glyph(ctx.loadedFont.font.ptr, glyphId, ctx.drawCallbacks.getDrawFuncsPtr(), 0);
|
|
677
|
+
ctx.collector.finishGlyph();
|
|
678
|
+
const collected = ctx.collector.getCollectedGlyphs()[0];
|
|
679
|
+
const outline = collected ?? {
|
|
680
|
+
glyphId,
|
|
681
|
+
textIndex: 0,
|
|
682
|
+
segments: [],
|
|
683
|
+
bounds: { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }
|
|
684
|
+
};
|
|
685
|
+
if (outline.segments.length === 0) {
|
|
686
|
+
ctx.emptyGlyphs.add(glyphId);
|
|
687
|
+
}
|
|
688
|
+
ctx.outlineCache.set(key, outline);
|
|
689
|
+
return outline;
|
|
690
|
+
}
|
|
691
|
+
function collectForSlug(ctx, clustersByLine, scale) {
|
|
692
|
+
const seen = new Set();
|
|
693
|
+
const outlinesByGlyph = new Map();
|
|
694
|
+
for (const line of clustersByLine) {
|
|
695
|
+
for (const cluster of line) {
|
|
696
|
+
for (const g of cluster.glyphs) {
|
|
697
|
+
if (!seen.has(g.g)) {
|
|
698
|
+
seen.add(g.g);
|
|
699
|
+
outlinesByGlyph.set(g.g, getOutlineForGlyph(ctx, g.g));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const shapes = [];
|
|
705
|
+
const glyphInfos = [];
|
|
706
|
+
for (const line of clustersByLine) {
|
|
707
|
+
for (const cluster of line) {
|
|
708
|
+
for (const g of cluster.glyphs) {
|
|
709
|
+
const outline = outlinesByGlyph.get(g.g);
|
|
710
|
+
if (!outline || outline.segments.length === 0)
|
|
711
|
+
continue;
|
|
712
|
+
const px = (cluster.position.x + (g.x ?? 0)) * scale;
|
|
713
|
+
const py = (cluster.position.y + (g.y ?? 0)) * scale;
|
|
714
|
+
const curves = [];
|
|
715
|
+
for (const seg of outline.segments) {
|
|
716
|
+
switch (seg.type) {
|
|
717
|
+
// line to quadratic
|
|
718
|
+
case 0: {
|
|
719
|
+
const x0 = seg.p0.x * scale + px, y0 = seg.p0.y * scale + py;
|
|
720
|
+
const x1 = seg.p1.x * scale + px, y1 = seg.p1.y * scale + py;
|
|
721
|
+
curves.push({
|
|
722
|
+
p1: [x0, y0],
|
|
723
|
+
p2: [(x0 + x1) * 0.5, (y0 + y1) * 0.5],
|
|
724
|
+
p3: [x1, y1],
|
|
725
|
+
});
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
// quadratic
|
|
729
|
+
case 1:
|
|
730
|
+
curves.push({
|
|
731
|
+
p1: [seg.p0.x * scale + px, seg.p0.y * scale + py],
|
|
732
|
+
p2: [seg.p1.x * scale + px, seg.p1.y * scale + py],
|
|
733
|
+
p3: [seg.p2.x * scale + px, seg.p2.y * scale + py],
|
|
734
|
+
});
|
|
735
|
+
break;
|
|
736
|
+
// cubic to quadratic
|
|
737
|
+
case 2: {
|
|
738
|
+
const quads = cubicToQuadratics([seg.p0.x, seg.p0.y], [seg.p1.x, seg.p1.y], [seg.p2.x, seg.p2.y], [seg.p3.x, seg.p3.y]);
|
|
739
|
+
for (const q of quads) {
|
|
740
|
+
curves.push({
|
|
741
|
+
p1: [q.p1[0] * scale + px, q.p1[1] * scale + py],
|
|
742
|
+
p2: [q.p2[0] * scale + px, q.p2[1] * scale + py],
|
|
743
|
+
p3: [q.p3[0] * scale + px, q.p3[1] * scale + py],
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const bounds = [
|
|
751
|
+
outline.bounds.min.x * scale + px,
|
|
752
|
+
outline.bounds.min.y * scale + py,
|
|
753
|
+
outline.bounds.max.x * scale + px,
|
|
754
|
+
outline.bounds.max.y * scale + py,
|
|
755
|
+
];
|
|
756
|
+
glyphInfos.push({
|
|
757
|
+
textIndex: g.absoluteTextIndex,
|
|
758
|
+
lineIndex: g.lineIndex,
|
|
759
|
+
vertexStart: 0,
|
|
760
|
+
vertexCount: 0,
|
|
761
|
+
segmentStart: 0,
|
|
762
|
+
segmentCount: outline.segments.length,
|
|
763
|
+
bounds: {
|
|
764
|
+
min: { x: bounds[0], y: bounds[1], z: 0 },
|
|
765
|
+
max: { x: bounds[2], y: bounds[3], z: 0 },
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
shapes.push({ curves, bounds });
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const gpuData = packSlugData(shapes);
|
|
773
|
+
const planeBounds = computePlaneBounds(glyphInfos);
|
|
774
|
+
return { gpuData, glyphs: glyphInfos, planeBounds };
|
|
775
|
+
}
|
|
776
|
+
function computePlaneBounds(glyphInfos) {
|
|
777
|
+
if (glyphInfos.length === 0) {
|
|
778
|
+
return { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } };
|
|
779
|
+
}
|
|
780
|
+
let minX = Infinity, minY = Infinity;
|
|
781
|
+
let maxX = -Infinity, maxY = -Infinity;
|
|
782
|
+
for (const g of glyphInfos) {
|
|
783
|
+
if (g.bounds.min.x < minX)
|
|
784
|
+
minX = g.bounds.min.x;
|
|
785
|
+
if (g.bounds.min.y < minY)
|
|
786
|
+
minY = g.bounds.min.y;
|
|
787
|
+
if (g.bounds.max.x > maxX)
|
|
788
|
+
maxX = g.bounds.max.x;
|
|
789
|
+
if (g.bounds.max.y > maxY)
|
|
790
|
+
maxY = g.bounds.max.y;
|
|
791
|
+
}
|
|
792
|
+
return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
|
|
793
|
+
}
|
|
794
|
+
// Public API
|
|
795
|
+
function buildVectorResult(layoutHandle, ctx, options) {
|
|
796
|
+
const scale = layoutHandle.layoutData.pixelsPerFontUnit;
|
|
797
|
+
let cachedQuery = null;
|
|
798
|
+
const update = async (newOptions) => {
|
|
799
|
+
const mergedOptions = { ...options };
|
|
800
|
+
for (const key in newOptions) {
|
|
801
|
+
const value = newOptions[key];
|
|
802
|
+
if (value !== undefined) {
|
|
803
|
+
mergedOptions[key] = value;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (newOptions.font !== undefined ||
|
|
807
|
+
newOptions.fontVariations !== undefined ||
|
|
808
|
+
newOptions.fontFeatures !== undefined) {
|
|
809
|
+
const newLayout = await layoutHandle.update(mergedOptions);
|
|
810
|
+
const newCtx = createOutlineContext(newLayout.loadedFont, newLayout.fontId, globalOutlineCache);
|
|
811
|
+
layoutHandle = newLayout;
|
|
812
|
+
options = mergedOptions;
|
|
813
|
+
return buildVectorResult(layoutHandle, newCtx, options);
|
|
814
|
+
}
|
|
815
|
+
const newLayout = await layoutHandle.update(mergedOptions);
|
|
816
|
+
layoutHandle = newLayout;
|
|
817
|
+
options = mergedOptions;
|
|
818
|
+
return buildVectorResult(layoutHandle, ctx, options);
|
|
819
|
+
};
|
|
820
|
+
const { gpuData, glyphs, planeBounds } = collectForSlug(ctx, layoutHandle.clustersByLine, scale);
|
|
821
|
+
return {
|
|
822
|
+
gpuData,
|
|
823
|
+
glyphs,
|
|
824
|
+
planeBounds,
|
|
825
|
+
query: (queryOptions) => {
|
|
826
|
+
if (!cachedQuery) {
|
|
827
|
+
cachedQuery = new TextRangeQuery(options.text, glyphs);
|
|
828
|
+
}
|
|
829
|
+
return cachedQuery.execute(queryOptions);
|
|
830
|
+
},
|
|
831
|
+
getLoadedFont: () => layoutHandle.getLoadedFont(),
|
|
832
|
+
measureTextWidth: (text, letterSpacing) => layoutHandle.measureTextWidth(text, letterSpacing),
|
|
833
|
+
update,
|
|
834
|
+
dispose: () => {
|
|
835
|
+
layoutHandle.dispose();
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
class Text {
|
|
840
|
+
static { this.setHarfBuzzPath = Text$1.setHarfBuzzPath; }
|
|
841
|
+
static { this.setHarfBuzzBuffer = Text$1.setHarfBuzzBuffer; }
|
|
842
|
+
static { this.init = Text$1.init; }
|
|
843
|
+
static { this.registerPattern = Text$1.registerPattern; }
|
|
844
|
+
static { this.preloadPatterns = Text$1.preloadPatterns; }
|
|
845
|
+
static { this.setMaxFontCacheMemoryMB = Text$1.setMaxFontCacheMemoryMB; }
|
|
846
|
+
static { this.enableWoff2 = Text$1.enableWoff2; }
|
|
847
|
+
static async create(options) {
|
|
848
|
+
const layoutHandle = await Text$1.create(options);
|
|
849
|
+
const ctx = createOutlineContext(layoutHandle.loadedFont, layoutHandle.fontId, globalOutlineCache);
|
|
850
|
+
return buildVectorResult(layoutHandle, ctx, options);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export { Text };
|