pa_font 0.2.6 → 0.2.8
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 +244 -445
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +244 -445
- package/dist/paFont.js.map +1 -1
- package/package.json +1 -1
package/dist/paFont.js
CHANGED
|
@@ -3,127 +3,52 @@ 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
|
+
}
|
|
6
12
|
function pushUniquePoint(points, point) {
|
|
7
13
|
if (points.length === 0 || !pointsEqual2D(points[points.length - 1], point)) points.push(point);
|
|
8
14
|
}
|
|
9
|
-
function flattenQuadratic(p0, p1, p2, tolerance, out) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
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;
|
|
15
|
+
function flattenQuadratic(p0, p1, p2, tolerance, out, depth = 0) {
|
|
16
|
+
if (depth >= 12 || quadFlatness(p0, p1, p2) <= tolerance) {
|
|
17
|
+
pushUniquePoint(out, p2);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const p01 = midpoint(p0, p1);
|
|
21
|
+
const p12 = midpoint(p1, p2);
|
|
22
|
+
const p012 = midpoint(p01, p12);
|
|
23
|
+
flattenQuadratic(p0, p01, p012, tolerance, out, depth + 1);
|
|
24
|
+
flattenQuadratic(p012, p12, p2, tolerance, out, depth + 1);
|
|
25
|
+
}
|
|
26
|
+
function flattenCubic(p0, p1, p2, p3, tolerance, out, depth = 0) {
|
|
27
|
+
if (depth >= 12 || cubicFlatness(p0, p1, p2, p3) <= tolerance) {
|
|
28
|
+
pushUniquePoint(out, p3);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const p01 = midpoint(p0, p1);
|
|
32
|
+
const p12 = midpoint(p1, p2);
|
|
33
|
+
const p23 = midpoint(p2, p3);
|
|
34
|
+
const p012 = midpoint(p01, p12);
|
|
35
|
+
const p123 = midpoint(p12, p23);
|
|
36
|
+
const p0123 = midpoint(p012, p123);
|
|
37
|
+
flattenCubic(p0, p01, p012, p0123, tolerance, out, depth + 1);
|
|
38
|
+
flattenCubic(p0123, p123, p23, p3, tolerance, out, depth + 1);
|
|
39
|
+
}
|
|
40
|
+
function quadFlatness(p0, p1, p2) {
|
|
41
|
+
return pointToLineDistance(p1, p0, p2);
|
|
42
|
+
}
|
|
43
|
+
function cubicFlatness(p0, p1, p2, p3) {
|
|
44
|
+
return Math.max(pointToLineDistance(p1, p0, p3), pointToLineDistance(p2, p0, p3));
|
|
45
|
+
}
|
|
46
|
+
function pointToLineDistance(point, lineStart, lineEnd) {
|
|
47
|
+
const dx = lineEnd[0] - lineStart[0];
|
|
48
|
+
const dy = lineEnd[1] - lineStart[1];
|
|
49
|
+
const lengthSq = dx * dx + dy * dy;
|
|
50
|
+
if (lengthSq === 0) return distance(point, lineStart);
|
|
51
|
+
return Math.abs(dx * (lineStart[1] - point[1]) - (lineStart[0] - point[0]) * dy) / Math.sqrt(lengthSq);
|
|
127
52
|
}
|
|
128
53
|
function midpoint(a, b) {
|
|
129
54
|
return [(a[0] + b[0]) * .5, (a[1] + b[1]) * .5];
|
|
@@ -181,37 +106,28 @@ function distance(a, b) {
|
|
|
181
106
|
return Math.sqrt(dx * dx + dy * dy);
|
|
182
107
|
}
|
|
183
108
|
function sampleRing(ring, step, callback) {
|
|
184
|
-
|
|
185
|
-
for (let index = 0; index < len; index++) {
|
|
109
|
+
for (let index = 0; index < ring.length; index += 1) {
|
|
186
110
|
const a = ring[index];
|
|
187
|
-
const b = ring[(index + 1) %
|
|
188
|
-
const
|
|
189
|
-
const segmentLength = Math.sqrt(dx * dx + dy * dy);
|
|
111
|
+
const b = ring[(index + 1) % ring.length];
|
|
112
|
+
const segmentLength = distance(a, b);
|
|
190
113
|
const divisions = Math.max(1, Math.ceil(segmentLength / step));
|
|
191
|
-
if (index
|
|
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++) {
|
|
114
|
+
for (let offset = 0; offset < divisions; offset += 1) if (index > 0 || offset > 0) {
|
|
198
115
|
const t = offset / divisions;
|
|
199
|
-
callback([a[0] +
|
|
200
|
-
}
|
|
116
|
+
callback([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
|
|
117
|
+
} else callback([a[0], a[1]]);
|
|
201
118
|
}
|
|
202
119
|
}
|
|
203
120
|
function ringBounds(ring) {
|
|
204
|
-
let minX =
|
|
205
|
-
let minY =
|
|
206
|
-
let maxX =
|
|
207
|
-
let maxY =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
121
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
122
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
123
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
124
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
125
|
+
ring.forEach((point) => {
|
|
126
|
+
minX = Math.min(minX, point[0]);
|
|
127
|
+
minY = Math.min(minY, point[1]);
|
|
128
|
+
maxX = Math.max(maxX, point[0]);
|
|
129
|
+
maxY = Math.max(maxY, point[1]);
|
|
130
|
+
});
|
|
215
131
|
return {
|
|
216
132
|
x: minX,
|
|
217
133
|
y: minY,
|
|
@@ -220,10 +136,7 @@ function ringBounds(ring) {
|
|
|
220
136
|
};
|
|
221
137
|
}
|
|
222
138
|
function translateRing(ring, tx, ty) {
|
|
223
|
-
|
|
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;
|
|
139
|
+
return ring.map((point) => [point[0] + tx, point[1] + ty]);
|
|
227
140
|
}
|
|
228
141
|
function translateRect(rect, tx, ty) {
|
|
229
142
|
return {
|
|
@@ -233,29 +146,21 @@ function translateRect(rect, tx, ty) {
|
|
|
233
146
|
h: rect.h
|
|
234
147
|
};
|
|
235
148
|
}
|
|
236
|
-
function copyRing
|
|
237
|
-
|
|
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;
|
|
149
|
+
function copyRing(ring) {
|
|
150
|
+
return ring.map((point) => [point[0], point[1]]);
|
|
241
151
|
}
|
|
242
152
|
function combineRects(rects) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
let
|
|
247
|
-
let
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
}
|
|
153
|
+
if (rects.length === 0) return null;
|
|
154
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
155
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
156
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
157
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
158
|
+
rects.forEach((rect) => {
|
|
159
|
+
minX = Math.min(minX, rect.x);
|
|
160
|
+
minY = Math.min(minY, rect.y);
|
|
161
|
+
maxX = Math.max(maxX, rect.x + rect.w);
|
|
162
|
+
maxY = Math.max(maxY, rect.y + rect.h);
|
|
163
|
+
});
|
|
259
164
|
return {
|
|
260
165
|
x: minX,
|
|
261
166
|
y: minY,
|
|
@@ -297,64 +202,43 @@ function ringPerimeter(ring) {
|
|
|
297
202
|
for (let index = 0; index < ring.length; index += 1) total += distance(ring[index], ring[(index + 1) % ring.length]);
|
|
298
203
|
return total;
|
|
299
204
|
}
|
|
300
|
-
function
|
|
301
|
-
const len = ring.length;
|
|
302
|
-
const cumulative = new Float64Array(len + 1);
|
|
303
|
-
let total = 0;
|
|
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);
|
|
205
|
+
function polylineLength(path) {
|
|
317
206
|
let total = 0;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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];
|
|
207
|
+
for (let index = 0; index < path.length - 1; index += 1) total += distance(path[index], path[index + 1]);
|
|
208
|
+
return total;
|
|
209
|
+
}
|
|
210
|
+
function pointAtClosedPathDistance(ring, distanceAlong) {
|
|
211
|
+
const perimeter = ringPerimeter(ring);
|
|
338
212
|
if (perimeter <= 1e-9) return [ring[0][0], ring[0][1]];
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
213
|
+
let remaining = mod(distanceAlong, perimeter);
|
|
214
|
+
for (let index = 0; index < ring.length; index += 1) {
|
|
215
|
+
const start = ring[index];
|
|
216
|
+
const end = ring[(index + 1) % ring.length];
|
|
217
|
+
const segmentLength = distance(start, end);
|
|
218
|
+
if (segmentLength <= 1e-9) continue;
|
|
219
|
+
if (remaining <= segmentLength) {
|
|
220
|
+
const t = remaining / segmentLength;
|
|
221
|
+
return [start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t];
|
|
222
|
+
}
|
|
223
|
+
remaining -= segmentLength;
|
|
224
|
+
}
|
|
225
|
+
return [ring[ring.length - 1][0], ring[ring.length - 1][1]];
|
|
226
|
+
}
|
|
227
|
+
function pointAtOpenPathDistance(path, distanceAlong) {
|
|
349
228
|
if (distanceAlong <= 0) return [path[0][0], path[0][1]];
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
229
|
+
let remaining = distanceAlong;
|
|
230
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
231
|
+
const start = path[index];
|
|
232
|
+
const end = path[index + 1];
|
|
233
|
+
const segmentLength = distance(start, end);
|
|
234
|
+
if (segmentLength <= 1e-9) continue;
|
|
235
|
+
if (remaining <= segmentLength) {
|
|
236
|
+
const t = remaining / segmentLength;
|
|
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]];
|
|
358
242
|
}
|
|
359
243
|
function segmentIntersectsRing(a, b, ring, ignoredEdges, epsilon) {
|
|
360
244
|
for (let index = 0; index < ring.length; index += 1) {
|
|
@@ -436,28 +320,23 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
436
320
|
current = null;
|
|
437
321
|
return;
|
|
438
322
|
}
|
|
439
|
-
if (!samePoint(current.cursor, current.start))
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
y: current.start.y
|
|
448
|
-
}
|
|
449
|
-
});
|
|
323
|
+
if (!samePoint(current.cursor, current.start)) {
|
|
324
|
+
current.segments.push({
|
|
325
|
+
type: "L",
|
|
326
|
+
from: cloneRawPoint(current.cursor),
|
|
327
|
+
to: cloneRawPoint(current.start)
|
|
328
|
+
});
|
|
329
|
+
current.cursor = cloneRawPoint(current.start);
|
|
330
|
+
}
|
|
450
331
|
contours.push({
|
|
451
332
|
id: contourId++,
|
|
452
|
-
start: current.start,
|
|
333
|
+
start: cloneRawPoint(current.start),
|
|
453
334
|
segments: current.segments
|
|
454
335
|
});
|
|
455
336
|
current = null;
|
|
456
337
|
};
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const type = cmd.type;
|
|
460
|
-
if (type === "M") {
|
|
338
|
+
commands.forEach((cmd) => {
|
|
339
|
+
if (cmd.type === "M") {
|
|
461
340
|
finishCurrentContour();
|
|
462
341
|
current = {
|
|
463
342
|
start: {
|
|
@@ -470,49 +349,47 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
470
349
|
},
|
|
471
350
|
segments: []
|
|
472
351
|
};
|
|
473
|
-
|
|
352
|
+
return;
|
|
474
353
|
}
|
|
475
|
-
if (!current)
|
|
476
|
-
if (type === "L") {
|
|
354
|
+
if (!current) return;
|
|
355
|
+
if (cmd.type === "L") {
|
|
477
356
|
const to = {
|
|
478
357
|
x: cmd.x,
|
|
479
358
|
y: cmd.y
|
|
480
359
|
};
|
|
481
360
|
current.segments.push({
|
|
482
361
|
type: "L",
|
|
483
|
-
from: current.cursor,
|
|
362
|
+
from: cloneRawPoint(current.cursor),
|
|
484
363
|
to
|
|
485
364
|
});
|
|
486
|
-
current.cursor = to;
|
|
487
|
-
|
|
365
|
+
current.cursor = cloneRawPoint(to);
|
|
366
|
+
return;
|
|
488
367
|
}
|
|
489
|
-
if (type === "Q") {
|
|
490
|
-
const from = current.cursor;
|
|
368
|
+
if (cmd.type === "Q") {
|
|
491
369
|
const to = {
|
|
492
370
|
x: cmd.x,
|
|
493
371
|
y: cmd.y
|
|
494
372
|
};
|
|
495
373
|
current.segments.push({
|
|
496
374
|
type: "Q",
|
|
497
|
-
from,
|
|
375
|
+
from: cloneRawPoint(current.cursor),
|
|
498
376
|
c1: {
|
|
499
377
|
x: cmd.x1,
|
|
500
378
|
y: cmd.y1
|
|
501
379
|
},
|
|
502
380
|
to
|
|
503
381
|
});
|
|
504
|
-
current.cursor = to;
|
|
505
|
-
|
|
382
|
+
current.cursor = cloneRawPoint(to);
|
|
383
|
+
return;
|
|
506
384
|
}
|
|
507
|
-
if (type === "C") {
|
|
508
|
-
const from = current.cursor;
|
|
385
|
+
if (cmd.type === "C") {
|
|
509
386
|
const to = {
|
|
510
387
|
x: cmd.x,
|
|
511
388
|
y: cmd.y
|
|
512
389
|
};
|
|
513
390
|
current.segments.push({
|
|
514
391
|
type: "C",
|
|
515
|
-
from,
|
|
392
|
+
from: cloneRawPoint(current.cursor),
|
|
516
393
|
c1: {
|
|
517
394
|
x: cmd.x1,
|
|
518
395
|
y: cmd.y1
|
|
@@ -523,11 +400,11 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
523
400
|
},
|
|
524
401
|
to
|
|
525
402
|
});
|
|
526
|
-
current.cursor = to;
|
|
527
|
-
|
|
403
|
+
current.cursor = cloneRawPoint(to);
|
|
404
|
+
return;
|
|
528
405
|
}
|
|
529
|
-
if (type === "Z") finishCurrentContour();
|
|
530
|
-
}
|
|
406
|
+
if (cmd.type === "Z") finishCurrentContour();
|
|
407
|
+
});
|
|
531
408
|
finishCurrentContour();
|
|
532
409
|
return {
|
|
533
410
|
glyphIndex: glyph.index,
|
|
@@ -551,25 +428,18 @@ function flattenGlyphTopology(topology, options) {
|
|
|
551
428
|
}
|
|
552
429
|
function flattenContour(contour, scale, tolerance) {
|
|
553
430
|
const ring = [];
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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;
|
|
431
|
+
pushUniquePoint(ring, toScreenPoint(contour.start, scale));
|
|
432
|
+
contour.segments.forEach((segment) => {
|
|
433
|
+
if (segment.type === "L") {
|
|
434
|
+
pushUniquePoint(ring, toScreenPoint(segment.to, scale));
|
|
435
|
+
return;
|
|
571
436
|
}
|
|
572
|
-
|
|
437
|
+
if (segment.type === "Q") {
|
|
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
|
+
});
|
|
573
443
|
if (ring.length > 1 && pointsEqual2D(ring[0], ring[ring.length - 1])) ring.pop();
|
|
574
444
|
if (ring.length < 3) return null;
|
|
575
445
|
const bbox = ringBounds(ring);
|
|
@@ -582,45 +452,37 @@ function flattenContour(contour, scale, tolerance) {
|
|
|
582
452
|
};
|
|
583
453
|
}
|
|
584
454
|
function classifyContours(contours) {
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
for (let i = 0; i < len; i++) result[i] = {
|
|
588
|
-
...contours[i],
|
|
455
|
+
const result = contours.map((contour) => ({
|
|
456
|
+
...contour,
|
|
589
457
|
parentId: null,
|
|
590
458
|
depth: 0,
|
|
591
459
|
role: "outer"
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
for (let ci = 0; ci < len; ci++) {
|
|
595
|
-
const child = result[ci];
|
|
596
|
-
const childAbsArea = Math.abs(child.area);
|
|
460
|
+
}));
|
|
461
|
+
result.forEach((child) => {
|
|
597
462
|
let parent = null;
|
|
598
463
|
let parentArea = Number.POSITIVE_INFINITY;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
464
|
+
result.forEach((candidate) => {
|
|
465
|
+
if (candidate.id === child.id) return;
|
|
466
|
+
if (Math.abs(candidate.area) <= Math.abs(child.area)) return;
|
|
467
|
+
if (!rectContainsRect(candidate.bbox, child.bbox)) return;
|
|
468
|
+
if (!pointInRing(child.ring[0], candidate.ring)) return;
|
|
469
|
+
const candidateArea = Math.abs(candidate.area);
|
|
470
|
+
if (candidateArea < parentArea) {
|
|
471
|
+
parent = candidate;
|
|
472
|
+
parentArea = candidateArea;
|
|
473
|
+
}
|
|
474
|
+
});
|
|
610
475
|
if (parent) child.parentId = parent.id;
|
|
611
|
-
}
|
|
476
|
+
});
|
|
612
477
|
const byId = new Map(result.map((contour) => [contour.id, contour]));
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
contour.depth = depth;
|
|
622
|
-
contour.role = depth % 2 === 0 ? "outer" : "hole";
|
|
623
|
-
}
|
|
478
|
+
const resolveDepth = (contour) => {
|
|
479
|
+
if (contour.parentId == null) return 0;
|
|
480
|
+
return resolveDepth(byId.get(contour.parentId)) + 1;
|
|
481
|
+
};
|
|
482
|
+
result.forEach((contour) => {
|
|
483
|
+
contour.depth = resolveDepth(contour);
|
|
484
|
+
contour.role = contour.depth % 2 === 0 ? "outer" : "hole";
|
|
485
|
+
});
|
|
624
486
|
return result;
|
|
625
487
|
}
|
|
626
488
|
function buildParts(contours) {
|
|
@@ -635,26 +497,16 @@ function buildParts(contours) {
|
|
|
635
497
|
});
|
|
636
498
|
}
|
|
637
499
|
function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
|
|
638
|
-
|
|
639
|
-
|
|
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] = {
|
|
500
|
+
return {
|
|
501
|
+
parts: geometry.parts.map((part, partIndex) => ({
|
|
647
502
|
outer: translateRing(part.outer, tx, ty),
|
|
648
503
|
holes: part.holes.map((hole) => translateRing(hole, tx, ty)),
|
|
649
504
|
bbox: translateRect(part.bbox, tx, ty),
|
|
650
505
|
area: part.area,
|
|
651
506
|
glyphPosition,
|
|
652
|
-
partIndex
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
for (let i = 0; i < contoursLen; i++) {
|
|
656
|
-
const contour = srcContours[i];
|
|
657
|
-
contours[i] = {
|
|
507
|
+
partIndex
|
|
508
|
+
})),
|
|
509
|
+
contours: geometry.contours.map((contour) => ({
|
|
658
510
|
id: `${glyphPosition}:${contour.id}`,
|
|
659
511
|
sourceId: contour.id,
|
|
660
512
|
glyphPosition,
|
|
@@ -664,11 +516,7 @@ function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
|
|
|
664
516
|
area: contour.area,
|
|
665
517
|
bbox: translateRect(contour.bbox, tx, ty),
|
|
666
518
|
ring: translateRing(contour.ring, tx, ty)
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
return {
|
|
670
|
-
parts,
|
|
671
|
-
contours,
|
|
519
|
+
})),
|
|
672
520
|
bbox: translateRect(geometry.bbox, tx, ty)
|
|
673
521
|
};
|
|
674
522
|
}
|
|
@@ -844,31 +692,26 @@ var PAShape = class {
|
|
|
844
692
|
function createTextShape(layout, opts, fontInstance) {
|
|
845
693
|
const parts = [];
|
|
846
694
|
const contours = [];
|
|
847
|
-
const
|
|
848
|
-
const
|
|
849
|
-
const glyphs = new Array(glyphCount);
|
|
850
|
-
const sourceLayoutGlyphs = Array.isArray(layoutGlyphs) ? layoutGlyphs.slice() : [];
|
|
695
|
+
const glyphs = [];
|
|
696
|
+
const sourceLayoutGlyphs = Array.isArray(layout.glyphs) ? layout.glyphs.slice() : [];
|
|
851
697
|
const variantOptions = {
|
|
852
698
|
openWidth: normalizePositive(opts.openWidth, 0),
|
|
853
699
|
step: normalizePositive(opts.step, 0)
|
|
854
700
|
};
|
|
855
|
-
|
|
856
|
-
const item = layoutGlyphs[glyphPosition];
|
|
701
|
+
layout.glyphs.forEach((item, glyphPosition) => {
|
|
857
702
|
const translated = translateGlyphGeometry(fontInstance._getGlyphGeometryVariant(item.glyph, opts), item.x, item.y, glyphPosition);
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
for (let j = 0; j < tContours.length; j++) contours.push(tContours[j]);
|
|
862
|
-
glyphs[glyphPosition] = {
|
|
703
|
+
parts.push(...translated.parts);
|
|
704
|
+
contours.push(...translated.contours);
|
|
705
|
+
glyphs.push({
|
|
863
706
|
position: glyphPosition,
|
|
864
707
|
glyphIndex: item.glyph.index,
|
|
865
708
|
x: item.x,
|
|
866
709
|
y: item.y,
|
|
867
710
|
size: item.size,
|
|
868
711
|
bbox: translated.bbox,
|
|
869
|
-
partCount:
|
|
870
|
-
};
|
|
871
|
-
}
|
|
712
|
+
partCount: translated.parts.length
|
|
713
|
+
});
|
|
714
|
+
});
|
|
872
715
|
const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
|
|
873
716
|
return new PAShape({
|
|
874
717
|
text: layout.text,
|
|
@@ -962,8 +805,8 @@ function createDerivedShape(shape, parts) {
|
|
|
962
805
|
}
|
|
963
806
|
function createRegionCollection(shape) {
|
|
964
807
|
return shape.parts.map((part) => ({
|
|
965
|
-
outer: copyRing
|
|
966
|
-
holes: part.holes.map((hole) => copyRing
|
|
808
|
+
outer: copyRing(part.outer),
|
|
809
|
+
holes: part.holes.map((hole) => copyRing(hole)),
|
|
967
810
|
bbox: { ...part.bbox }
|
|
968
811
|
}));
|
|
969
812
|
}
|
|
@@ -973,8 +816,8 @@ function copyParts(parts) {
|
|
|
973
816
|
function copyPart(part) {
|
|
974
817
|
return {
|
|
975
818
|
...part,
|
|
976
|
-
outer: copyRing
|
|
977
|
-
holes: part.holes.map((hole) => copyRing
|
|
819
|
+
outer: copyRing(part.outer),
|
|
820
|
+
holes: part.holes.map((hole) => copyRing(hole)),
|
|
978
821
|
anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
|
|
979
822
|
bbox: { ...part.bbox }
|
|
980
823
|
};
|
|
@@ -983,7 +826,7 @@ function copyContours(contours) {
|
|
|
983
826
|
return contours.map((contour) => ({
|
|
984
827
|
...contour,
|
|
985
828
|
bbox: { ...contour.bbox },
|
|
986
|
-
ring: copyRing
|
|
829
|
+
ring: copyRing(contour.ring)
|
|
987
830
|
}));
|
|
988
831
|
}
|
|
989
832
|
function groupPartsByGlyphPosition(parts) {
|
|
@@ -1099,108 +942,76 @@ function resolveGeometryEpsilon(width) {
|
|
|
1099
942
|
}
|
|
1100
943
|
function openPartWithSlit(part, width, epsilon) {
|
|
1101
944
|
if (!part.holes || part.holes.length === 0) return copyPart(part);
|
|
1102
|
-
let outer = copyRing
|
|
1103
|
-
const holes = part.holes.map((hole) => copyRing
|
|
945
|
+
let outer = copyRing(part.outer);
|
|
946
|
+
const holes = part.holes.map((hole) => copyRing(hole));
|
|
1104
947
|
const anchors = [];
|
|
1105
|
-
const blockers = [];
|
|
1106
948
|
while (holes.length > 0) {
|
|
1107
949
|
let selectedHoleIndex = -1;
|
|
1108
950
|
let selectedBridge = null;
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const candidate = findBestHoleBridge(outer, holes[hi], blockers, epsilon);
|
|
1114
|
-
if (candidate && candidate.distance < selectedDist) {
|
|
951
|
+
holes.forEach((hole, holeIndex) => {
|
|
952
|
+
const blockers = holes.filter((_, index) => index !== holeIndex);
|
|
953
|
+
const candidate = findBestHoleBridge(outer, hole, blockers, epsilon);
|
|
954
|
+
if (candidate && (!selectedBridge || candidate.distance < selectedBridge.distance)) {
|
|
1115
955
|
selectedBridge = candidate;
|
|
1116
|
-
selectedHoleIndex =
|
|
1117
|
-
selectedDist = candidate.distance;
|
|
956
|
+
selectedHoleIndex = holeIndex;
|
|
1118
957
|
}
|
|
1119
|
-
}
|
|
958
|
+
});
|
|
1120
959
|
if (!selectedBridge || selectedHoleIndex === -1) break;
|
|
1121
960
|
const [hole] = holes.splice(selectedHoleIndex, 1);
|
|
1122
961
|
const merged = mergeHoleIntoOuter(outer, hole, selectedBridge, width, epsilon);
|
|
1123
962
|
outer = merged.ring;
|
|
1124
|
-
|
|
1125
|
-
for (let i = 0; i < mergedAnchors.length; i++) anchors.push(mergedAnchors[i]);
|
|
963
|
+
anchors.push(...merged.anchors);
|
|
1126
964
|
}
|
|
1127
|
-
let holeArea = 0;
|
|
1128
|
-
for (let i = 0; i < holes.length; i++) holeArea += Math.abs(signedArea(holes[i]));
|
|
1129
965
|
return {
|
|
1130
966
|
...part,
|
|
1131
967
|
outer,
|
|
1132
968
|
holes,
|
|
1133
969
|
anchors,
|
|
1134
970
|
bbox: ringBounds(outer),
|
|
1135
|
-
area: Math.abs(signedArea(outer)) -
|
|
971
|
+
area: Math.abs(signedArea(outer)) - holes.reduce((sum, hole) => sum + Math.abs(signedArea(hole)), 0)
|
|
1136
972
|
};
|
|
1137
973
|
}
|
|
1138
974
|
function resamplePart(part, step) {
|
|
1139
975
|
const outer = resampleRing(part.outer, step, part.anchors ?? []);
|
|
1140
|
-
const
|
|
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
|
-
}
|
|
976
|
+
const holes = part.holes.map((hole) => resampleRing(hole, step));
|
|
1147
977
|
return {
|
|
1148
978
|
...part,
|
|
1149
979
|
outer,
|
|
1150
980
|
holes,
|
|
1151
981
|
anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
|
|
1152
982
|
bbox: ringBounds(outer),
|
|
1153
|
-
area: Math.abs(signedArea(outer)) -
|
|
983
|
+
area: Math.abs(signedArea(outer)) - holes.reduce((sum, hole) => sum + Math.abs(signedArea(hole)), 0)
|
|
1154
984
|
};
|
|
1155
985
|
}
|
|
1156
986
|
function findBestHoleBridge(outer, hole, blockers, epsilon) {
|
|
1157
987
|
let best = null;
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
|
988
|
+
outer.forEach((outerPoint, outerIndex) => {
|
|
989
|
+
hole.forEach((holePoint, holeIndex) => {
|
|
990
|
+
if (!isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon)) return;
|
|
991
|
+
const candidate = {
|
|
992
|
+
outerIndex,
|
|
993
|
+
holeIndex,
|
|
994
|
+
distance: distance(outerPoint, holePoint)
|
|
1173
995
|
};
|
|
1174
|
-
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
996
|
+
if (!best || candidate.distance < best.distance) best = candidate;
|
|
997
|
+
});
|
|
998
|
+
});
|
|
1177
999
|
if (best) return best;
|
|
1178
1000
|
return findNearestBridge(outer, hole);
|
|
1179
1001
|
}
|
|
1180
1002
|
function findNearestBridge(outer, hole) {
|
|
1181
|
-
let
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
-
};
|
|
1003
|
+
let best = null;
|
|
1004
|
+
outer.forEach((outerPoint, outerIndex) => {
|
|
1005
|
+
hole.forEach((holePoint, holeIndex) => {
|
|
1006
|
+
const candidate = {
|
|
1007
|
+
outerIndex,
|
|
1008
|
+
holeIndex,
|
|
1009
|
+
distance: distance(outerPoint, holePoint)
|
|
1010
|
+
};
|
|
1011
|
+
if (!best || candidate.distance < best.distance) best = candidate;
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
return best;
|
|
1204
1015
|
}
|
|
1205
1016
|
function isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon) {
|
|
1206
1017
|
if (distance(outerPoint, holePoint) <= epsilon) return false;
|
|
@@ -1327,7 +1138,7 @@ function normalizeCutLocation(name, ring, cut, epsilon) {
|
|
|
1327
1138
|
};
|
|
1328
1139
|
}
|
|
1329
1140
|
function ringPath(ring, startIndex, endIndex) {
|
|
1330
|
-
if (startIndex == null || endIndex == null) return copyRing
|
|
1141
|
+
if (startIndex == null || endIndex == null) return copyRing(ring);
|
|
1331
1142
|
const path = [ring[startIndex]];
|
|
1332
1143
|
let cursor = startIndex;
|
|
1333
1144
|
while (cursor !== endIndex) {
|
|
@@ -1342,7 +1153,7 @@ function appendPath(target, path, epsilon) {
|
|
|
1342
1153
|
});
|
|
1343
1154
|
}
|
|
1344
1155
|
function resampleRing(ring, step, anchors = []) {
|
|
1345
|
-
if (ring.length < 3) return copyRing
|
|
1156
|
+
if (ring.length < 3) return copyRing(ring);
|
|
1346
1157
|
const normalizedAnchors = normalizeAnchorPoints(ring, anchors);
|
|
1347
1158
|
if (normalizedAnchors.length >= 2) return resampleRingWithAnchors(ring, step, normalizedAnchors);
|
|
1348
1159
|
return resampleClosedPath(ring, step);
|
|
@@ -1363,32 +1174,30 @@ function resampleRingWithAnchors(ring, step, anchorIndices) {
|
|
|
1363
1174
|
appendPath(result, resampleOpenPath(ringPath(ring, startIndex, endIndex), step), 1e-6);
|
|
1364
1175
|
}
|
|
1365
1176
|
if (result.length > 1 && pointsAlmostEqual2D(result[0], result[result.length - 1], 1e-6)) result.pop();
|
|
1366
|
-
if (result.length < 3 || result.length >= ring.length) return copyRing
|
|
1177
|
+
if (result.length < 3 || result.length >= ring.length) return copyRing(ring);
|
|
1367
1178
|
return result;
|
|
1368
1179
|
}
|
|
1369
1180
|
function resampleClosedPath(ring, step) {
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
if (perimeter <= 1e-9) return copyRing$1(ring);
|
|
1181
|
+
const perimeter = ringPerimeter(ring);
|
|
1182
|
+
if (perimeter <= 1e-9) return copyRing(ring);
|
|
1373
1183
|
const pointCount = Math.max(3, Math.round(perimeter / step));
|
|
1374
|
-
if (pointCount >= ring.length) return copyRing
|
|
1184
|
+
if (pointCount >= ring.length) return copyRing(ring);
|
|
1375
1185
|
const sampled = [];
|
|
1376
|
-
for (let index = 0; index < pointCount; index
|
|
1377
|
-
const point =
|
|
1186
|
+
for (let index = 0; index < pointCount; index += 1) {
|
|
1187
|
+
const point = pointAtClosedPathDistance(ring, perimeter * index / pointCount);
|
|
1378
1188
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1379
1189
|
}
|
|
1380
|
-
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing
|
|
1190
|
+
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing(ring);
|
|
1381
1191
|
return sampled;
|
|
1382
1192
|
}
|
|
1383
1193
|
function resampleOpenPath(path, step) {
|
|
1384
1194
|
if (path.length <= 2) return path.map((point) => [point[0], point[1]]);
|
|
1385
|
-
const
|
|
1386
|
-
const length = cumulative[path.length - 1];
|
|
1195
|
+
const length = polylineLength(path);
|
|
1387
1196
|
if (length <= step) return [[path[0][0], path[0][1]], [path[path.length - 1][0], path[path.length - 1][1]]];
|
|
1388
1197
|
const divisionCount = Math.max(1, Math.round(length / step));
|
|
1389
1198
|
const sampled = [];
|
|
1390
|
-
for (let index = 0; index <= divisionCount; index
|
|
1391
|
-
const point =
|
|
1199
|
+
for (let index = 0; index <= divisionCount; index += 1) {
|
|
1200
|
+
const point = pointAtOpenPathDistance(path, length * index / divisionCount);
|
|
1392
1201
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1393
1202
|
}
|
|
1394
1203
|
return sampled;
|
|
@@ -3760,25 +3569,9 @@ function truncateLineToWidth(line, maxWidth, measureWidth, suffix) {
|
|
|
3760
3569
|
width: 0,
|
|
3761
3570
|
hardBreak: false
|
|
3762
3571
|
};
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
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;
|
|
3572
|
+
let nextText = trimTrailingWhitespace(line.text);
|
|
3573
|
+
while (nextText.length > 0 && measureWidth(`${nextText}${suffixText}`) > maxWidth + JUSTIFY_EPSILON) nextText = trimLastGrapheme(nextText);
|
|
3574
|
+
const text = `${nextText}${suffixText}`;
|
|
3782
3575
|
return {
|
|
3783
3576
|
...line,
|
|
3784
3577
|
text,
|
|
@@ -4315,6 +4108,12 @@ function getGraphemeSegmenter() {
|
|
|
4315
4108
|
function trimTrailingWhitespace(value) {
|
|
4316
4109
|
return value.replace(/\s+$/u, "");
|
|
4317
4110
|
}
|
|
4111
|
+
function trimLastGrapheme(value) {
|
|
4112
|
+
const segmenter = getGraphemeSegmenter();
|
|
4113
|
+
const graphemes = Array.from(segmenter.segment(value), (segment) => segment.segment);
|
|
4114
|
+
graphemes.pop();
|
|
4115
|
+
return graphemes.join("");
|
|
4116
|
+
}
|
|
4318
4117
|
function splitPreservingWhitespace(value) {
|
|
4319
4118
|
return value.split(/(\s+)/u).filter((token) => token.length > 0);
|
|
4320
4119
|
}
|