pa_font 0.2.4 → 0.2.6
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/dist/paFont.cjs +445 -244
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +445 -244
- package/dist/paFont.js.map +1 -1
- 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
11
|
if (points.length === 0 || !pointsEqual2D(points[points.length - 1], point)) points.push(point);
|
|
18
12
|
}
|
|
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
|
-
|
|
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,
|
|
@@ -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
|
}
|
|
@@ -696,26 +848,31 @@ var PAShape = class {
|
|
|
696
848
|
function createTextShape(layout, opts, fontInstance) {
|
|
697
849
|
const parts = [];
|
|
698
850
|
const contours = [];
|
|
699
|
-
const
|
|
700
|
-
const
|
|
851
|
+
const layoutGlyphs = layout.glyphs;
|
|
852
|
+
const glyphCount = layoutGlyphs.length;
|
|
853
|
+
const glyphs = new Array(glyphCount);
|
|
854
|
+
const sourceLayoutGlyphs = Array.isArray(layoutGlyphs) ? layoutGlyphs.slice() : [];
|
|
701
855
|
const variantOptions = {
|
|
702
856
|
openWidth: normalizePositive(opts.openWidth, 0),
|
|
703
857
|
step: normalizePositive(opts.step, 0)
|
|
704
858
|
};
|
|
705
|
-
|
|
859
|
+
for (let glyphPosition = 0; glyphPosition < glyphCount; glyphPosition++) {
|
|
860
|
+
const item = layoutGlyphs[glyphPosition];
|
|
706
861
|
const translated = translateGlyphGeometry(fontInstance._getGlyphGeometryVariant(item.glyph, opts), item.x, item.y, glyphPosition);
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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] = {
|
|
710
867
|
position: glyphPosition,
|
|
711
868
|
glyphIndex: item.glyph.index,
|
|
712
869
|
x: item.x,
|
|
713
870
|
y: item.y,
|
|
714
871
|
size: item.size,
|
|
715
872
|
bbox: translated.bbox,
|
|
716
|
-
partCount:
|
|
717
|
-
}
|
|
718
|
-
}
|
|
873
|
+
partCount: tParts.length
|
|
874
|
+
};
|
|
875
|
+
}
|
|
719
876
|
const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
|
|
720
877
|
return new PAShape({
|
|
721
878
|
text: layout.text,
|
|
@@ -809,8 +966,8 @@ function createDerivedShape(shape, parts) {
|
|
|
809
966
|
}
|
|
810
967
|
function createRegionCollection(shape) {
|
|
811
968
|
return shape.parts.map((part) => ({
|
|
812
|
-
outer: copyRing(part.outer),
|
|
813
|
-
holes: part.holes.map((hole) => copyRing(hole)),
|
|
969
|
+
outer: copyRing$1(part.outer),
|
|
970
|
+
holes: part.holes.map((hole) => copyRing$1(hole)),
|
|
814
971
|
bbox: { ...part.bbox }
|
|
815
972
|
}));
|
|
816
973
|
}
|
|
@@ -820,8 +977,8 @@ function copyParts(parts) {
|
|
|
820
977
|
function copyPart(part) {
|
|
821
978
|
return {
|
|
822
979
|
...part,
|
|
823
|
-
outer: copyRing(part.outer),
|
|
824
|
-
holes: part.holes.map((hole) => copyRing(hole)),
|
|
980
|
+
outer: copyRing$1(part.outer),
|
|
981
|
+
holes: part.holes.map((hole) => copyRing$1(hole)),
|
|
825
982
|
anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
|
|
826
983
|
bbox: { ...part.bbox }
|
|
827
984
|
};
|
|
@@ -830,7 +987,7 @@ function copyContours(contours) {
|
|
|
830
987
|
return contours.map((contour) => ({
|
|
831
988
|
...contour,
|
|
832
989
|
bbox: { ...contour.bbox },
|
|
833
|
-
ring: copyRing(contour.ring)
|
|
990
|
+
ring: copyRing$1(contour.ring)
|
|
834
991
|
}));
|
|
835
992
|
}
|
|
836
993
|
function groupPartsByGlyphPosition(parts) {
|
|
@@ -946,76 +1103,108 @@ function resolveGeometryEpsilon(width) {
|
|
|
946
1103
|
}
|
|
947
1104
|
function openPartWithSlit(part, width, epsilon) {
|
|
948
1105
|
if (!part.holes || part.holes.length === 0) return copyPart(part);
|
|
949
|
-
let outer = copyRing(part.outer);
|
|
950
|
-
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));
|
|
951
1108
|
const anchors = [];
|
|
1109
|
+
const blockers = [];
|
|
952
1110
|
while (holes.length > 0) {
|
|
953
1111
|
let selectedHoleIndex = -1;
|
|
954
1112
|
let selectedBridge = null;
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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) {
|
|
959
1119
|
selectedBridge = candidate;
|
|
960
|
-
selectedHoleIndex =
|
|
1120
|
+
selectedHoleIndex = hi;
|
|
1121
|
+
selectedDist = candidate.distance;
|
|
961
1122
|
}
|
|
962
|
-
}
|
|
1123
|
+
}
|
|
963
1124
|
if (!selectedBridge || selectedHoleIndex === -1) break;
|
|
964
1125
|
const [hole] = holes.splice(selectedHoleIndex, 1);
|
|
965
1126
|
const merged = mergeHoleIntoOuter(outer, hole, selectedBridge, width, epsilon);
|
|
966
1127
|
outer = merged.ring;
|
|
967
|
-
|
|
1128
|
+
const mergedAnchors = merged.anchors;
|
|
1129
|
+
for (let i = 0; i < mergedAnchors.length; i++) anchors.push(mergedAnchors[i]);
|
|
968
1130
|
}
|
|
1131
|
+
let holeArea = 0;
|
|
1132
|
+
for (let i = 0; i < holes.length; i++) holeArea += Math.abs(signedArea(holes[i]));
|
|
969
1133
|
return {
|
|
970
1134
|
...part,
|
|
971
1135
|
outer,
|
|
972
1136
|
holes,
|
|
973
1137
|
anchors,
|
|
974
1138
|
bbox: ringBounds(outer),
|
|
975
|
-
area: Math.abs(signedArea(outer)) -
|
|
1139
|
+
area: Math.abs(signedArea(outer)) - holeArea
|
|
976
1140
|
};
|
|
977
1141
|
}
|
|
978
1142
|
function resamplePart(part, step) {
|
|
979
1143
|
const outer = resampleRing(part.outer, step, part.anchors ?? []);
|
|
980
|
-
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
|
+
}
|
|
981
1151
|
return {
|
|
982
1152
|
...part,
|
|
983
1153
|
outer,
|
|
984
1154
|
holes,
|
|
985
1155
|
anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
|
|
986
1156
|
bbox: ringBounds(outer),
|
|
987
|
-
area: Math.abs(signedArea(outer)) -
|
|
1157
|
+
area: Math.abs(signedArea(outer)) - holeArea
|
|
988
1158
|
};
|
|
989
1159
|
}
|
|
990
1160
|
function findBestHoleBridge(outer, hole, blockers, epsilon) {
|
|
991
1161
|
let best = null;
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
|
999
1177
|
};
|
|
1000
|
-
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1178
|
+
bestDist = dist;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1003
1181
|
if (best) return best;
|
|
1004
1182
|
return findNearestBridge(outer, hole);
|
|
1005
1183
|
}
|
|
1006
1184
|
function findNearestBridge(outer, hole) {
|
|
1007
|
-
let
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
+
};
|
|
1019
1208
|
}
|
|
1020
1209
|
function isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon) {
|
|
1021
1210
|
if (distance(outerPoint, holePoint) <= epsilon) return false;
|
|
@@ -1142,7 +1331,7 @@ function normalizeCutLocation(name, ring, cut, epsilon) {
|
|
|
1142
1331
|
};
|
|
1143
1332
|
}
|
|
1144
1333
|
function ringPath(ring, startIndex, endIndex) {
|
|
1145
|
-
if (startIndex == null || endIndex == null) return copyRing(ring);
|
|
1334
|
+
if (startIndex == null || endIndex == null) return copyRing$1(ring);
|
|
1146
1335
|
const path = [ring[startIndex]];
|
|
1147
1336
|
let cursor = startIndex;
|
|
1148
1337
|
while (cursor !== endIndex) {
|
|
@@ -1157,7 +1346,7 @@ function appendPath(target, path, epsilon) {
|
|
|
1157
1346
|
});
|
|
1158
1347
|
}
|
|
1159
1348
|
function resampleRing(ring, step, anchors = []) {
|
|
1160
|
-
if (ring.length < 3) return copyRing(ring);
|
|
1349
|
+
if (ring.length < 3) return copyRing$1(ring);
|
|
1161
1350
|
const normalizedAnchors = normalizeAnchorPoints(ring, anchors);
|
|
1162
1351
|
if (normalizedAnchors.length >= 2) return resampleRingWithAnchors(ring, step, normalizedAnchors);
|
|
1163
1352
|
return resampleClosedPath(ring, step);
|
|
@@ -1178,30 +1367,32 @@ function resampleRingWithAnchors(ring, step, anchorIndices) {
|
|
|
1178
1367
|
appendPath(result, resampleOpenPath(ringPath(ring, startIndex, endIndex), step), 1e-6);
|
|
1179
1368
|
}
|
|
1180
1369
|
if (result.length > 1 && pointsAlmostEqual2D(result[0], result[result.length - 1], 1e-6)) result.pop();
|
|
1181
|
-
if (result.length < 3 || result.length >= ring.length) return copyRing(ring);
|
|
1370
|
+
if (result.length < 3 || result.length >= ring.length) return copyRing$1(ring);
|
|
1182
1371
|
return result;
|
|
1183
1372
|
}
|
|
1184
1373
|
function resampleClosedPath(ring, step) {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1374
|
+
const cumulative = buildCumulativeDistances(ring);
|
|
1375
|
+
const perimeter = cumulative[ring.length];
|
|
1376
|
+
if (perimeter <= 1e-9) return copyRing$1(ring);
|
|
1187
1377
|
const pointCount = Math.max(3, Math.round(perimeter / step));
|
|
1188
|
-
if (pointCount >= ring.length) return copyRing(ring);
|
|
1378
|
+
if (pointCount >= ring.length) return copyRing$1(ring);
|
|
1189
1379
|
const sampled = [];
|
|
1190
|
-
for (let index = 0; index < pointCount; index
|
|
1191
|
-
const point =
|
|
1380
|
+
for (let index = 0; index < pointCount; index++) {
|
|
1381
|
+
const point = pointAtClosedPathDistanceFast(ring, cumulative, perimeter * index / pointCount);
|
|
1192
1382
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1193
1383
|
}
|
|
1194
|
-
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing(ring);
|
|
1384
|
+
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing$1(ring);
|
|
1195
1385
|
return sampled;
|
|
1196
1386
|
}
|
|
1197
1387
|
function resampleOpenPath(path, step) {
|
|
1198
1388
|
if (path.length <= 2) return path.map((point) => [point[0], point[1]]);
|
|
1199
|
-
const
|
|
1389
|
+
const cumulative = buildOpenCumulativeDistances(path);
|
|
1390
|
+
const length = cumulative[path.length - 1];
|
|
1200
1391
|
if (length <= step) return [[path[0][0], path[0][1]], [path[path.length - 1][0], path[path.length - 1][1]]];
|
|
1201
1392
|
const divisionCount = Math.max(1, Math.round(length / step));
|
|
1202
1393
|
const sampled = [];
|
|
1203
|
-
for (let index = 0; index <= divisionCount; index
|
|
1204
|
-
const point =
|
|
1394
|
+
for (let index = 0; index <= divisionCount; index++) {
|
|
1395
|
+
const point = pointAtOpenPathDistanceFast(path, cumulative, length * index / divisionCount);
|
|
1205
1396
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1206
1397
|
}
|
|
1207
1398
|
return sampled;
|
|
@@ -3573,9 +3764,25 @@ function truncateLineToWidth(line, maxWidth, measureWidth, suffix) {
|
|
|
3573
3764
|
width: 0,
|
|
3574
3765
|
hardBreak: false
|
|
3575
3766
|
};
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
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;
|
|
3579
3786
|
return {
|
|
3580
3787
|
...line,
|
|
3581
3788
|
text,
|
|
@@ -4112,12 +4319,6 @@ function getGraphemeSegmenter() {
|
|
|
4112
4319
|
function trimTrailingWhitespace(value) {
|
|
4113
4320
|
return value.replace(/\s+$/u, "");
|
|
4114
4321
|
}
|
|
4115
|
-
function trimLastGrapheme(value) {
|
|
4116
|
-
const segmenter = getGraphemeSegmenter();
|
|
4117
|
-
const graphemes = Array.from(segmenter.segment(value), (segment) => segment.segment);
|
|
4118
|
-
graphemes.pop();
|
|
4119
|
-
return graphemes.join("");
|
|
4120
|
-
}
|
|
4121
4322
|
function splitPreservingWhitespace(value) {
|
|
4122
4323
|
return value.split(/(\s+)/u).filter((token) => token.length > 0);
|
|
4123
4324
|
}
|