pa_font 0.2.7 → 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 +230 -431
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +230 -431
- 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 {
|
|
@@ -234,28 +147,20 @@ function translateRect(rect, tx, ty) {
|
|
|
234
147
|
};
|
|
235
148
|
}
|
|
236
149
|
function copyRing(ring) {
|
|
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;
|
|
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,
|
|
@@ -1102,105 +945,73 @@ function openPartWithSlit(part, width, epsilon) {
|
|
|
1102
945
|
let outer = copyRing(part.outer);
|
|
1103
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;
|
|
@@ -1367,14 +1178,13 @@ function resampleRingWithAnchors(ring, step, anchorIndices) {
|
|
|
1367
1178
|
return result;
|
|
1368
1179
|
}
|
|
1369
1180
|
function resampleClosedPath(ring, step) {
|
|
1370
|
-
const
|
|
1371
|
-
const perimeter = cumulative[ring.length];
|
|
1181
|
+
const perimeter = ringPerimeter(ring);
|
|
1372
1182
|
if (perimeter <= 1e-9) return copyRing(ring);
|
|
1373
1183
|
const pointCount = Math.max(3, Math.round(perimeter / step));
|
|
1374
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
1190
|
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing(ring);
|
|
@@ -1382,13 +1192,12 @@ function resampleClosedPath(ring, step) {
|
|
|
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
|
}
|