pa_font 0.2.3 → 0.2.5
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/USAGE.md +9 -0
- package/dist/paFont.cjs +659 -255
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +659 -255
- package/dist/paFont.js.map +1 -1
- package/paFont.d.ts +8 -0
- package/package.json +1 -1
package/dist/paFont.js
CHANGED
|
@@ -3,52 +3,127 @@ import { load, parse } from "opentype.js";
|
|
|
3
3
|
function toScreenPoint(point, scale) {
|
|
4
4
|
return [point.x * scale, -point.y * scale];
|
|
5
5
|
}
|
|
6
|
-
function cloneRawPoint(point) {
|
|
7
|
-
return {
|
|
8
|
-
x: point.x,
|
|
9
|
-
y: point.y
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
6
|
function pushUniquePoint(points, point) {
|
|
13
|
-
if (points.length === 0 || !pointsEqual2D(points[points.length - 1], point)) points.push(point);
|
|
14
|
-
}
|
|
15
|
-
function flattenQuadratic(p0, p1, p2, tolerance, out
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
7
|
+
if (points.length === 0 || !pointsEqual2D$1(points[points.length - 1], point)) points.push(point);
|
|
8
|
+
}
|
|
9
|
+
function flattenQuadratic(p0, p1, p2, tolerance, out) {
|
|
10
|
+
let sp = 0;
|
|
11
|
+
const stack = _quadStack;
|
|
12
|
+
stack[sp++] = p0[0];
|
|
13
|
+
stack[sp++] = p0[1];
|
|
14
|
+
stack[sp++] = p1[0];
|
|
15
|
+
stack[sp++] = p1[1];
|
|
16
|
+
stack[sp++] = p2[0];
|
|
17
|
+
stack[sp++] = p2[1];
|
|
18
|
+
stack[sp++] = 0;
|
|
19
|
+
while (sp > 0) {
|
|
20
|
+
const depth = stack[--sp];
|
|
21
|
+
const ax2 = stack[--sp];
|
|
22
|
+
const ay2 = stack[--sp];
|
|
23
|
+
const ax1 = stack[--sp];
|
|
24
|
+
const ay1 = stack[--sp];
|
|
25
|
+
const ax0 = stack[--sp];
|
|
26
|
+
const ay0 = stack[--sp];
|
|
27
|
+
if (depth >= 12 || _quadFlatnessInline(ax0, ay0, ax1, ay1, ax2, ay2) <= tolerance) {
|
|
28
|
+
pushUniquePoint(out, [ax2, ay2]);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const m01x = (ax0 + ax1) * .5, m01y = (ay0 + ay1) * .5;
|
|
32
|
+
const m12x = (ax1 + ax2) * .5, m12y = (ay1 + ay2) * .5;
|
|
33
|
+
const mx = (m01x + m12x) * .5, my = (m01y + m12y) * .5;
|
|
34
|
+
const d1 = depth + 1;
|
|
35
|
+
stack[sp++] = mx;
|
|
36
|
+
stack[sp++] = my;
|
|
37
|
+
stack[sp++] = m12x;
|
|
38
|
+
stack[sp++] = m12y;
|
|
39
|
+
stack[sp++] = ax2;
|
|
40
|
+
stack[sp++] = ay2;
|
|
41
|
+
stack[sp++] = d1;
|
|
42
|
+
stack[sp++] = ax0;
|
|
43
|
+
stack[sp++] = ay0;
|
|
44
|
+
stack[sp++] = m01x;
|
|
45
|
+
stack[sp++] = m01y;
|
|
46
|
+
stack[sp++] = mx;
|
|
47
|
+
stack[sp++] = my;
|
|
48
|
+
stack[sp++] = d1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function flattenCubic(p0, p1, p2, p3, tolerance, out) {
|
|
52
|
+
let sp = 0;
|
|
53
|
+
const stack = _cubicStack;
|
|
54
|
+
stack[sp++] = p0[0];
|
|
55
|
+
stack[sp++] = p0[1];
|
|
56
|
+
stack[sp++] = p1[0];
|
|
57
|
+
stack[sp++] = p1[1];
|
|
58
|
+
stack[sp++] = p2[0];
|
|
59
|
+
stack[sp++] = p2[1];
|
|
60
|
+
stack[sp++] = p3[0];
|
|
61
|
+
stack[sp++] = p3[1];
|
|
62
|
+
stack[sp++] = 0;
|
|
63
|
+
while (sp > 0) {
|
|
64
|
+
const depth = stack[--sp];
|
|
65
|
+
const bx3 = stack[--sp];
|
|
66
|
+
const by3 = stack[--sp];
|
|
67
|
+
const bx2 = stack[--sp];
|
|
68
|
+
const by2 = stack[--sp];
|
|
69
|
+
const bx1 = stack[--sp];
|
|
70
|
+
const by1 = stack[--sp];
|
|
71
|
+
const bx0 = stack[--sp];
|
|
72
|
+
const by0 = stack[--sp];
|
|
73
|
+
if (depth >= 12 || _cubicFlatnessInline(bx0, by0, bx1, by1, bx2, by2, bx3, by3) <= tolerance) {
|
|
74
|
+
pushUniquePoint(out, [bx3, by3]);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const m01x = (bx0 + bx1) * .5, m01y = (by0 + by1) * .5;
|
|
78
|
+
const m12x = (bx1 + bx2) * .5, m12y = (by1 + by2) * .5;
|
|
79
|
+
const m23x = (bx2 + bx3) * .5, m23y = (by2 + by3) * .5;
|
|
80
|
+
const m012x = (m01x + m12x) * .5, m012y = (m01y + m12y) * .5;
|
|
81
|
+
const m123x = (m12x + m23x) * .5, m123y = (m12y + m23y) * .5;
|
|
82
|
+
const mx = (m012x + m123x) * .5, my = (m012y + m123y) * .5;
|
|
83
|
+
const d1 = depth + 1;
|
|
84
|
+
stack[sp++] = mx;
|
|
85
|
+
stack[sp++] = my;
|
|
86
|
+
stack[sp++] = m123x;
|
|
87
|
+
stack[sp++] = m123y;
|
|
88
|
+
stack[sp++] = m23x;
|
|
89
|
+
stack[sp++] = m23y;
|
|
90
|
+
stack[sp++] = bx3;
|
|
91
|
+
stack[sp++] = by3;
|
|
92
|
+
stack[sp++] = d1;
|
|
93
|
+
stack[sp++] = bx0;
|
|
94
|
+
stack[sp++] = by0;
|
|
95
|
+
stack[sp++] = m01x;
|
|
96
|
+
stack[sp++] = m01y;
|
|
97
|
+
stack[sp++] = m012x;
|
|
98
|
+
stack[sp++] = m012y;
|
|
99
|
+
stack[sp++] = mx;
|
|
100
|
+
stack[sp++] = my;
|
|
101
|
+
stack[sp++] = d1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
var _quadStack = new Float64Array(896);
|
|
105
|
+
var _cubicStack = new Float64Array(1152);
|
|
106
|
+
function _quadFlatnessInline(x0, y0, x1, y1, x2, y2) {
|
|
107
|
+
const dx = x2 - x0, dy = y2 - y0;
|
|
108
|
+
const lenSq = dx * dx + dy * dy;
|
|
109
|
+
if (lenSq === 0) {
|
|
110
|
+
const ex = x1 - x0, ey = y1 - y0;
|
|
111
|
+
return Math.sqrt(ex * ex + ey * ey);
|
|
112
|
+
}
|
|
113
|
+
return Math.abs(dx * (y0 - y1) - (x0 - x1) * dy) / Math.sqrt(lenSq);
|
|
114
|
+
}
|
|
115
|
+
function _cubicFlatnessInline(x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
116
|
+
const dx = x3 - x0, dy = y3 - y0;
|
|
117
|
+
const lenSq = dx * dx + dy * dy;
|
|
118
|
+
if (lenSq === 0) {
|
|
119
|
+
const e1x = x1 - x0, e1y = y1 - y0;
|
|
120
|
+
const e2x = x2 - x0, e2y = y2 - y0;
|
|
121
|
+
return Math.max(Math.sqrt(e1x * e1x + e1y * e1y), Math.sqrt(e2x * e2x + e2y * e2y));
|
|
122
|
+
}
|
|
123
|
+
const invLen = 1 / Math.sqrt(lenSq);
|
|
124
|
+
const a1 = Math.abs(dx * (y0 - y1) - (x0 - x1) * dy);
|
|
125
|
+
const a2 = Math.abs(dx * (y0 - y2) - (x0 - x2) * dy);
|
|
126
|
+
return Math.max(a1, a2) * invLen;
|
|
52
127
|
}
|
|
53
128
|
function midpoint(a, b) {
|
|
54
129
|
return [(a[0] + b[0]) * .5, (a[1] + b[1]) * .5];
|
|
@@ -106,28 +181,37 @@ function distance(a, b) {
|
|
|
106
181
|
return Math.sqrt(dx * dx + dy * dy);
|
|
107
182
|
}
|
|
108
183
|
function sampleRing(ring, step, callback) {
|
|
109
|
-
|
|
184
|
+
const len = ring.length;
|
|
185
|
+
for (let index = 0; index < len; index++) {
|
|
110
186
|
const a = ring[index];
|
|
111
|
-
const b = ring[(index + 1) %
|
|
112
|
-
const
|
|
187
|
+
const b = ring[(index + 1) % len];
|
|
188
|
+
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
189
|
+
const segmentLength = Math.sqrt(dx * dx + dy * dy);
|
|
113
190
|
const divisions = Math.max(1, Math.ceil(segmentLength / step));
|
|
114
|
-
|
|
191
|
+
if (index === 0) {
|
|
192
|
+
callback([a[0], a[1]]);
|
|
193
|
+
for (let offset = 1; offset < divisions; offset++) {
|
|
194
|
+
const t = offset / divisions;
|
|
195
|
+
callback([a[0] + dx * t, a[1] + dy * t]);
|
|
196
|
+
}
|
|
197
|
+
} else for (let offset = 0; offset < divisions; offset++) {
|
|
115
198
|
const t = offset / divisions;
|
|
116
|
-
callback([a[0] +
|
|
117
|
-
}
|
|
199
|
+
callback([a[0] + dx * t, a[1] + dy * t]);
|
|
200
|
+
}
|
|
118
201
|
}
|
|
119
202
|
}
|
|
120
203
|
function ringBounds(ring) {
|
|
121
|
-
let minX =
|
|
122
|
-
let minY =
|
|
123
|
-
let maxX =
|
|
124
|
-
let maxY =
|
|
125
|
-
ring.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
204
|
+
let minX = ring[0][0];
|
|
205
|
+
let minY = ring[0][1];
|
|
206
|
+
let maxX = minX;
|
|
207
|
+
let maxY = minY;
|
|
208
|
+
for (let i = 1, len = ring.length; i < len; i++) {
|
|
209
|
+
const x = ring[i][0], y = ring[i][1];
|
|
210
|
+
if (x < minX) minX = x;
|
|
211
|
+
else if (x > maxX) maxX = x;
|
|
212
|
+
if (y < minY) minY = y;
|
|
213
|
+
else if (y > maxY) maxY = y;
|
|
214
|
+
}
|
|
131
215
|
return {
|
|
132
216
|
x: minX,
|
|
133
217
|
y: minY,
|
|
@@ -136,7 +220,10 @@ function ringBounds(ring) {
|
|
|
136
220
|
};
|
|
137
221
|
}
|
|
138
222
|
function translateRing(ring, tx, ty) {
|
|
139
|
-
|
|
223
|
+
const len = ring.length;
|
|
224
|
+
const result = new Array(len);
|
|
225
|
+
for (let i = 0; i < len; i++) result[i] = [ring[i][0] + tx, ring[i][1] + ty];
|
|
226
|
+
return result;
|
|
140
227
|
}
|
|
141
228
|
function translateRect(rect, tx, ty) {
|
|
142
229
|
return {
|
|
@@ -146,21 +233,29 @@ function translateRect(rect, tx, ty) {
|
|
|
146
233
|
h: rect.h
|
|
147
234
|
};
|
|
148
235
|
}
|
|
149
|
-
function copyRing(ring) {
|
|
150
|
-
|
|
236
|
+
function copyRing$1(ring) {
|
|
237
|
+
const len = ring.length;
|
|
238
|
+
const result = new Array(len);
|
|
239
|
+
for (let i = 0; i < len; i++) result[i] = [ring[i][0], ring[i][1]];
|
|
240
|
+
return result;
|
|
151
241
|
}
|
|
152
242
|
function combineRects(rects) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let
|
|
157
|
-
let
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
243
|
+
const len = rects.length;
|
|
244
|
+
if (len === 0) return null;
|
|
245
|
+
const first = rects[0];
|
|
246
|
+
let minX = first.x;
|
|
247
|
+
let minY = first.y;
|
|
248
|
+
let maxX = first.x + first.w;
|
|
249
|
+
let maxY = first.y + first.h;
|
|
250
|
+
for (let i = 1; i < len; i++) {
|
|
251
|
+
const r = rects[i];
|
|
252
|
+
const rx = r.x, ry = r.y;
|
|
253
|
+
const rx2 = rx + r.w, ry2 = ry + r.h;
|
|
254
|
+
if (rx < minX) minX = rx;
|
|
255
|
+
if (ry < minY) minY = ry;
|
|
256
|
+
if (rx2 > maxX) maxX = rx2;
|
|
257
|
+
if (ry2 > maxY) maxY = ry2;
|
|
258
|
+
}
|
|
164
259
|
return {
|
|
165
260
|
x: minX,
|
|
166
261
|
y: minY,
|
|
@@ -191,7 +286,7 @@ function normalizePositive(value, fallback) {
|
|
|
191
286
|
function samePoint(a, b) {
|
|
192
287
|
return a.x === b.x && a.y === b.y;
|
|
193
288
|
}
|
|
194
|
-
function pointsEqual2D(a, b) {
|
|
289
|
+
function pointsEqual2D$1(a, b) {
|
|
195
290
|
return a[0] === b[0] && a[1] === b[1];
|
|
196
291
|
}
|
|
197
292
|
function pointsAlmostEqual2D(a, b, epsilon = 1e-6) {
|
|
@@ -202,43 +297,64 @@ function ringPerimeter(ring) {
|
|
|
202
297
|
for (let index = 0; index < ring.length; index += 1) total += distance(ring[index], ring[(index + 1) % ring.length]);
|
|
203
298
|
return total;
|
|
204
299
|
}
|
|
205
|
-
function
|
|
300
|
+
function buildCumulativeDistances(ring) {
|
|
301
|
+
const len = ring.length;
|
|
302
|
+
const cumulative = new Float64Array(len + 1);
|
|
206
303
|
let total = 0;
|
|
207
|
-
for (let
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
304
|
+
for (let i = 0; i < len; i++) {
|
|
305
|
+
cumulative[i] = total;
|
|
306
|
+
const next = (i + 1) % len;
|
|
307
|
+
const dx = ring[next][0] - ring[i][0];
|
|
308
|
+
const dy = ring[next][1] - ring[i][1];
|
|
309
|
+
total += Math.sqrt(dx * dx + dy * dy);
|
|
310
|
+
}
|
|
311
|
+
cumulative[len] = total;
|
|
312
|
+
return cumulative;
|
|
313
|
+
}
|
|
314
|
+
function buildOpenCumulativeDistances(path) {
|
|
315
|
+
const len = path.length;
|
|
316
|
+
const cumulative = new Float64Array(len);
|
|
317
|
+
let total = 0;
|
|
318
|
+
cumulative[0] = 0;
|
|
319
|
+
for (let i = 1; i < len; i++) {
|
|
320
|
+
const dx = path[i][0] - path[i - 1][0];
|
|
321
|
+
const dy = path[i][1] - path[i - 1][1];
|
|
322
|
+
total += Math.sqrt(dx * dx + dy * dy);
|
|
323
|
+
cumulative[i] = total;
|
|
324
|
+
}
|
|
325
|
+
return cumulative;
|
|
326
|
+
}
|
|
327
|
+
function binarySearchSegment(cumulative, d) {
|
|
328
|
+
let lo = 0, hi = cumulative.length - 2;
|
|
329
|
+
while (lo < hi) {
|
|
330
|
+
const mid = lo + hi + 1 >>> 1;
|
|
331
|
+
if (cumulative[mid] <= d) lo = mid;
|
|
332
|
+
else hi = mid - 1;
|
|
333
|
+
}
|
|
334
|
+
return lo;
|
|
335
|
+
}
|
|
336
|
+
function pointAtClosedPathDistanceFast(ring, cumulative, distanceAlong) {
|
|
337
|
+
const perimeter = cumulative[ring.length];
|
|
212
338
|
if (perimeter <= 1e-9) return [ring[0][0], ring[0][1]];
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
remaining -= segmentLength;
|
|
224
|
-
}
|
|
225
|
-
return [ring[ring.length - 1][0], ring[ring.length - 1][1]];
|
|
226
|
-
}
|
|
227
|
-
function pointAtOpenPathDistance(path, distanceAlong) {
|
|
339
|
+
const d = mod(distanceAlong, perimeter);
|
|
340
|
+
const idx = binarySearchSegment(cumulative, d);
|
|
341
|
+
const segLen = cumulative[idx + 1] - cumulative[idx];
|
|
342
|
+
if (segLen <= 1e-9) return [ring[idx][0], ring[idx][1]];
|
|
343
|
+
const t = (d - cumulative[idx]) / segLen;
|
|
344
|
+
const start = ring[idx];
|
|
345
|
+
const end = ring[(idx + 1) % ring.length];
|
|
346
|
+
return [start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t];
|
|
347
|
+
}
|
|
348
|
+
function pointAtOpenPathDistanceFast(path, cumulative, distanceAlong) {
|
|
228
349
|
if (distanceAlong <= 0) return [path[0][0], path[0][1]];
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return [start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t];
|
|
238
|
-
}
|
|
239
|
-
remaining -= segmentLength;
|
|
240
|
-
}
|
|
241
|
-
return [path[path.length - 1][0], path[path.length - 1][1]];
|
|
350
|
+
if (distanceAlong >= cumulative[path.length - 1]) return [path[path.length - 1][0], path[path.length - 1][1]];
|
|
351
|
+
const idx = binarySearchSegment(cumulative, distanceAlong);
|
|
352
|
+
const segLen = cumulative[idx + 1] - cumulative[idx];
|
|
353
|
+
if (segLen <= 1e-9) return [path[idx][0], path[idx][1]];
|
|
354
|
+
const t = (distanceAlong - cumulative[idx]) / segLen;
|
|
355
|
+
const start = path[idx];
|
|
356
|
+
const end = path[idx + 1];
|
|
357
|
+
return [start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t];
|
|
242
358
|
}
|
|
243
359
|
function segmentIntersectsRing(a, b, ring, ignoredEdges, epsilon) {
|
|
244
360
|
for (let index = 0; index < ring.length; index += 1) {
|
|
@@ -320,23 +436,28 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
320
436
|
current = null;
|
|
321
437
|
return;
|
|
322
438
|
}
|
|
323
|
-
if (!samePoint(current.cursor, current.start)) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
439
|
+
if (!samePoint(current.cursor, current.start)) current.segments.push({
|
|
440
|
+
type: "L",
|
|
441
|
+
from: {
|
|
442
|
+
x: current.cursor.x,
|
|
443
|
+
y: current.cursor.y
|
|
444
|
+
},
|
|
445
|
+
to: {
|
|
446
|
+
x: current.start.x,
|
|
447
|
+
y: current.start.y
|
|
448
|
+
}
|
|
449
|
+
});
|
|
331
450
|
contours.push({
|
|
332
451
|
id: contourId++,
|
|
333
|
-
start:
|
|
452
|
+
start: current.start,
|
|
334
453
|
segments: current.segments
|
|
335
454
|
});
|
|
336
455
|
current = null;
|
|
337
456
|
};
|
|
338
|
-
commands.
|
|
339
|
-
|
|
457
|
+
for (let i = 0, len = commands.length; i < len; i++) {
|
|
458
|
+
const cmd = commands[i];
|
|
459
|
+
const type = cmd.type;
|
|
460
|
+
if (type === "M") {
|
|
340
461
|
finishCurrentContour();
|
|
341
462
|
current = {
|
|
342
463
|
start: {
|
|
@@ -349,47 +470,49 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
349
470
|
},
|
|
350
471
|
segments: []
|
|
351
472
|
};
|
|
352
|
-
|
|
473
|
+
continue;
|
|
353
474
|
}
|
|
354
|
-
if (!current)
|
|
355
|
-
if (
|
|
475
|
+
if (!current) continue;
|
|
476
|
+
if (type === "L") {
|
|
356
477
|
const to = {
|
|
357
478
|
x: cmd.x,
|
|
358
479
|
y: cmd.y
|
|
359
480
|
};
|
|
360
481
|
current.segments.push({
|
|
361
482
|
type: "L",
|
|
362
|
-
from:
|
|
483
|
+
from: current.cursor,
|
|
363
484
|
to
|
|
364
485
|
});
|
|
365
|
-
current.cursor =
|
|
366
|
-
|
|
486
|
+
current.cursor = to;
|
|
487
|
+
continue;
|
|
367
488
|
}
|
|
368
|
-
if (
|
|
489
|
+
if (type === "Q") {
|
|
490
|
+
const from = current.cursor;
|
|
369
491
|
const to = {
|
|
370
492
|
x: cmd.x,
|
|
371
493
|
y: cmd.y
|
|
372
494
|
};
|
|
373
495
|
current.segments.push({
|
|
374
496
|
type: "Q",
|
|
375
|
-
from
|
|
497
|
+
from,
|
|
376
498
|
c1: {
|
|
377
499
|
x: cmd.x1,
|
|
378
500
|
y: cmd.y1
|
|
379
501
|
},
|
|
380
502
|
to
|
|
381
503
|
});
|
|
382
|
-
current.cursor =
|
|
383
|
-
|
|
504
|
+
current.cursor = to;
|
|
505
|
+
continue;
|
|
384
506
|
}
|
|
385
|
-
if (
|
|
507
|
+
if (type === "C") {
|
|
508
|
+
const from = current.cursor;
|
|
386
509
|
const to = {
|
|
387
510
|
x: cmd.x,
|
|
388
511
|
y: cmd.y
|
|
389
512
|
};
|
|
390
513
|
current.segments.push({
|
|
391
514
|
type: "C",
|
|
392
|
-
from
|
|
515
|
+
from,
|
|
393
516
|
c1: {
|
|
394
517
|
x: cmd.x1,
|
|
395
518
|
y: cmd.y1
|
|
@@ -400,11 +523,11 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
400
523
|
},
|
|
401
524
|
to
|
|
402
525
|
});
|
|
403
|
-
current.cursor =
|
|
404
|
-
|
|
526
|
+
current.cursor = to;
|
|
527
|
+
continue;
|
|
405
528
|
}
|
|
406
|
-
if (
|
|
407
|
-
}
|
|
529
|
+
if (type === "Z") finishCurrentContour();
|
|
530
|
+
}
|
|
408
531
|
finishCurrentContour();
|
|
409
532
|
return {
|
|
410
533
|
glyphIndex: glyph.index,
|
|
@@ -428,18 +551,25 @@ function flattenGlyphTopology(topology, options) {
|
|
|
428
551
|
}
|
|
429
552
|
function flattenContour(contour, scale, tolerance) {
|
|
430
553
|
const ring = [];
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
554
|
+
let cursor = toScreenPoint(contour.start, scale);
|
|
555
|
+
pushUniquePoint(ring, cursor);
|
|
556
|
+
const segments = contour.segments;
|
|
557
|
+
for (let i = 0, len = segments.length; i < len; i++) {
|
|
558
|
+
const segment = segments[i];
|
|
559
|
+
const type = segment.type;
|
|
560
|
+
if (type === "L") {
|
|
561
|
+
cursor = toScreenPoint(segment.to, scale);
|
|
562
|
+
pushUniquePoint(ring, cursor);
|
|
563
|
+
} else if (type === "Q") {
|
|
564
|
+
const to = toScreenPoint(segment.to, scale);
|
|
565
|
+
flattenQuadratic(cursor, toScreenPoint(segment.c1, scale), to, tolerance, ring);
|
|
566
|
+
cursor = to;
|
|
567
|
+
} else if (type === "C") {
|
|
568
|
+
const to = toScreenPoint(segment.to, scale);
|
|
569
|
+
flattenCubic(cursor, toScreenPoint(segment.c1, scale), toScreenPoint(segment.c2, scale), to, tolerance, ring);
|
|
570
|
+
cursor = to;
|
|
436
571
|
}
|
|
437
|
-
|
|
438
|
-
flattenQuadratic(toScreenPoint(segment.from, scale), toScreenPoint(segment.c1, scale), toScreenPoint(segment.to, scale), tolerance, ring);
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (segment.type === "C") flattenCubic(toScreenPoint(segment.from, scale), toScreenPoint(segment.c1, scale), toScreenPoint(segment.c2, scale), toScreenPoint(segment.to, scale), tolerance, ring);
|
|
442
|
-
});
|
|
572
|
+
}
|
|
443
573
|
if (ring.length > 1 && pointsEqual2D(ring[0], ring[ring.length - 1])) ring.pop();
|
|
444
574
|
if (ring.length < 3) return null;
|
|
445
575
|
const bbox = ringBounds(ring);
|
|
@@ -452,37 +582,45 @@ function flattenContour(contour, scale, tolerance) {
|
|
|
452
582
|
};
|
|
453
583
|
}
|
|
454
584
|
function classifyContours(contours) {
|
|
455
|
-
const
|
|
456
|
-
|
|
585
|
+
const len = contours.length;
|
|
586
|
+
const result = new Array(len);
|
|
587
|
+
for (let i = 0; i < len; i++) result[i] = {
|
|
588
|
+
...contours[i],
|
|
457
589
|
parentId: null,
|
|
458
590
|
depth: 0,
|
|
459
591
|
role: "outer"
|
|
460
|
-
}
|
|
461
|
-
result.
|
|
592
|
+
};
|
|
593
|
+
const sortedByArea = result.slice().sort((a, b) => Math.abs(b.area) - Math.abs(a.area));
|
|
594
|
+
for (let ci = 0; ci < len; ci++) {
|
|
595
|
+
const child = result[ci];
|
|
596
|
+
const childAbsArea = Math.abs(child.area);
|
|
462
597
|
let parent = null;
|
|
463
598
|
let parentArea = Number.POSITIVE_INFINITY;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
if (
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
599
|
+
for (let si = 0; si < len; si++) {
|
|
600
|
+
const candidate = sortedByArea[si];
|
|
601
|
+
const candidateAbsArea = Math.abs(candidate.area);
|
|
602
|
+
if (candidateAbsArea <= childAbsArea) break;
|
|
603
|
+
if (candidate.id === child.id) continue;
|
|
604
|
+
if (candidateAbsArea >= parentArea) continue;
|
|
605
|
+
if (!rectContainsRect(candidate.bbox, child.bbox)) continue;
|
|
606
|
+
if (!pointInRing(child.ring[0], candidate.ring)) continue;
|
|
607
|
+
parent = candidate;
|
|
608
|
+
parentArea = candidateAbsArea;
|
|
609
|
+
}
|
|
475
610
|
if (parent) child.parentId = parent.id;
|
|
476
|
-
}
|
|
611
|
+
}
|
|
477
612
|
const byId = new Map(result.map((contour) => [contour.id, contour]));
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
613
|
+
for (let i = 0; i < len; i++) {
|
|
614
|
+
const contour = result[i];
|
|
615
|
+
let depth = 0;
|
|
616
|
+
let current = contour;
|
|
617
|
+
while (current.parentId != null) {
|
|
618
|
+
depth++;
|
|
619
|
+
current = byId.get(current.parentId);
|
|
620
|
+
}
|
|
621
|
+
contour.depth = depth;
|
|
622
|
+
contour.role = depth % 2 === 0 ? "outer" : "hole";
|
|
623
|
+
}
|
|
486
624
|
return result;
|
|
487
625
|
}
|
|
488
626
|
function buildParts(contours) {
|
|
@@ -497,16 +635,26 @@ function buildParts(contours) {
|
|
|
497
635
|
});
|
|
498
636
|
}
|
|
499
637
|
function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
|
|
500
|
-
|
|
501
|
-
|
|
638
|
+
const srcParts = geometry.parts;
|
|
639
|
+
const srcContours = geometry.contours;
|
|
640
|
+
const partsLen = srcParts.length;
|
|
641
|
+
const contoursLen = srcContours.length;
|
|
642
|
+
const parts = new Array(partsLen);
|
|
643
|
+
const contours = new Array(contoursLen);
|
|
644
|
+
for (let i = 0; i < partsLen; i++) {
|
|
645
|
+
const part = srcParts[i];
|
|
646
|
+
parts[i] = {
|
|
502
647
|
outer: translateRing(part.outer, tx, ty),
|
|
503
648
|
holes: part.holes.map((hole) => translateRing(hole, tx, ty)),
|
|
504
649
|
bbox: translateRect(part.bbox, tx, ty),
|
|
505
650
|
area: part.area,
|
|
506
651
|
glyphPosition,
|
|
507
|
-
partIndex
|
|
508
|
-
}
|
|
509
|
-
|
|
652
|
+
partIndex: i
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
for (let i = 0; i < contoursLen; i++) {
|
|
656
|
+
const contour = srcContours[i];
|
|
657
|
+
contours[i] = {
|
|
510
658
|
id: `${glyphPosition}:${contour.id}`,
|
|
511
659
|
sourceId: contour.id,
|
|
512
660
|
glyphPosition,
|
|
@@ -516,7 +664,11 @@ function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
|
|
|
516
664
|
area: contour.area,
|
|
517
665
|
bbox: translateRect(contour.bbox, tx, ty),
|
|
518
666
|
ring: translateRing(contour.ring, tx, ty)
|
|
519
|
-
}
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
parts,
|
|
671
|
+
contours,
|
|
520
672
|
bbox: translateRect(geometry.bbox, tx, ty)
|
|
521
673
|
};
|
|
522
674
|
}
|
|
@@ -556,13 +708,14 @@ function toArrayBuffer(value) {
|
|
|
556
708
|
//#endregion
|
|
557
709
|
//#region src/paFont/shape.js
|
|
558
710
|
var PAShape = class {
|
|
559
|
-
constructor({ text, parts, contours, glyphs, bbox, metrics, edgeEpsilon }) {
|
|
711
|
+
constructor({ text, parts, contours, glyphs, bbox, metrics, edgeEpsilon, rawData }) {
|
|
560
712
|
this.text = text;
|
|
561
713
|
this.parts = parts;
|
|
562
714
|
this.bbox = bbox;
|
|
563
715
|
this.metrics = metrics;
|
|
564
716
|
this.edgeEpsilon = edgeEpsilon;
|
|
565
717
|
this.raw = {
|
|
718
|
+
...rawData ?? {},
|
|
566
719
|
contours,
|
|
567
720
|
glyphs
|
|
568
721
|
};
|
|
@@ -595,12 +748,23 @@ var PAShape = class {
|
|
|
595
748
|
toRegions(options = {}) {
|
|
596
749
|
return createRegionCollection(this.toShape(options));
|
|
597
750
|
}
|
|
751
|
+
toRegionViews(options = {}) {
|
|
752
|
+
return this.toShape(options)._getRegionViews();
|
|
753
|
+
}
|
|
598
754
|
openHoles(width) {
|
|
599
755
|
const slitWidth = normalizePositive(width, 0);
|
|
600
756
|
if (slitWidth <= 0 || !this.parts.some((part) => part.holes.length > 0)) return this;
|
|
601
757
|
const cacheKey = toCacheKey(slitWidth);
|
|
602
758
|
const cached = this._cache.openHoles.get(cacheKey);
|
|
603
759
|
if (cached) return cached;
|
|
760
|
+
const glyphVariant = createGlyphDerivedShape(this, {
|
|
761
|
+
openWidth: slitWidth,
|
|
762
|
+
step: 0
|
|
763
|
+
});
|
|
764
|
+
if (glyphVariant) {
|
|
765
|
+
this._cache.openHoles.set(cacheKey, glyphVariant);
|
|
766
|
+
return glyphVariant;
|
|
767
|
+
}
|
|
604
768
|
const geometryEpsilon = resolveGeometryEpsilon(slitWidth);
|
|
605
769
|
const shape = createDerivedShape(this, this.parts.map((part) => openPartWithSlit(part, slitWidth, geometryEpsilon)));
|
|
606
770
|
this._cache.openHoles.set(cacheKey, shape);
|
|
@@ -612,6 +776,14 @@ var PAShape = class {
|
|
|
612
776
|
const cacheKey = toCacheKey(spacing);
|
|
613
777
|
const cached = this._cache.resample.get(cacheKey);
|
|
614
778
|
if (cached) return cached;
|
|
779
|
+
const glyphVariant = createGlyphDerivedShape(this, {
|
|
780
|
+
openWidth: 0,
|
|
781
|
+
step: spacing
|
|
782
|
+
});
|
|
783
|
+
if (glyphVariant) {
|
|
784
|
+
this._cache.resample.set(cacheKey, glyphVariant);
|
|
785
|
+
return glyphVariant;
|
|
786
|
+
}
|
|
615
787
|
const shape = createDerivedShape(this, this.parts.map((part) => resamplePart(part, spacing)));
|
|
616
788
|
this._cache.resample.set(cacheKey, shape);
|
|
617
789
|
return shape;
|
|
@@ -672,21 +844,31 @@ var PAShape = class {
|
|
|
672
844
|
function createTextShape(layout, opts, fontInstance) {
|
|
673
845
|
const parts = [];
|
|
674
846
|
const contours = [];
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
847
|
+
const layoutGlyphs = layout.glyphs;
|
|
848
|
+
const glyphCount = layoutGlyphs.length;
|
|
849
|
+
const glyphs = new Array(glyphCount);
|
|
850
|
+
const sourceLayoutGlyphs = Array.isArray(layoutGlyphs) ? layoutGlyphs.slice() : [];
|
|
851
|
+
const variantOptions = {
|
|
852
|
+
openWidth: normalizePositive(opts.openWidth, 0),
|
|
853
|
+
step: normalizePositive(opts.step, 0)
|
|
854
|
+
};
|
|
855
|
+
for (let glyphPosition = 0; glyphPosition < glyphCount; glyphPosition++) {
|
|
856
|
+
const item = layoutGlyphs[glyphPosition];
|
|
857
|
+
const translated = translateGlyphGeometry(fontInstance._getGlyphGeometryVariant(item.glyph, opts), item.x, item.y, glyphPosition);
|
|
858
|
+
const tParts = translated.parts;
|
|
859
|
+
const tContours = translated.contours;
|
|
860
|
+
for (let j = 0; j < tParts.length; j++) parts.push(tParts[j]);
|
|
861
|
+
for (let j = 0; j < tContours.length; j++) contours.push(tContours[j]);
|
|
862
|
+
glyphs[glyphPosition] = {
|
|
681
863
|
position: glyphPosition,
|
|
682
864
|
glyphIndex: item.glyph.index,
|
|
683
865
|
x: item.x,
|
|
684
866
|
y: item.y,
|
|
685
867
|
size: item.size,
|
|
686
868
|
bbox: translated.bbox,
|
|
687
|
-
partCount:
|
|
688
|
-
}
|
|
689
|
-
}
|
|
869
|
+
partCount: tParts.length
|
|
870
|
+
};
|
|
871
|
+
}
|
|
690
872
|
const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
|
|
691
873
|
return new PAShape({
|
|
692
874
|
text: layout.text,
|
|
@@ -698,11 +880,21 @@ function createTextShape(layout, opts, fontInstance) {
|
|
|
698
880
|
...layout.metrics,
|
|
699
881
|
bbox
|
|
700
882
|
},
|
|
701
|
-
edgeEpsilon: opts.edgeEpsilon
|
|
883
|
+
edgeEpsilon: opts.edgeEpsilon,
|
|
884
|
+
rawData: {
|
|
885
|
+
fontInstance,
|
|
886
|
+
sourceLayout: {
|
|
887
|
+
glyphs: sourceLayoutGlyphs,
|
|
888
|
+
metrics: { ...layout.metrics }
|
|
889
|
+
},
|
|
890
|
+
sourceOptions: extractShapeSourceOptions(opts),
|
|
891
|
+
variantOptions
|
|
892
|
+
}
|
|
702
893
|
});
|
|
703
894
|
}
|
|
704
895
|
function createGlyphShape(shape, glyphPosition) {
|
|
705
896
|
const glyphMeta = shape.raw.glyphs?.[glyphPosition] ?? null;
|
|
897
|
+
const sourceGlyph = shape.raw.sourceLayout?.glyphs?.[glyphPosition] ?? null;
|
|
706
898
|
const parts = copyParts(shape.parts.filter((part) => part.glyphPosition === glyphPosition));
|
|
707
899
|
const contours = copyContours((shape.raw.contours ?? []).filter((contour) => contour.glyphPosition === glyphPosition));
|
|
708
900
|
const bbox = combineRects(parts.map((part) => part.bbox)) ?? (glyphMeta?.bbox ? { ...glyphMeta.bbox } : emptyRect());
|
|
@@ -723,14 +915,28 @@ function createGlyphShape(shape, glyphPosition) {
|
|
|
723
915
|
width: bbox.w,
|
|
724
916
|
bbox
|
|
725
917
|
},
|
|
726
|
-
edgeEpsilon: shape.edgeEpsilon
|
|
918
|
+
edgeEpsilon: shape.edgeEpsilon,
|
|
919
|
+
rawData: sourceGlyph && shape.raw.fontInstance ? {
|
|
920
|
+
fontInstance: shape.raw.fontInstance,
|
|
921
|
+
sourceLayout: {
|
|
922
|
+
glyphs: [sourceGlyph],
|
|
923
|
+
metrics: {
|
|
924
|
+
x: glyphMeta?.x ?? shape.metrics?.x ?? 0,
|
|
925
|
+
y: glyphMeta?.y ?? shape.metrics?.y ?? 0,
|
|
926
|
+
size: glyphMeta?.size ?? shape.metrics?.size ?? 0,
|
|
927
|
+
width: glyphMeta?.bbox?.w ?? bbox.w
|
|
928
|
+
}
|
|
929
|
+
},
|
|
930
|
+
sourceOptions: shape.raw.sourceOptions ?? null,
|
|
931
|
+
variantOptions: { ...shape.raw.variantOptions ?? zeroShapeVariant() }
|
|
932
|
+
} : void 0
|
|
727
933
|
});
|
|
728
934
|
}
|
|
729
935
|
function createDerivedShape(shape, parts) {
|
|
730
|
-
const
|
|
731
|
-
const
|
|
936
|
+
const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
|
|
937
|
+
const partsByGlyphPosition = groupPartsByGlyphPosition(parts);
|
|
732
938
|
const glyphs = (shape.raw.glyphs ?? []).map((glyph, glyphPosition) => {
|
|
733
|
-
const glyphParts =
|
|
939
|
+
const glyphParts = partsByGlyphPosition.get(glyphPosition) ?? [];
|
|
734
940
|
return {
|
|
735
941
|
...glyph,
|
|
736
942
|
bbox: combineRects(glyphParts.map((part) => part.bbox)) ?? (glyph.bbox ? { ...glyph.bbox } : emptyRect()),
|
|
@@ -739,7 +945,7 @@ function createDerivedShape(shape, parts) {
|
|
|
739
945
|
});
|
|
740
946
|
return new PAShape({
|
|
741
947
|
text: shape.text,
|
|
742
|
-
parts
|
|
948
|
+
parts,
|
|
743
949
|
contours: [],
|
|
744
950
|
glyphs,
|
|
745
951
|
bbox,
|
|
@@ -747,13 +953,17 @@ function createDerivedShape(shape, parts) {
|
|
|
747
953
|
...shape.metrics,
|
|
748
954
|
bbox
|
|
749
955
|
},
|
|
750
|
-
edgeEpsilon: shape.edgeEpsilon
|
|
956
|
+
edgeEpsilon: shape.edgeEpsilon,
|
|
957
|
+
rawData: {
|
|
958
|
+
...shape.raw,
|
|
959
|
+
variantOptions: { ...shape.raw.variantOptions ?? zeroShapeVariant() }
|
|
960
|
+
}
|
|
751
961
|
});
|
|
752
962
|
}
|
|
753
963
|
function createRegionCollection(shape) {
|
|
754
964
|
return shape.parts.map((part) => ({
|
|
755
|
-
outer: copyRing(part.outer),
|
|
756
|
-
holes: part.holes.map((hole) => copyRing(hole)),
|
|
965
|
+
outer: copyRing$1(part.outer),
|
|
966
|
+
holes: part.holes.map((hole) => copyRing$1(hole)),
|
|
757
967
|
bbox: { ...part.bbox }
|
|
758
968
|
}));
|
|
759
969
|
}
|
|
@@ -763,8 +973,8 @@ function copyParts(parts) {
|
|
|
763
973
|
function copyPart(part) {
|
|
764
974
|
return {
|
|
765
975
|
...part,
|
|
766
|
-
outer: copyRing(part.outer),
|
|
767
|
-
holes: part.holes.map((hole) => copyRing(hole)),
|
|
976
|
+
outer: copyRing$1(part.outer),
|
|
977
|
+
holes: part.holes.map((hole) => copyRing$1(hole)),
|
|
768
978
|
anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
|
|
769
979
|
bbox: { ...part.bbox }
|
|
770
980
|
};
|
|
@@ -773,9 +983,19 @@ function copyContours(contours) {
|
|
|
773
983
|
return contours.map((contour) => ({
|
|
774
984
|
...contour,
|
|
775
985
|
bbox: { ...contour.bbox },
|
|
776
|
-
ring: copyRing(contour.ring)
|
|
986
|
+
ring: copyRing$1(contour.ring)
|
|
777
987
|
}));
|
|
778
988
|
}
|
|
989
|
+
function groupPartsByGlyphPosition(parts) {
|
|
990
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
991
|
+
parts.forEach((part) => {
|
|
992
|
+
if (typeof part.glyphPosition !== "number") return;
|
|
993
|
+
const bucket = grouped.get(part.glyphPosition) ?? [];
|
|
994
|
+
bucket.push(part);
|
|
995
|
+
grouped.set(part.glyphPosition, bucket);
|
|
996
|
+
});
|
|
997
|
+
return grouped;
|
|
998
|
+
}
|
|
779
999
|
function extractGlyphText(text, glyphPosition, glyphCount) {
|
|
780
1000
|
if (glyphCount <= 1) return text;
|
|
781
1001
|
return Array.from(text ?? "")[glyphPosition] ?? "";
|
|
@@ -804,6 +1024,19 @@ function normalizeShapeOptions(options = {}) {
|
|
|
804
1024
|
openWidth: normalizePositive(options.openWidth, 0)
|
|
805
1025
|
};
|
|
806
1026
|
}
|
|
1027
|
+
function extractShapeSourceOptions(opts) {
|
|
1028
|
+
return {
|
|
1029
|
+
size: opts.size,
|
|
1030
|
+
flatten: opts.flatten,
|
|
1031
|
+
edgeEpsilon: opts.edgeEpsilon,
|
|
1032
|
+
kerning: opts.kerning,
|
|
1033
|
+
letterSpacing: opts.letterSpacing,
|
|
1034
|
+
tracking: opts.tracking,
|
|
1035
|
+
script: opts.script,
|
|
1036
|
+
language: opts.language,
|
|
1037
|
+
features: opts.features
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
807
1040
|
function resolveShapeVariant(shape, options = {}) {
|
|
808
1041
|
const normalized = normalizeShapeOptions(options);
|
|
809
1042
|
if (normalized.step <= 0 && normalized.openWidth <= 0) return shape;
|
|
@@ -816,84 +1049,158 @@ function resolveShapeVariant(shape, options = {}) {
|
|
|
816
1049
|
shape._cache.shapes.set(cacheKey, next);
|
|
817
1050
|
return next;
|
|
818
1051
|
}
|
|
1052
|
+
function deriveGlyphGeometryVariant(geometry, options = {}) {
|
|
1053
|
+
const normalized = normalizeShapeOptions(options);
|
|
1054
|
+
if (normalized.step <= 0 && normalized.openWidth <= 0) return geometry;
|
|
1055
|
+
let parts = geometry.parts;
|
|
1056
|
+
if (normalized.openWidth > 0) {
|
|
1057
|
+
const geometryEpsilon = resolveGeometryEpsilon(normalized.openWidth);
|
|
1058
|
+
parts = parts.map((part) => openPartWithSlit(part, normalized.openWidth, geometryEpsilon));
|
|
1059
|
+
}
|
|
1060
|
+
if (normalized.step > 0) parts = parts.map((part) => resamplePart(part, normalized.step));
|
|
1061
|
+
return {
|
|
1062
|
+
...geometry,
|
|
1063
|
+
contours: [],
|
|
1064
|
+
parts,
|
|
1065
|
+
bbox: combineRects(parts.map((part) => part.bbox)) ?? emptyRect()
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
819
1068
|
function toCacheKey(value) {
|
|
820
1069
|
return normalizePositive(value, 0).toFixed(6);
|
|
821
1070
|
}
|
|
1071
|
+
function zeroShapeVariant() {
|
|
1072
|
+
return {
|
|
1073
|
+
openWidth: 0,
|
|
1074
|
+
step: 0
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function createGlyphDerivedShape(shape, variant) {
|
|
1078
|
+
if (!canUseGlyphDerivedShape(shape)) return null;
|
|
1079
|
+
return createTextShape({
|
|
1080
|
+
text: shape.text,
|
|
1081
|
+
glyphs: shape.raw.sourceLayout.glyphs,
|
|
1082
|
+
metrics: shape.raw.sourceLayout.metrics
|
|
1083
|
+
}, {
|
|
1084
|
+
...shape.raw.sourceOptions,
|
|
1085
|
+
...mergeShapeVariantOptions(shape.raw.variantOptions, variant)
|
|
1086
|
+
}, shape.raw.fontInstance);
|
|
1087
|
+
}
|
|
1088
|
+
function canUseGlyphDerivedShape(shape) {
|
|
1089
|
+
return shape.raw?.fontInstance != null && shape.raw?.sourceOptions != null && shape.raw?.sourceLayout != null && Array.isArray(shape.raw.sourceLayout.glyphs);
|
|
1090
|
+
}
|
|
1091
|
+
function mergeShapeVariantOptions(previous, next) {
|
|
1092
|
+
return {
|
|
1093
|
+
openWidth: normalizePositive(next.openWidth, previous?.openWidth ?? 0),
|
|
1094
|
+
step: normalizePositive(next.step, previous?.step ?? 0)
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
822
1097
|
function resolveGeometryEpsilon(width) {
|
|
823
1098
|
return Math.max(1e-4, normalizePositive(width, 1) * .001);
|
|
824
1099
|
}
|
|
825
1100
|
function openPartWithSlit(part, width, epsilon) {
|
|
826
1101
|
if (!part.holes || part.holes.length === 0) return copyPart(part);
|
|
827
|
-
let outer = copyRing(part.outer);
|
|
828
|
-
const holes = part.holes.map((hole) => copyRing(hole));
|
|
1102
|
+
let outer = copyRing$1(part.outer);
|
|
1103
|
+
const holes = part.holes.map((hole) => copyRing$1(hole));
|
|
829
1104
|
const anchors = [];
|
|
1105
|
+
const blockers = [];
|
|
830
1106
|
while (holes.length > 0) {
|
|
831
1107
|
let selectedHoleIndex = -1;
|
|
832
1108
|
let selectedBridge = null;
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1109
|
+
let selectedDist = Number.POSITIVE_INFINITY;
|
|
1110
|
+
for (let hi = 0; hi < holes.length; hi++) {
|
|
1111
|
+
blockers.length = 0;
|
|
1112
|
+
for (let bi = 0; bi < holes.length; bi++) if (bi !== hi) blockers.push(holes[bi]);
|
|
1113
|
+
const candidate = findBestHoleBridge(outer, holes[hi], blockers, epsilon);
|
|
1114
|
+
if (candidate && candidate.distance < selectedDist) {
|
|
837
1115
|
selectedBridge = candidate;
|
|
838
|
-
selectedHoleIndex =
|
|
1116
|
+
selectedHoleIndex = hi;
|
|
1117
|
+
selectedDist = candidate.distance;
|
|
839
1118
|
}
|
|
840
|
-
}
|
|
1119
|
+
}
|
|
841
1120
|
if (!selectedBridge || selectedHoleIndex === -1) break;
|
|
842
1121
|
const [hole] = holes.splice(selectedHoleIndex, 1);
|
|
843
1122
|
const merged = mergeHoleIntoOuter(outer, hole, selectedBridge, width, epsilon);
|
|
844
1123
|
outer = merged.ring;
|
|
845
|
-
|
|
1124
|
+
const mergedAnchors = merged.anchors;
|
|
1125
|
+
for (let i = 0; i < mergedAnchors.length; i++) anchors.push(mergedAnchors[i]);
|
|
846
1126
|
}
|
|
1127
|
+
let holeArea = 0;
|
|
1128
|
+
for (let i = 0; i < holes.length; i++) holeArea += Math.abs(signedArea(holes[i]));
|
|
847
1129
|
return {
|
|
848
1130
|
...part,
|
|
849
1131
|
outer,
|
|
850
1132
|
holes,
|
|
851
1133
|
anchors,
|
|
852
1134
|
bbox: ringBounds(outer),
|
|
853
|
-
area: Math.abs(signedArea(outer)) -
|
|
1135
|
+
area: Math.abs(signedArea(outer)) - holeArea
|
|
854
1136
|
};
|
|
855
1137
|
}
|
|
856
1138
|
function resamplePart(part, step) {
|
|
857
1139
|
const outer = resampleRing(part.outer, step, part.anchors ?? []);
|
|
858
|
-
const
|
|
1140
|
+
const srcHoles = part.holes;
|
|
1141
|
+
const holes = new Array(srcHoles.length);
|
|
1142
|
+
let holeArea = 0;
|
|
1143
|
+
for (let i = 0; i < srcHoles.length; i++) {
|
|
1144
|
+
holes[i] = resampleRing(srcHoles[i], step);
|
|
1145
|
+
holeArea += Math.abs(signedArea(holes[i]));
|
|
1146
|
+
}
|
|
859
1147
|
return {
|
|
860
1148
|
...part,
|
|
861
1149
|
outer,
|
|
862
1150
|
holes,
|
|
863
1151
|
anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
|
|
864
1152
|
bbox: ringBounds(outer),
|
|
865
|
-
area: Math.abs(signedArea(outer)) -
|
|
1153
|
+
area: Math.abs(signedArea(outer)) - holeArea
|
|
866
1154
|
};
|
|
867
1155
|
}
|
|
868
1156
|
function findBestHoleBridge(outer, hole, blockers, epsilon) {
|
|
869
1157
|
let best = null;
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1158
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
1159
|
+
const outerLen = outer.length;
|
|
1160
|
+
const holeLen = hole.length;
|
|
1161
|
+
for (let oi = 0; oi < outerLen; oi++) {
|
|
1162
|
+
const op = outer[oi];
|
|
1163
|
+
for (let hi = 0; hi < holeLen; hi++) {
|
|
1164
|
+
const hp = hole[hi];
|
|
1165
|
+
const dx = op[0] - hp[0], dy = op[1] - hp[1];
|
|
1166
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1167
|
+
if (dist >= bestDist) continue;
|
|
1168
|
+
if (!isVisibleBridge(outer, hole, blockers, op, hp, oi, hi, epsilon)) continue;
|
|
1169
|
+
best = {
|
|
1170
|
+
outerIndex: oi,
|
|
1171
|
+
holeIndex: hi,
|
|
1172
|
+
distance: dist
|
|
877
1173
|
};
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
}
|
|
1174
|
+
bestDist = dist;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
881
1177
|
if (best) return best;
|
|
882
1178
|
return findNearestBridge(outer, hole);
|
|
883
1179
|
}
|
|
884
1180
|
function findNearestBridge(outer, hole) {
|
|
885
|
-
let
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1181
|
+
let bestOi = 0, bestHi = 0;
|
|
1182
|
+
let bestDistSq = Number.POSITIVE_INFINITY;
|
|
1183
|
+
const outerLen = outer.length;
|
|
1184
|
+
const holeLen = hole.length;
|
|
1185
|
+
for (let oi = 0; oi < outerLen; oi++) {
|
|
1186
|
+
const op = outer[oi];
|
|
1187
|
+
const ox = op[0], oy = op[1];
|
|
1188
|
+
for (let hi = 0; hi < holeLen; hi++) {
|
|
1189
|
+
const hp = hole[hi];
|
|
1190
|
+
const dx = ox - hp[0], dy = oy - hp[1];
|
|
1191
|
+
const distSq = dx * dx + dy * dy;
|
|
1192
|
+
if (distSq < bestDistSq) {
|
|
1193
|
+
bestDistSq = distSq;
|
|
1194
|
+
bestOi = oi;
|
|
1195
|
+
bestHi = hi;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
outerIndex: bestOi,
|
|
1201
|
+
holeIndex: bestHi,
|
|
1202
|
+
distance: Math.sqrt(bestDistSq)
|
|
1203
|
+
};
|
|
897
1204
|
}
|
|
898
1205
|
function isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon) {
|
|
899
1206
|
if (distance(outerPoint, holePoint) <= epsilon) return false;
|
|
@@ -1020,7 +1327,7 @@ function normalizeCutLocation(name, ring, cut, epsilon) {
|
|
|
1020
1327
|
};
|
|
1021
1328
|
}
|
|
1022
1329
|
function ringPath(ring, startIndex, endIndex) {
|
|
1023
|
-
if (startIndex == null || endIndex == null) return copyRing(ring);
|
|
1330
|
+
if (startIndex == null || endIndex == null) return copyRing$1(ring);
|
|
1024
1331
|
const path = [ring[startIndex]];
|
|
1025
1332
|
let cursor = startIndex;
|
|
1026
1333
|
while (cursor !== endIndex) {
|
|
@@ -1035,7 +1342,7 @@ function appendPath(target, path, epsilon) {
|
|
|
1035
1342
|
});
|
|
1036
1343
|
}
|
|
1037
1344
|
function resampleRing(ring, step, anchors = []) {
|
|
1038
|
-
if (ring.length < 3) return copyRing(ring);
|
|
1345
|
+
if (ring.length < 3) return copyRing$1(ring);
|
|
1039
1346
|
const normalizedAnchors = normalizeAnchorPoints(ring, anchors);
|
|
1040
1347
|
if (normalizedAnchors.length >= 2) return resampleRingWithAnchors(ring, step, normalizedAnchors);
|
|
1041
1348
|
return resampleClosedPath(ring, step);
|
|
@@ -1056,30 +1363,32 @@ function resampleRingWithAnchors(ring, step, anchorIndices) {
|
|
|
1056
1363
|
appendPath(result, resampleOpenPath(ringPath(ring, startIndex, endIndex), step), 1e-6);
|
|
1057
1364
|
}
|
|
1058
1365
|
if (result.length > 1 && pointsAlmostEqual2D(result[0], result[result.length - 1], 1e-6)) result.pop();
|
|
1059
|
-
if (result.length < 3 || result.length >= ring.length) return copyRing(ring);
|
|
1366
|
+
if (result.length < 3 || result.length >= ring.length) return copyRing$1(ring);
|
|
1060
1367
|
return result;
|
|
1061
1368
|
}
|
|
1062
1369
|
function resampleClosedPath(ring, step) {
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1370
|
+
const cumulative = buildCumulativeDistances(ring);
|
|
1371
|
+
const perimeter = cumulative[ring.length];
|
|
1372
|
+
if (perimeter <= 1e-9) return copyRing$1(ring);
|
|
1065
1373
|
const pointCount = Math.max(3, Math.round(perimeter / step));
|
|
1066
|
-
if (pointCount >= ring.length) return copyRing(ring);
|
|
1374
|
+
if (pointCount >= ring.length) return copyRing$1(ring);
|
|
1067
1375
|
const sampled = [];
|
|
1068
|
-
for (let index = 0; index < pointCount; index
|
|
1069
|
-
const point =
|
|
1376
|
+
for (let index = 0; index < pointCount; index++) {
|
|
1377
|
+
const point = pointAtClosedPathDistanceFast(ring, cumulative, perimeter * index / pointCount);
|
|
1070
1378
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1071
1379
|
}
|
|
1072
|
-
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing(ring);
|
|
1380
|
+
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing$1(ring);
|
|
1073
1381
|
return sampled;
|
|
1074
1382
|
}
|
|
1075
1383
|
function resampleOpenPath(path, step) {
|
|
1076
1384
|
if (path.length <= 2) return path.map((point) => [point[0], point[1]]);
|
|
1077
|
-
const
|
|
1385
|
+
const cumulative = buildOpenCumulativeDistances(path);
|
|
1386
|
+
const length = cumulative[path.length - 1];
|
|
1078
1387
|
if (length <= step) return [[path[0][0], path[0][1]], [path[path.length - 1][0], path[path.length - 1][1]]];
|
|
1079
1388
|
const divisionCount = Math.max(1, Math.round(length / step));
|
|
1080
1389
|
const sampled = [];
|
|
1081
|
-
for (let index = 0; index <= divisionCount; index
|
|
1082
|
-
const point =
|
|
1390
|
+
for (let index = 0; index <= divisionCount; index++) {
|
|
1391
|
+
const point = pointAtOpenPathDistanceFast(path, cumulative, length * index / divisionCount);
|
|
1083
1392
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1084
1393
|
}
|
|
1085
1394
|
return sampled;
|
|
@@ -3180,9 +3489,11 @@ var DEFAULT_LINE_HEIGHT_RATIO = 1.2;
|
|
|
3180
3489
|
var HUGE_LAYOUT_WIDTH = 1e9;
|
|
3181
3490
|
var JUSTIFY_EPSILON = 1e-6;
|
|
3182
3491
|
var QUOTE_RE = /"/g;
|
|
3492
|
+
var SHARED_MEASURE_CACHE_LIMIT = 2048;
|
|
3183
3493
|
var sharedMeasureContext = null;
|
|
3184
3494
|
var sharedWordSegmenter = null;
|
|
3185
3495
|
var sharedGraphemeSegmenter = null;
|
|
3496
|
+
var sharedMeasureCaches = /* @__PURE__ */ new WeakMap();
|
|
3186
3497
|
function layoutParagraph(fontInstance, text, options = {}, state = {}) {
|
|
3187
3498
|
const normalized = normalizeParagraphOptions(fontInstance, options);
|
|
3188
3499
|
const textValue = String(text ?? "");
|
|
@@ -3449,9 +3760,25 @@ function truncateLineToWidth(line, maxWidth, measureWidth, suffix) {
|
|
|
3449
3760
|
width: 0,
|
|
3450
3761
|
hardBreak: false
|
|
3451
3762
|
};
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3763
|
+
const trimmed = trimTrailingWhitespace(line.text);
|
|
3764
|
+
if (measureWidth(`${trimmed}${suffixText}`) <= maxWidth + JUSTIFY_EPSILON) {
|
|
3765
|
+
const text = `${trimmed}${suffixText}`;
|
|
3766
|
+
return {
|
|
3767
|
+
...line,
|
|
3768
|
+
text,
|
|
3769
|
+
width: measureWidth(text),
|
|
3770
|
+
hardBreak: false
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
const segmenter = getGraphemeSegmenter();
|
|
3774
|
+
const graphemes = Array.from(segmenter.segment(trimmed), (seg) => seg.segment);
|
|
3775
|
+
let lo = 0, hi = graphemes.length;
|
|
3776
|
+
while (lo < hi) {
|
|
3777
|
+
const mid = lo + hi + 1 >>> 1;
|
|
3778
|
+
if (measureWidth(graphemes.slice(0, mid).join("") + suffixText) <= maxWidth + JUSTIFY_EPSILON) lo = mid;
|
|
3779
|
+
else hi = mid - 1;
|
|
3780
|
+
}
|
|
3781
|
+
const text = (lo > 0 ? graphemes.slice(0, lo).join("") : "") + suffixText;
|
|
3455
3782
|
return {
|
|
3456
3783
|
...line,
|
|
3457
3784
|
text,
|
|
@@ -3862,15 +4189,24 @@ function createTextMeasurer(fontInstance, options) {
|
|
|
3862
4189
|
const cache = /* @__PURE__ */ new Map();
|
|
3863
4190
|
const openTypeMeasurer = createOpenTypeMeasurer(fontInstance, options);
|
|
3864
4191
|
const context = getMeasureContext();
|
|
4192
|
+
const sharedCache = getSharedMeasureCache(fontInstance, "canvas");
|
|
4193
|
+
const sharedKeyPrefix = `${options.font}\u0000`;
|
|
3865
4194
|
return (value) => {
|
|
3866
4195
|
if (value.length === 0) return 0;
|
|
3867
4196
|
if (cache.has(value)) return cache.get(value);
|
|
4197
|
+
const sharedKey = `${sharedKeyPrefix}${value}`;
|
|
4198
|
+
const sharedWidth = readSharedMeasureCache(sharedCache, sharedKey);
|
|
4199
|
+
if (sharedWidth != null) {
|
|
4200
|
+
cache.set(value, sharedWidth);
|
|
4201
|
+
return sharedWidth;
|
|
4202
|
+
}
|
|
3868
4203
|
let width;
|
|
3869
4204
|
if (context) {
|
|
3870
4205
|
context.font = options.font;
|
|
3871
4206
|
width = context.measureText(value).width;
|
|
3872
4207
|
} else width = openTypeMeasurer(value);
|
|
3873
4208
|
cache.set(value, width);
|
|
4209
|
+
writeSharedMeasureCache(sharedCache, sharedKey, width);
|
|
3874
4210
|
return width;
|
|
3875
4211
|
};
|
|
3876
4212
|
}
|
|
@@ -3883,6 +4219,7 @@ function createLazyTextMeasurer(fontInstance, options) {
|
|
|
3883
4219
|
}
|
|
3884
4220
|
function createOpenTypeMeasurer(fontInstance, options) {
|
|
3885
4221
|
const cache = /* @__PURE__ */ new Map();
|
|
4222
|
+
const sharedCache = getSharedMeasureCache(fontInstance, "openType");
|
|
3886
4223
|
const widthOptions = {
|
|
3887
4224
|
x: 0,
|
|
3888
4225
|
y: 0,
|
|
@@ -3896,11 +4233,19 @@ function createOpenTypeMeasurer(fontInstance, options) {
|
|
|
3896
4233
|
language: options.language,
|
|
3897
4234
|
features: options.features
|
|
3898
4235
|
};
|
|
4236
|
+
const sharedKeyPrefix = createOpenTypeMeasureKeyPrefix(widthOptions);
|
|
3899
4237
|
return (value) => {
|
|
3900
4238
|
if (value.length === 0) return 0;
|
|
3901
4239
|
if (cache.has(value)) return cache.get(value);
|
|
4240
|
+
const sharedKey = `${sharedKeyPrefix}${value}`;
|
|
4241
|
+
const sharedWidth = readSharedMeasureCache(sharedCache, sharedKey);
|
|
4242
|
+
if (sharedWidth != null) {
|
|
4243
|
+
cache.set(value, sharedWidth);
|
|
4244
|
+
return sharedWidth;
|
|
4245
|
+
}
|
|
3902
4246
|
const width = measureAdvanceWidth(fontInstance.font, value, widthOptions);
|
|
3903
4247
|
cache.set(value, width);
|
|
4248
|
+
writeSharedMeasureCache(sharedCache, sharedKey, width);
|
|
3904
4249
|
return width;
|
|
3905
4250
|
};
|
|
3906
4251
|
}
|
|
@@ -3916,6 +4261,49 @@ function getMeasureContext() {
|
|
|
3916
4261
|
}
|
|
3917
4262
|
return null;
|
|
3918
4263
|
}
|
|
4264
|
+
function getSharedMeasureCache(fontInstance, bucket) {
|
|
4265
|
+
if (fontInstance == null || typeof fontInstance !== "object" && typeof fontInstance !== "function") return null;
|
|
4266
|
+
let caches = sharedMeasureCaches.get(fontInstance);
|
|
4267
|
+
if (!caches) {
|
|
4268
|
+
caches = {
|
|
4269
|
+
canvas: /* @__PURE__ */ new Map(),
|
|
4270
|
+
openType: /* @__PURE__ */ new Map()
|
|
4271
|
+
};
|
|
4272
|
+
sharedMeasureCaches.set(fontInstance, caches);
|
|
4273
|
+
}
|
|
4274
|
+
return caches[bucket];
|
|
4275
|
+
}
|
|
4276
|
+
function readSharedMeasureCache(cache, key) {
|
|
4277
|
+
if (!cache || !cache.has(key)) return null;
|
|
4278
|
+
const value = cache.get(key);
|
|
4279
|
+
cache.delete(key);
|
|
4280
|
+
cache.set(key, value);
|
|
4281
|
+
return value;
|
|
4282
|
+
}
|
|
4283
|
+
function writeSharedMeasureCache(cache, key, value) {
|
|
4284
|
+
if (!cache) return;
|
|
4285
|
+
if (cache.has(key)) cache.delete(key);
|
|
4286
|
+
cache.set(key, value);
|
|
4287
|
+
if (cache.size > SHARED_MEASURE_CACHE_LIMIT) {
|
|
4288
|
+
const oldestKey = cache.keys().next().value;
|
|
4289
|
+
cache.delete(oldestKey);
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
function createOpenTypeMeasureKeyPrefix(options) {
|
|
4293
|
+
return [
|
|
4294
|
+
options.size,
|
|
4295
|
+
options.kerning ? 1 : 0,
|
|
4296
|
+
options.letterSpacing ?? "",
|
|
4297
|
+
options.tracking ?? "",
|
|
4298
|
+
options.script ?? "",
|
|
4299
|
+
options.language ?? "",
|
|
4300
|
+
serializeMeasureFeatures(options.features)
|
|
4301
|
+
].join("|") + "\0";
|
|
4302
|
+
}
|
|
4303
|
+
function serializeMeasureFeatures(features) {
|
|
4304
|
+
if (!features || typeof features !== "object") return "";
|
|
4305
|
+
return Object.keys(features).sort().map((key) => `${key}:${features[key]}`).join(",");
|
|
4306
|
+
}
|
|
3919
4307
|
function getWordSegmenter() {
|
|
3920
4308
|
if (sharedWordSegmenter == null) sharedWordSegmenter = new Intl.Segmenter(void 0, { granularity: "word" });
|
|
3921
4309
|
return sharedWordSegmenter;
|
|
@@ -3927,12 +4315,6 @@ function getGraphemeSegmenter() {
|
|
|
3927
4315
|
function trimTrailingWhitespace(value) {
|
|
3928
4316
|
return value.replace(/\s+$/u, "");
|
|
3929
4317
|
}
|
|
3930
|
-
function trimLastGrapheme(value) {
|
|
3931
|
-
const segmenter = getGraphemeSegmenter();
|
|
3932
|
-
const graphemes = Array.from(segmenter.segment(value), (segment) => segment.segment);
|
|
3933
|
-
graphemes.pop();
|
|
3934
|
-
return graphemes.join("");
|
|
3935
|
-
}
|
|
3936
4318
|
function splitPreservingWhitespace(value) {
|
|
3937
4319
|
return value.split(/(\s+)/u).filter((token) => token.length > 0);
|
|
3938
4320
|
}
|
|
@@ -4011,6 +4393,10 @@ var paParagraph = class paParagraph {
|
|
|
4011
4393
|
const { layout = "current", ...shapeOptions } = normalizeParagraphShapeOptions(options, "toRegions()");
|
|
4012
4394
|
return this._getBaseShape(layout).toRegions(shapeOptions);
|
|
4013
4395
|
}
|
|
4396
|
+
toRegionViews(options = {}) {
|
|
4397
|
+
const { layout = "current", ...shapeOptions } = normalizeParagraphShapeOptions(options, "toRegionViews()");
|
|
4398
|
+
return this._getBaseShape(layout).toRegionViews(shapeOptions);
|
|
4399
|
+
}
|
|
4014
4400
|
toPoints(options = {}) {
|
|
4015
4401
|
const { layout = "current", ...pointOptions } = normalizeParagraphPointOptions(options);
|
|
4016
4402
|
return this._getBaseShape(layout).toPoints(pointOptions);
|
|
@@ -4146,6 +4532,7 @@ var paFont = class paFont {
|
|
|
4146
4532
|
this.canvasFamily = this.family;
|
|
4147
4533
|
this._glyphTopologyCache = /* @__PURE__ */ new Map();
|
|
4148
4534
|
this._glyphFlatCache = /* @__PURE__ */ new Map();
|
|
4535
|
+
this._glyphVariantCache = /* @__PURE__ */ new Map();
|
|
4149
4536
|
}
|
|
4150
4537
|
static async load(source, options = {}) {
|
|
4151
4538
|
const opts = normalizeLoadOptions(options);
|
|
@@ -4223,10 +4610,27 @@ var paFont = class paFont {
|
|
|
4223
4610
|
}
|
|
4224
4611
|
return this._glyphFlatCache.get(key);
|
|
4225
4612
|
}
|
|
4613
|
+
_getGlyphGeometryVariant(glyph, opts) {
|
|
4614
|
+
const openWidth = normalizeShapeVariantValue(opts.openWidth);
|
|
4615
|
+
const step = normalizeShapeVariantValue(opts.step);
|
|
4616
|
+
if (openWidth <= 0 && step <= 0) return this._getFlattenedGlyph(glyph, opts);
|
|
4617
|
+
const key = `${glyph.index}:${opts.size}:${opts.flatten}:${toShapeVariantKey(openWidth)}:${toShapeVariantKey(step)}`;
|
|
4618
|
+
if (!this._glyphVariantCache.has(key)) this._glyphVariantCache.set(key, deriveGlyphGeometryVariant(this._getFlattenedGlyph(glyph, opts), {
|
|
4619
|
+
openWidth,
|
|
4620
|
+
step
|
|
4621
|
+
}));
|
|
4622
|
+
return this._glyphVariantCache.get(key);
|
|
4623
|
+
}
|
|
4226
4624
|
_layoutText(value, opts) {
|
|
4227
4625
|
return layoutGlyphs(this.font, value, opts);
|
|
4228
4626
|
}
|
|
4229
4627
|
};
|
|
4628
|
+
function normalizeShapeVariantValue(value) {
|
|
4629
|
+
return Number.isFinite(value) && value > 0 ? Number(value) : 0;
|
|
4630
|
+
}
|
|
4631
|
+
function toShapeVariantKey(value) {
|
|
4632
|
+
return normalizeShapeVariantValue(value).toFixed(6);
|
|
4633
|
+
}
|
|
4230
4634
|
async function fetchFontBytes(source) {
|
|
4231
4635
|
const response = await fetch(source);
|
|
4232
4636
|
if (!response.ok) throw new Error(`Failed to load font from ${source}: ${response.status} ${response.statusText}`);
|