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