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.cjs
CHANGED
|
@@ -7,127 +7,52 @@ 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
|
+
}
|
|
10
16
|
function pushUniquePoint(points, point) {
|
|
11
17
|
if (points.length === 0 || !pointsEqual2D(points[points.length - 1], point)) points.push(point);
|
|
12
18
|
}
|
|
13
|
-
function flattenQuadratic(p0, p1, p2, tolerance, out) {
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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;
|
|
19
|
+
function flattenQuadratic(p0, p1, p2, tolerance, out, depth = 0) {
|
|
20
|
+
if (depth >= 12 || quadFlatness(p0, p1, p2) <= tolerance) {
|
|
21
|
+
pushUniquePoint(out, p2);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const p01 = midpoint(p0, p1);
|
|
25
|
+
const p12 = midpoint(p1, p2);
|
|
26
|
+
const p012 = midpoint(p01, p12);
|
|
27
|
+
flattenQuadratic(p0, p01, p012, tolerance, out, depth + 1);
|
|
28
|
+
flattenQuadratic(p012, p12, p2, tolerance, out, depth + 1);
|
|
29
|
+
}
|
|
30
|
+
function flattenCubic(p0, p1, p2, p3, tolerance, out, depth = 0) {
|
|
31
|
+
if (depth >= 12 || cubicFlatness(p0, p1, p2, p3) <= tolerance) {
|
|
32
|
+
pushUniquePoint(out, p3);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const p01 = midpoint(p0, p1);
|
|
36
|
+
const p12 = midpoint(p1, p2);
|
|
37
|
+
const p23 = midpoint(p2, p3);
|
|
38
|
+
const p012 = midpoint(p01, p12);
|
|
39
|
+
const p123 = midpoint(p12, p23);
|
|
40
|
+
const p0123 = midpoint(p012, p123);
|
|
41
|
+
flattenCubic(p0, p01, p012, p0123, tolerance, out, depth + 1);
|
|
42
|
+
flattenCubic(p0123, p123, p23, p3, tolerance, out, depth + 1);
|
|
43
|
+
}
|
|
44
|
+
function quadFlatness(p0, p1, p2) {
|
|
45
|
+
return pointToLineDistance(p1, p0, p2);
|
|
46
|
+
}
|
|
47
|
+
function cubicFlatness(p0, p1, p2, p3) {
|
|
48
|
+
return Math.max(pointToLineDistance(p1, p0, p3), pointToLineDistance(p2, p0, p3));
|
|
49
|
+
}
|
|
50
|
+
function pointToLineDistance(point, lineStart, lineEnd) {
|
|
51
|
+
const dx = lineEnd[0] - lineStart[0];
|
|
52
|
+
const dy = lineEnd[1] - lineStart[1];
|
|
53
|
+
const lengthSq = dx * dx + dy * dy;
|
|
54
|
+
if (lengthSq === 0) return distance(point, lineStart);
|
|
55
|
+
return Math.abs(dx * (lineStart[1] - point[1]) - (lineStart[0] - point[0]) * dy) / Math.sqrt(lengthSq);
|
|
131
56
|
}
|
|
132
57
|
function midpoint(a, b) {
|
|
133
58
|
return [(a[0] + b[0]) * .5, (a[1] + b[1]) * .5];
|
|
@@ -185,37 +110,28 @@ function distance(a, b) {
|
|
|
185
110
|
return Math.sqrt(dx * dx + dy * dy);
|
|
186
111
|
}
|
|
187
112
|
function sampleRing(ring, step, callback) {
|
|
188
|
-
|
|
189
|
-
for (let index = 0; index < len; index++) {
|
|
113
|
+
for (let index = 0; index < ring.length; index += 1) {
|
|
190
114
|
const a = ring[index];
|
|
191
|
-
const b = ring[(index + 1) %
|
|
192
|
-
const
|
|
193
|
-
const segmentLength = Math.sqrt(dx * dx + dy * dy);
|
|
115
|
+
const b = ring[(index + 1) % ring.length];
|
|
116
|
+
const segmentLength = distance(a, b);
|
|
194
117
|
const divisions = Math.max(1, Math.ceil(segmentLength / step));
|
|
195
|
-
if (index
|
|
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++) {
|
|
118
|
+
for (let offset = 0; offset < divisions; offset += 1) if (index > 0 || offset > 0) {
|
|
202
119
|
const t = offset / divisions;
|
|
203
|
-
callback([a[0] +
|
|
204
|
-
}
|
|
120
|
+
callback([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
|
|
121
|
+
} else callback([a[0], a[1]]);
|
|
205
122
|
}
|
|
206
123
|
}
|
|
207
124
|
function ringBounds(ring) {
|
|
208
|
-
let minX =
|
|
209
|
-
let minY =
|
|
210
|
-
let maxX =
|
|
211
|
-
let maxY =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
125
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
126
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
127
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
128
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
129
|
+
ring.forEach((point) => {
|
|
130
|
+
minX = Math.min(minX, point[0]);
|
|
131
|
+
minY = Math.min(minY, point[1]);
|
|
132
|
+
maxX = Math.max(maxX, point[0]);
|
|
133
|
+
maxY = Math.max(maxY, point[1]);
|
|
134
|
+
});
|
|
219
135
|
return {
|
|
220
136
|
x: minX,
|
|
221
137
|
y: minY,
|
|
@@ -224,10 +140,7 @@ function ringBounds(ring) {
|
|
|
224
140
|
};
|
|
225
141
|
}
|
|
226
142
|
function translateRing(ring, tx, ty) {
|
|
227
|
-
|
|
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;
|
|
143
|
+
return ring.map((point) => [point[0] + tx, point[1] + ty]);
|
|
231
144
|
}
|
|
232
145
|
function translateRect(rect, tx, ty) {
|
|
233
146
|
return {
|
|
@@ -238,28 +151,20 @@ function translateRect(rect, tx, ty) {
|
|
|
238
151
|
};
|
|
239
152
|
}
|
|
240
153
|
function copyRing(ring) {
|
|
241
|
-
|
|
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;
|
|
154
|
+
return ring.map((point) => [point[0], point[1]]);
|
|
245
155
|
}
|
|
246
156
|
function combineRects(rects) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
let
|
|
251
|
-
let
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
}
|
|
157
|
+
if (rects.length === 0) return null;
|
|
158
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
159
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
160
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
161
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
162
|
+
rects.forEach((rect) => {
|
|
163
|
+
minX = Math.min(minX, rect.x);
|
|
164
|
+
minY = Math.min(minY, rect.y);
|
|
165
|
+
maxX = Math.max(maxX, rect.x + rect.w);
|
|
166
|
+
maxY = Math.max(maxY, rect.y + rect.h);
|
|
167
|
+
});
|
|
263
168
|
return {
|
|
264
169
|
x: minX,
|
|
265
170
|
y: minY,
|
|
@@ -301,64 +206,43 @@ function ringPerimeter(ring) {
|
|
|
301
206
|
for (let index = 0; index < ring.length; index += 1) total += distance(ring[index], ring[(index + 1) % ring.length]);
|
|
302
207
|
return total;
|
|
303
208
|
}
|
|
304
|
-
function
|
|
305
|
-
const len = ring.length;
|
|
306
|
-
const cumulative = new Float64Array(len + 1);
|
|
307
|
-
let total = 0;
|
|
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);
|
|
209
|
+
function polylineLength(path) {
|
|
321
210
|
let total = 0;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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];
|
|
211
|
+
for (let index = 0; index < path.length - 1; index += 1) total += distance(path[index], path[index + 1]);
|
|
212
|
+
return total;
|
|
213
|
+
}
|
|
214
|
+
function pointAtClosedPathDistance(ring, distanceAlong) {
|
|
215
|
+
const perimeter = ringPerimeter(ring);
|
|
342
216
|
if (perimeter <= 1e-9) return [ring[0][0], ring[0][1]];
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
217
|
+
let remaining = mod(distanceAlong, perimeter);
|
|
218
|
+
for (let index = 0; index < ring.length; index += 1) {
|
|
219
|
+
const start = ring[index];
|
|
220
|
+
const end = ring[(index + 1) % ring.length];
|
|
221
|
+
const segmentLength = distance(start, end);
|
|
222
|
+
if (segmentLength <= 1e-9) continue;
|
|
223
|
+
if (remaining <= segmentLength) {
|
|
224
|
+
const t = remaining / segmentLength;
|
|
225
|
+
return [start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t];
|
|
226
|
+
}
|
|
227
|
+
remaining -= segmentLength;
|
|
228
|
+
}
|
|
229
|
+
return [ring[ring.length - 1][0], ring[ring.length - 1][1]];
|
|
230
|
+
}
|
|
231
|
+
function pointAtOpenPathDistance(path, distanceAlong) {
|
|
353
232
|
if (distanceAlong <= 0) return [path[0][0], path[0][1]];
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
233
|
+
let remaining = distanceAlong;
|
|
234
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
235
|
+
const start = path[index];
|
|
236
|
+
const end = path[index + 1];
|
|
237
|
+
const segmentLength = distance(start, end);
|
|
238
|
+
if (segmentLength <= 1e-9) continue;
|
|
239
|
+
if (remaining <= segmentLength) {
|
|
240
|
+
const t = remaining / segmentLength;
|
|
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]];
|
|
362
246
|
}
|
|
363
247
|
function segmentIntersectsRing(a, b, ring, ignoredEdges, epsilon) {
|
|
364
248
|
for (let index = 0; index < ring.length; index += 1) {
|
|
@@ -440,28 +324,23 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
440
324
|
current = null;
|
|
441
325
|
return;
|
|
442
326
|
}
|
|
443
|
-
if (!samePoint(current.cursor, current.start))
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
y: current.start.y
|
|
452
|
-
}
|
|
453
|
-
});
|
|
327
|
+
if (!samePoint(current.cursor, current.start)) {
|
|
328
|
+
current.segments.push({
|
|
329
|
+
type: "L",
|
|
330
|
+
from: cloneRawPoint(current.cursor),
|
|
331
|
+
to: cloneRawPoint(current.start)
|
|
332
|
+
});
|
|
333
|
+
current.cursor = cloneRawPoint(current.start);
|
|
334
|
+
}
|
|
454
335
|
contours.push({
|
|
455
336
|
id: contourId++,
|
|
456
|
-
start: current.start,
|
|
337
|
+
start: cloneRawPoint(current.start),
|
|
457
338
|
segments: current.segments
|
|
458
339
|
});
|
|
459
340
|
current = null;
|
|
460
341
|
};
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const type = cmd.type;
|
|
464
|
-
if (type === "M") {
|
|
342
|
+
commands.forEach((cmd) => {
|
|
343
|
+
if (cmd.type === "M") {
|
|
465
344
|
finishCurrentContour();
|
|
466
345
|
current = {
|
|
467
346
|
start: {
|
|
@@ -474,49 +353,47 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
474
353
|
},
|
|
475
354
|
segments: []
|
|
476
355
|
};
|
|
477
|
-
|
|
356
|
+
return;
|
|
478
357
|
}
|
|
479
|
-
if (!current)
|
|
480
|
-
if (type === "L") {
|
|
358
|
+
if (!current) return;
|
|
359
|
+
if (cmd.type === "L") {
|
|
481
360
|
const to = {
|
|
482
361
|
x: cmd.x,
|
|
483
362
|
y: cmd.y
|
|
484
363
|
};
|
|
485
364
|
current.segments.push({
|
|
486
365
|
type: "L",
|
|
487
|
-
from: current.cursor,
|
|
366
|
+
from: cloneRawPoint(current.cursor),
|
|
488
367
|
to
|
|
489
368
|
});
|
|
490
|
-
current.cursor = to;
|
|
491
|
-
|
|
369
|
+
current.cursor = cloneRawPoint(to);
|
|
370
|
+
return;
|
|
492
371
|
}
|
|
493
|
-
if (type === "Q") {
|
|
494
|
-
const from = current.cursor;
|
|
372
|
+
if (cmd.type === "Q") {
|
|
495
373
|
const to = {
|
|
496
374
|
x: cmd.x,
|
|
497
375
|
y: cmd.y
|
|
498
376
|
};
|
|
499
377
|
current.segments.push({
|
|
500
378
|
type: "Q",
|
|
501
|
-
from,
|
|
379
|
+
from: cloneRawPoint(current.cursor),
|
|
502
380
|
c1: {
|
|
503
381
|
x: cmd.x1,
|
|
504
382
|
y: cmd.y1
|
|
505
383
|
},
|
|
506
384
|
to
|
|
507
385
|
});
|
|
508
|
-
current.cursor = to;
|
|
509
|
-
|
|
386
|
+
current.cursor = cloneRawPoint(to);
|
|
387
|
+
return;
|
|
510
388
|
}
|
|
511
|
-
if (type === "C") {
|
|
512
|
-
const from = current.cursor;
|
|
389
|
+
if (cmd.type === "C") {
|
|
513
390
|
const to = {
|
|
514
391
|
x: cmd.x,
|
|
515
392
|
y: cmd.y
|
|
516
393
|
};
|
|
517
394
|
current.segments.push({
|
|
518
395
|
type: "C",
|
|
519
|
-
from,
|
|
396
|
+
from: cloneRawPoint(current.cursor),
|
|
520
397
|
c1: {
|
|
521
398
|
x: cmd.x1,
|
|
522
399
|
y: cmd.y1
|
|
@@ -527,11 +404,11 @@ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
|
|
|
527
404
|
},
|
|
528
405
|
to
|
|
529
406
|
});
|
|
530
|
-
current.cursor = to;
|
|
531
|
-
|
|
407
|
+
current.cursor = cloneRawPoint(to);
|
|
408
|
+
return;
|
|
532
409
|
}
|
|
533
|
-
if (type === "Z") finishCurrentContour();
|
|
534
|
-
}
|
|
410
|
+
if (cmd.type === "Z") finishCurrentContour();
|
|
411
|
+
});
|
|
535
412
|
finishCurrentContour();
|
|
536
413
|
return {
|
|
537
414
|
glyphIndex: glyph.index,
|
|
@@ -555,25 +432,18 @@ function flattenGlyphTopology(topology, options) {
|
|
|
555
432
|
}
|
|
556
433
|
function flattenContour(contour, scale, tolerance) {
|
|
557
434
|
const ring = [];
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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;
|
|
435
|
+
pushUniquePoint(ring, toScreenPoint(contour.start, scale));
|
|
436
|
+
contour.segments.forEach((segment) => {
|
|
437
|
+
if (segment.type === "L") {
|
|
438
|
+
pushUniquePoint(ring, toScreenPoint(segment.to, scale));
|
|
439
|
+
return;
|
|
575
440
|
}
|
|
576
|
-
|
|
441
|
+
if (segment.type === "Q") {
|
|
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
|
+
});
|
|
577
447
|
if (ring.length > 1 && pointsEqual2D(ring[0], ring[ring.length - 1])) ring.pop();
|
|
578
448
|
if (ring.length < 3) return null;
|
|
579
449
|
const bbox = ringBounds(ring);
|
|
@@ -586,45 +456,37 @@ function flattenContour(contour, scale, tolerance) {
|
|
|
586
456
|
};
|
|
587
457
|
}
|
|
588
458
|
function classifyContours(contours) {
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
for (let i = 0; i < len; i++) result[i] = {
|
|
592
|
-
...contours[i],
|
|
459
|
+
const result = contours.map((contour) => ({
|
|
460
|
+
...contour,
|
|
593
461
|
parentId: null,
|
|
594
462
|
depth: 0,
|
|
595
463
|
role: "outer"
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
for (let ci = 0; ci < len; ci++) {
|
|
599
|
-
const child = result[ci];
|
|
600
|
-
const childAbsArea = Math.abs(child.area);
|
|
464
|
+
}));
|
|
465
|
+
result.forEach((child) => {
|
|
601
466
|
let parent = null;
|
|
602
467
|
let parentArea = Number.POSITIVE_INFINITY;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
468
|
+
result.forEach((candidate) => {
|
|
469
|
+
if (candidate.id === child.id) return;
|
|
470
|
+
if (Math.abs(candidate.area) <= Math.abs(child.area)) return;
|
|
471
|
+
if (!rectContainsRect(candidate.bbox, child.bbox)) return;
|
|
472
|
+
if (!pointInRing(child.ring[0], candidate.ring)) return;
|
|
473
|
+
const candidateArea = Math.abs(candidate.area);
|
|
474
|
+
if (candidateArea < parentArea) {
|
|
475
|
+
parent = candidate;
|
|
476
|
+
parentArea = candidateArea;
|
|
477
|
+
}
|
|
478
|
+
});
|
|
614
479
|
if (parent) child.parentId = parent.id;
|
|
615
|
-
}
|
|
480
|
+
});
|
|
616
481
|
const byId = new Map(result.map((contour) => [contour.id, contour]));
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
contour.depth = depth;
|
|
626
|
-
contour.role = depth % 2 === 0 ? "outer" : "hole";
|
|
627
|
-
}
|
|
482
|
+
const resolveDepth = (contour) => {
|
|
483
|
+
if (contour.parentId == null) return 0;
|
|
484
|
+
return resolveDepth(byId.get(contour.parentId)) + 1;
|
|
485
|
+
};
|
|
486
|
+
result.forEach((contour) => {
|
|
487
|
+
contour.depth = resolveDepth(contour);
|
|
488
|
+
contour.role = contour.depth % 2 === 0 ? "outer" : "hole";
|
|
489
|
+
});
|
|
628
490
|
return result;
|
|
629
491
|
}
|
|
630
492
|
function buildParts(contours) {
|
|
@@ -639,26 +501,16 @@ function buildParts(contours) {
|
|
|
639
501
|
});
|
|
640
502
|
}
|
|
641
503
|
function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
|
|
642
|
-
|
|
643
|
-
|
|
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] = {
|
|
504
|
+
return {
|
|
505
|
+
parts: geometry.parts.map((part, partIndex) => ({
|
|
651
506
|
outer: translateRing(part.outer, tx, ty),
|
|
652
507
|
holes: part.holes.map((hole) => translateRing(hole, tx, ty)),
|
|
653
508
|
bbox: translateRect(part.bbox, tx, ty),
|
|
654
509
|
area: part.area,
|
|
655
510
|
glyphPosition,
|
|
656
|
-
partIndex
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
for (let i = 0; i < contoursLen; i++) {
|
|
660
|
-
const contour = srcContours[i];
|
|
661
|
-
contours[i] = {
|
|
511
|
+
partIndex
|
|
512
|
+
})),
|
|
513
|
+
contours: geometry.contours.map((contour) => ({
|
|
662
514
|
id: `${glyphPosition}:${contour.id}`,
|
|
663
515
|
sourceId: contour.id,
|
|
664
516
|
glyphPosition,
|
|
@@ -668,11 +520,7 @@ function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
|
|
|
668
520
|
area: contour.area,
|
|
669
521
|
bbox: translateRect(contour.bbox, tx, ty),
|
|
670
522
|
ring: translateRing(contour.ring, tx, ty)
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return {
|
|
674
|
-
parts,
|
|
675
|
-
contours,
|
|
523
|
+
})),
|
|
676
524
|
bbox: translateRect(geometry.bbox, tx, ty)
|
|
677
525
|
};
|
|
678
526
|
}
|
|
@@ -848,31 +696,26 @@ var PAShape = class {
|
|
|
848
696
|
function createTextShape(layout, opts, fontInstance) {
|
|
849
697
|
const parts = [];
|
|
850
698
|
const contours = [];
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
const glyphs = new Array(glyphCount);
|
|
854
|
-
const sourceLayoutGlyphs = Array.isArray(layoutGlyphs) ? layoutGlyphs.slice() : [];
|
|
699
|
+
const glyphs = [];
|
|
700
|
+
const sourceLayoutGlyphs = Array.isArray(layout.glyphs) ? layout.glyphs.slice() : [];
|
|
855
701
|
const variantOptions = {
|
|
856
702
|
openWidth: normalizePositive(opts.openWidth, 0),
|
|
857
703
|
step: normalizePositive(opts.step, 0)
|
|
858
704
|
};
|
|
859
|
-
|
|
860
|
-
const item = layoutGlyphs[glyphPosition];
|
|
705
|
+
layout.glyphs.forEach((item, glyphPosition) => {
|
|
861
706
|
const translated = translateGlyphGeometry(fontInstance._getGlyphGeometryVariant(item.glyph, opts), item.x, item.y, glyphPosition);
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
for (let j = 0; j < tContours.length; j++) contours.push(tContours[j]);
|
|
866
|
-
glyphs[glyphPosition] = {
|
|
707
|
+
parts.push(...translated.parts);
|
|
708
|
+
contours.push(...translated.contours);
|
|
709
|
+
glyphs.push({
|
|
867
710
|
position: glyphPosition,
|
|
868
711
|
glyphIndex: item.glyph.index,
|
|
869
712
|
x: item.x,
|
|
870
713
|
y: item.y,
|
|
871
714
|
size: item.size,
|
|
872
715
|
bbox: translated.bbox,
|
|
873
|
-
partCount:
|
|
874
|
-
};
|
|
875
|
-
}
|
|
716
|
+
partCount: translated.parts.length
|
|
717
|
+
});
|
|
718
|
+
});
|
|
876
719
|
const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
|
|
877
720
|
return new PAShape({
|
|
878
721
|
text: layout.text,
|
|
@@ -1106,105 +949,73 @@ function openPartWithSlit(part, width, epsilon) {
|
|
|
1106
949
|
let outer = copyRing(part.outer);
|
|
1107
950
|
const holes = part.holes.map((hole) => copyRing(hole));
|
|
1108
951
|
const anchors = [];
|
|
1109
|
-
const blockers = [];
|
|
1110
952
|
while (holes.length > 0) {
|
|
1111
953
|
let selectedHoleIndex = -1;
|
|
1112
954
|
let selectedBridge = null;
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const candidate = findBestHoleBridge(outer, holes[hi], blockers, epsilon);
|
|
1118
|
-
if (candidate && candidate.distance < selectedDist) {
|
|
955
|
+
holes.forEach((hole, holeIndex) => {
|
|
956
|
+
const blockers = holes.filter((_, index) => index !== holeIndex);
|
|
957
|
+
const candidate = findBestHoleBridge(outer, hole, blockers, epsilon);
|
|
958
|
+
if (candidate && (!selectedBridge || candidate.distance < selectedBridge.distance)) {
|
|
1119
959
|
selectedBridge = candidate;
|
|
1120
|
-
selectedHoleIndex =
|
|
1121
|
-
selectedDist = candidate.distance;
|
|
960
|
+
selectedHoleIndex = holeIndex;
|
|
1122
961
|
}
|
|
1123
|
-
}
|
|
962
|
+
});
|
|
1124
963
|
if (!selectedBridge || selectedHoleIndex === -1) break;
|
|
1125
964
|
const [hole] = holes.splice(selectedHoleIndex, 1);
|
|
1126
965
|
const merged = mergeHoleIntoOuter(outer, hole, selectedBridge, width, epsilon);
|
|
1127
966
|
outer = merged.ring;
|
|
1128
|
-
|
|
1129
|
-
for (let i = 0; i < mergedAnchors.length; i++) anchors.push(mergedAnchors[i]);
|
|
967
|
+
anchors.push(...merged.anchors);
|
|
1130
968
|
}
|
|
1131
|
-
let holeArea = 0;
|
|
1132
|
-
for (let i = 0; i < holes.length; i++) holeArea += Math.abs(signedArea(holes[i]));
|
|
1133
969
|
return {
|
|
1134
970
|
...part,
|
|
1135
971
|
outer,
|
|
1136
972
|
holes,
|
|
1137
973
|
anchors,
|
|
1138
974
|
bbox: ringBounds(outer),
|
|
1139
|
-
area: Math.abs(signedArea(outer)) -
|
|
975
|
+
area: Math.abs(signedArea(outer)) - holes.reduce((sum, hole) => sum + Math.abs(signedArea(hole)), 0)
|
|
1140
976
|
};
|
|
1141
977
|
}
|
|
1142
978
|
function resamplePart(part, step) {
|
|
1143
979
|
const outer = resampleRing(part.outer, step, part.anchors ?? []);
|
|
1144
|
-
const
|
|
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
|
-
}
|
|
980
|
+
const holes = part.holes.map((hole) => resampleRing(hole, step));
|
|
1151
981
|
return {
|
|
1152
982
|
...part,
|
|
1153
983
|
outer,
|
|
1154
984
|
holes,
|
|
1155
985
|
anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
|
|
1156
986
|
bbox: ringBounds(outer),
|
|
1157
|
-
area: Math.abs(signedArea(outer)) -
|
|
987
|
+
area: Math.abs(signedArea(outer)) - holes.reduce((sum, hole) => sum + Math.abs(signedArea(hole)), 0)
|
|
1158
988
|
};
|
|
1159
989
|
}
|
|
1160
990
|
function findBestHoleBridge(outer, hole, blockers, epsilon) {
|
|
1161
991
|
let best = null;
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
|
992
|
+
outer.forEach((outerPoint, outerIndex) => {
|
|
993
|
+
hole.forEach((holePoint, holeIndex) => {
|
|
994
|
+
if (!isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon)) return;
|
|
995
|
+
const candidate = {
|
|
996
|
+
outerIndex,
|
|
997
|
+
holeIndex,
|
|
998
|
+
distance: distance(outerPoint, holePoint)
|
|
1177
999
|
};
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1000
|
+
if (!best || candidate.distance < best.distance) best = candidate;
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1181
1003
|
if (best) return best;
|
|
1182
1004
|
return findNearestBridge(outer, hole);
|
|
1183
1005
|
}
|
|
1184
1006
|
function findNearestBridge(outer, hole) {
|
|
1185
|
-
let
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
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
|
-
};
|
|
1007
|
+
let best = null;
|
|
1008
|
+
outer.forEach((outerPoint, outerIndex) => {
|
|
1009
|
+
hole.forEach((holePoint, holeIndex) => {
|
|
1010
|
+
const candidate = {
|
|
1011
|
+
outerIndex,
|
|
1012
|
+
holeIndex,
|
|
1013
|
+
distance: distance(outerPoint, holePoint)
|
|
1014
|
+
};
|
|
1015
|
+
if (!best || candidate.distance < best.distance) best = candidate;
|
|
1016
|
+
});
|
|
1017
|
+
});
|
|
1018
|
+
return best;
|
|
1208
1019
|
}
|
|
1209
1020
|
function isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon) {
|
|
1210
1021
|
if (distance(outerPoint, holePoint) <= epsilon) return false;
|
|
@@ -1371,14 +1182,13 @@ function resampleRingWithAnchors(ring, step, anchorIndices) {
|
|
|
1371
1182
|
return result;
|
|
1372
1183
|
}
|
|
1373
1184
|
function resampleClosedPath(ring, step) {
|
|
1374
|
-
const
|
|
1375
|
-
const perimeter = cumulative[ring.length];
|
|
1185
|
+
const perimeter = ringPerimeter(ring);
|
|
1376
1186
|
if (perimeter <= 1e-9) return copyRing(ring);
|
|
1377
1187
|
const pointCount = Math.max(3, Math.round(perimeter / step));
|
|
1378
1188
|
if (pointCount >= ring.length) return copyRing(ring);
|
|
1379
1189
|
const sampled = [];
|
|
1380
|
-
for (let index = 0; index < pointCount; index
|
|
1381
|
-
const point =
|
|
1190
|
+
for (let index = 0; index < pointCount; index += 1) {
|
|
1191
|
+
const point = pointAtClosedPathDistance(ring, perimeter * index / pointCount);
|
|
1382
1192
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1383
1193
|
}
|
|
1384
1194
|
if (sampled.length < 3 || sampled.length >= ring.length) return copyRing(ring);
|
|
@@ -1386,13 +1196,12 @@ function resampleClosedPath(ring, step) {
|
|
|
1386
1196
|
}
|
|
1387
1197
|
function resampleOpenPath(path, step) {
|
|
1388
1198
|
if (path.length <= 2) return path.map((point) => [point[0], point[1]]);
|
|
1389
|
-
const
|
|
1390
|
-
const length = cumulative[path.length - 1];
|
|
1199
|
+
const length = polylineLength(path);
|
|
1391
1200
|
if (length <= step) return [[path[0][0], path[0][1]], [path[path.length - 1][0], path[path.length - 1][1]]];
|
|
1392
1201
|
const divisionCount = Math.max(1, Math.round(length / step));
|
|
1393
1202
|
const sampled = [];
|
|
1394
|
-
for (let index = 0; index <= divisionCount; index
|
|
1395
|
-
const point =
|
|
1203
|
+
for (let index = 0; index <= divisionCount; index += 1) {
|
|
1204
|
+
const point = pointAtOpenPathDistance(path, length * index / divisionCount);
|
|
1396
1205
|
if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
|
|
1397
1206
|
}
|
|
1398
1207
|
return sampled;
|
|
@@ -3764,25 +3573,9 @@ function truncateLineToWidth(line, maxWidth, measureWidth, suffix) {
|
|
|
3764
3573
|
width: 0,
|
|
3765
3574
|
hardBreak: false
|
|
3766
3575
|
};
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
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;
|
|
3576
|
+
let nextText = trimTrailingWhitespace(line.text);
|
|
3577
|
+
while (nextText.length > 0 && measureWidth(`${nextText}${suffixText}`) > maxWidth + JUSTIFY_EPSILON) nextText = trimLastGrapheme(nextText);
|
|
3578
|
+
const text = `${nextText}${suffixText}`;
|
|
3786
3579
|
return {
|
|
3787
3580
|
...line,
|
|
3788
3581
|
text,
|
|
@@ -4319,6 +4112,12 @@ function getGraphemeSegmenter() {
|
|
|
4319
4112
|
function trimTrailingWhitespace(value) {
|
|
4320
4113
|
return value.replace(/\s+$/u, "");
|
|
4321
4114
|
}
|
|
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
|
+
}
|
|
4322
4121
|
function splitPreservingWhitespace(value) {
|
|
4323
4122
|
return value.split(/(\s+)/u).filter((token) => token.length > 0);
|
|
4324
4123
|
}
|