pa_font 0.1.1

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.
@@ -0,0 +1,1221 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ let opentype_js = require("opentype.js");
6
+ //#region src/paFont/geometry.js
7
+ function toScreenPoint(point, scale) {
8
+ return [point.x * scale, -point.y * scale];
9
+ }
10
+ function cloneRawPoint(point) {
11
+ return {
12
+ x: point.x,
13
+ y: point.y
14
+ };
15
+ }
16
+ function pushUniquePoint(points, point) {
17
+ if (points.length === 0 || !pointsEqual2D(points[points.length - 1], point)) points.push(point);
18
+ }
19
+ function flattenQuadratic(p0, p1, p2, tolerance, out, 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);
56
+ }
57
+ function midpoint(a, b) {
58
+ return [(a[0] + b[0]) * .5, (a[1] + b[1]) * .5];
59
+ }
60
+ function signedArea(ring) {
61
+ let area = 0;
62
+ for (let index = 0; index < ring.length; index += 1) {
63
+ const current = ring[index];
64
+ const next = ring[(index + 1) % ring.length];
65
+ area += current[0] * next[1] - next[0] * current[1];
66
+ }
67
+ return area * .5;
68
+ }
69
+ function pointInRing(point, ring) {
70
+ let inside = false;
71
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
72
+ const xi = ring[i][0];
73
+ const yi = ring[i][1];
74
+ const xj = ring[j][0];
75
+ const yj = ring[j][1];
76
+ if (yi > point[1] !== yj > point[1] && point[0] < (xj - xi) * (point[1] - yi) / (yj - yi || 1e-9) + xi) inside = !inside;
77
+ }
78
+ return inside;
79
+ }
80
+ function hitPart(point, part, epsilon) {
81
+ if (!rectContainsPoint(part.bbox, point, epsilon)) return "outside";
82
+ if (isPointOnRingEdge(point, part.outer, epsilon)) return "edge";
83
+ if (!pointInRing(point, part.outer)) return "outside";
84
+ for (const hole of part.holes) {
85
+ if (isPointOnRingEdge(point, hole, epsilon)) return "edge";
86
+ if (pointInRing(point, hole)) return "hole";
87
+ }
88
+ return "fill";
89
+ }
90
+ function isPointOnRingEdge(point, ring, epsilon) {
91
+ for (let index = 0; index < ring.length; index += 1) {
92
+ const a = ring[index];
93
+ const b = ring[(index + 1) % ring.length];
94
+ if (distancePointToSegment(point, a, b) <= epsilon) return true;
95
+ }
96
+ return false;
97
+ }
98
+ function distancePointToSegment(point, a, b) {
99
+ const dx = b[0] - a[0];
100
+ const dy = b[1] - a[1];
101
+ const lengthSq = dx * dx + dy * dy;
102
+ if (lengthSq === 0) return distance(point, a);
103
+ let t = ((point[0] - a[0]) * dx + (point[1] - a[1]) * dy) / lengthSq;
104
+ t = Math.max(0, Math.min(1, t));
105
+ return distance(point, [a[0] + dx * t, a[1] + dy * t]);
106
+ }
107
+ function distance(a, b) {
108
+ const dx = a[0] - b[0];
109
+ const dy = a[1] - b[1];
110
+ return Math.sqrt(dx * dx + dy * dy);
111
+ }
112
+ function sampleRing(ring, step, callback) {
113
+ for (let index = 0; index < ring.length; index += 1) {
114
+ const a = ring[index];
115
+ const b = ring[(index + 1) % ring.length];
116
+ const segmentLength = distance(a, b);
117
+ const divisions = Math.max(1, Math.ceil(segmentLength / step));
118
+ for (let offset = 0; offset < divisions; offset += 1) if (index > 0 || offset > 0) {
119
+ const t = offset / divisions;
120
+ callback([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
121
+ } else callback([a[0], a[1]]);
122
+ }
123
+ }
124
+ function ringBounds(ring) {
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
+ });
135
+ return {
136
+ x: minX,
137
+ y: minY,
138
+ w: maxX - minX,
139
+ h: maxY - minY
140
+ };
141
+ }
142
+ function translateRing(ring, tx, ty) {
143
+ return ring.map((point) => [point[0] + tx, point[1] + ty]);
144
+ }
145
+ function translateRect(rect, tx, ty) {
146
+ return {
147
+ x: rect.x + tx,
148
+ y: rect.y + ty,
149
+ w: rect.w,
150
+ h: rect.h
151
+ };
152
+ }
153
+ function copyRing(ring) {
154
+ return ring.map((point) => [point[0], point[1]]);
155
+ }
156
+ function combineRects(rects) {
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
+ });
168
+ return {
169
+ x: minX,
170
+ y: minY,
171
+ w: maxX - minX,
172
+ h: maxY - minY
173
+ };
174
+ }
175
+ function rectContainsRect(outer, inner, epsilon = 1e-6) {
176
+ return inner.x >= outer.x - epsilon && inner.y >= outer.y - epsilon && inner.x + inner.w <= outer.x + outer.w + epsilon && inner.y + inner.h <= outer.y + outer.h + epsilon;
177
+ }
178
+ function rectContainsPoint(rect, point, epsilon = 0) {
179
+ return point[0] >= rect.x - epsilon && point[0] <= rect.x + rect.w + epsilon && point[1] >= rect.y - epsilon && point[1] <= rect.y + rect.h + epsilon;
180
+ }
181
+ function emptyRect() {
182
+ return {
183
+ x: 0,
184
+ y: 0,
185
+ w: 0,
186
+ h: 0
187
+ };
188
+ }
189
+ function normalizeNumber(value, fallback) {
190
+ return Number.isFinite(value) ? value : fallback;
191
+ }
192
+ function normalizePositive(value, fallback) {
193
+ return Number.isFinite(value) && value > 0 ? value : fallback;
194
+ }
195
+ function samePoint(a, b) {
196
+ return a.x === b.x && a.y === b.y;
197
+ }
198
+ function pointsEqual2D(a, b) {
199
+ return a[0] === b[0] && a[1] === b[1];
200
+ }
201
+ function pointsAlmostEqual2D(a, b, epsilon = 1e-6) {
202
+ return Math.abs(a[0] - b[0]) <= epsilon && Math.abs(a[1] - b[1]) <= epsilon;
203
+ }
204
+ function ringPerimeter(ring) {
205
+ let total = 0;
206
+ for (let index = 0; index < ring.length; index += 1) total += distance(ring[index], ring[(index + 1) % ring.length]);
207
+ return total;
208
+ }
209
+ function polylineLength(path) {
210
+ let total = 0;
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);
216
+ if (perimeter <= 1e-9) return [ring[0][0], ring[0][1]];
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) {
232
+ if (distanceAlong <= 0) return [path[0][0], path[0][1]];
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]];
246
+ }
247
+ function segmentIntersectsRing(a, b, ring, ignoredEdges, epsilon) {
248
+ for (let index = 0; index < ring.length; index += 1) {
249
+ if (ignoredEdges.has(index)) continue;
250
+ const c = ring[index];
251
+ const d = ring[(index + 1) % ring.length];
252
+ if (segmentsIntersect(a, b, c, d, epsilon)) return true;
253
+ }
254
+ return false;
255
+ }
256
+ function segmentsIntersect(a, b, c, d, epsilon) {
257
+ const o1 = orientation(a, b, c);
258
+ const o2 = orientation(a, b, d);
259
+ const o3 = orientation(c, d, a);
260
+ const o4 = orientation(c, d, b);
261
+ if ((o1 > epsilon && o2 < -epsilon || o1 < -epsilon && o2 > epsilon) && (o3 > epsilon && o4 < -epsilon || o3 < -epsilon && o4 > epsilon)) return true;
262
+ if (Math.abs(o1) <= epsilon && pointOnSegment(c, a, b, epsilon)) return true;
263
+ if (Math.abs(o2) <= epsilon && pointOnSegment(d, a, b, epsilon)) return true;
264
+ if (Math.abs(o3) <= epsilon && pointOnSegment(a, c, d, epsilon)) return true;
265
+ if (Math.abs(o4) <= epsilon && pointOnSegment(b, c, d, epsilon)) return true;
266
+ return false;
267
+ }
268
+ function orientation(a, b, c) {
269
+ return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
270
+ }
271
+ function pointOnSegment(point, start, end, epsilon) {
272
+ return point[0] >= Math.min(start[0], end[0]) - epsilon && point[0] <= Math.max(start[0], end[0]) + epsilon && point[1] >= Math.min(start[1], end[1]) - epsilon && point[1] <= Math.max(start[1], end[1]) + epsilon;
273
+ }
274
+ function mod(value, divisor) {
275
+ return (value % divisor + divisor) % divisor;
276
+ }
277
+ //#endregion
278
+ //#region src/paFont/core.js
279
+ function layoutGlyphs(font, value, opts) {
280
+ const glyphs = [];
281
+ const renderOptions = toRenderOptions(opts);
282
+ return {
283
+ text: value,
284
+ glyphs,
285
+ metrics: {
286
+ width: font.forEachGlyph(value, opts.x, opts.y, opts.size, renderOptions, (glyph, x, y, size) => {
287
+ glyphs.push({
288
+ glyph,
289
+ x,
290
+ y,
291
+ size,
292
+ index: glyphs.length
293
+ });
294
+ }) - opts.x,
295
+ x: opts.x,
296
+ y: opts.y,
297
+ size: opts.size
298
+ }
299
+ };
300
+ }
301
+ function measureText(font, value, opts) {
302
+ const renderOptions = toRenderOptions(opts);
303
+ const box = font.getPath(value, opts.x, opts.y, opts.size, renderOptions).getBoundingBox();
304
+ return {
305
+ width: font.getAdvanceWidth(value, opts.size, renderOptions),
306
+ bbox: {
307
+ x: box.x1,
308
+ y: box.y1,
309
+ w: box.x2 - box.x1,
310
+ h: box.y2 - box.y1
311
+ }
312
+ };
313
+ }
314
+ function buildGlyphTopology(glyph, fallbackUnitsPerEm) {
315
+ const commands = glyph.path?.commands ?? [];
316
+ const contours = [];
317
+ let contourId = 0;
318
+ let current = null;
319
+ const finishCurrentContour = () => {
320
+ if (!current || current.segments.length === 0) {
321
+ current = null;
322
+ return;
323
+ }
324
+ if (!samePoint(current.cursor, current.start)) {
325
+ current.segments.push({
326
+ type: "L",
327
+ from: cloneRawPoint(current.cursor),
328
+ to: cloneRawPoint(current.start)
329
+ });
330
+ current.cursor = cloneRawPoint(current.start);
331
+ }
332
+ contours.push({
333
+ id: contourId++,
334
+ start: cloneRawPoint(current.start),
335
+ segments: current.segments
336
+ });
337
+ current = null;
338
+ };
339
+ commands.forEach((cmd) => {
340
+ if (cmd.type === "M") {
341
+ finishCurrentContour();
342
+ current = {
343
+ start: {
344
+ x: cmd.x,
345
+ y: cmd.y
346
+ },
347
+ cursor: {
348
+ x: cmd.x,
349
+ y: cmd.y
350
+ },
351
+ segments: []
352
+ };
353
+ return;
354
+ }
355
+ if (!current) return;
356
+ if (cmd.type === "L") {
357
+ const to = {
358
+ x: cmd.x,
359
+ y: cmd.y
360
+ };
361
+ current.segments.push({
362
+ type: "L",
363
+ from: cloneRawPoint(current.cursor),
364
+ to
365
+ });
366
+ current.cursor = cloneRawPoint(to);
367
+ return;
368
+ }
369
+ if (cmd.type === "Q") {
370
+ const to = {
371
+ x: cmd.x,
372
+ y: cmd.y
373
+ };
374
+ current.segments.push({
375
+ type: "Q",
376
+ from: cloneRawPoint(current.cursor),
377
+ c1: {
378
+ x: cmd.x1,
379
+ y: cmd.y1
380
+ },
381
+ to
382
+ });
383
+ current.cursor = cloneRawPoint(to);
384
+ return;
385
+ }
386
+ if (cmd.type === "C") {
387
+ const to = {
388
+ x: cmd.x,
389
+ y: cmd.y
390
+ };
391
+ current.segments.push({
392
+ type: "C",
393
+ from: cloneRawPoint(current.cursor),
394
+ c1: {
395
+ x: cmd.x1,
396
+ y: cmd.y1
397
+ },
398
+ c2: {
399
+ x: cmd.x2,
400
+ y: cmd.y2
401
+ },
402
+ to
403
+ });
404
+ current.cursor = cloneRawPoint(to);
405
+ return;
406
+ }
407
+ if (cmd.type === "Z") finishCurrentContour();
408
+ });
409
+ finishCurrentContour();
410
+ return {
411
+ glyphIndex: glyph.index,
412
+ advanceWidth: glyph.advanceWidth ?? 0,
413
+ unitsPerEm: glyph.path?.unitsPerEm ?? fallbackUnitsPerEm ?? 1e3,
414
+ contours
415
+ };
416
+ }
417
+ function flattenGlyphTopology(topology, options) {
418
+ const scale = options.size / topology.unitsPerEm;
419
+ const classifiedContours = classifyContours(topology.contours.map((contour) => flattenContour(contour, scale, options.flatten)).filter(Boolean));
420
+ const parts = buildParts(classifiedContours);
421
+ const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
422
+ return {
423
+ glyphIndex: topology.glyphIndex,
424
+ advanceWidth: topology.advanceWidth * scale,
425
+ contours: classifiedContours,
426
+ parts,
427
+ bbox
428
+ };
429
+ }
430
+ function flattenContour(contour, scale, tolerance) {
431
+ const ring = [];
432
+ pushUniquePoint(ring, toScreenPoint(contour.start, scale));
433
+ contour.segments.forEach((segment) => {
434
+ if (segment.type === "L") {
435
+ pushUniquePoint(ring, toScreenPoint(segment.to, scale));
436
+ return;
437
+ }
438
+ if (segment.type === "Q") {
439
+ flattenQuadratic(toScreenPoint(segment.from, scale), toScreenPoint(segment.c1, scale), toScreenPoint(segment.to, scale), tolerance, ring);
440
+ return;
441
+ }
442
+ if (segment.type === "C") flattenCubic(toScreenPoint(segment.from, scale), toScreenPoint(segment.c1, scale), toScreenPoint(segment.c2, scale), toScreenPoint(segment.to, scale), tolerance, ring);
443
+ });
444
+ if (ring.length > 1 && pointsEqual2D(ring[0], ring[ring.length - 1])) ring.pop();
445
+ if (ring.length < 3) return null;
446
+ const bbox = ringBounds(ring);
447
+ const area = signedArea(ring);
448
+ return {
449
+ id: contour.id,
450
+ ring,
451
+ bbox,
452
+ area
453
+ };
454
+ }
455
+ function classifyContours(contours) {
456
+ const result = contours.map((contour) => ({
457
+ ...contour,
458
+ parentId: null,
459
+ depth: 0,
460
+ role: "outer"
461
+ }));
462
+ result.forEach((child) => {
463
+ let parent = null;
464
+ let parentArea = Number.POSITIVE_INFINITY;
465
+ result.forEach((candidate) => {
466
+ if (candidate.id === child.id) return;
467
+ if (Math.abs(candidate.area) <= Math.abs(child.area)) return;
468
+ if (!rectContainsRect(candidate.bbox, child.bbox)) return;
469
+ if (!pointInRing(child.ring[0], candidate.ring)) return;
470
+ const candidateArea = Math.abs(candidate.area);
471
+ if (candidateArea < parentArea) {
472
+ parent = candidate;
473
+ parentArea = candidateArea;
474
+ }
475
+ });
476
+ if (parent) child.parentId = parent.id;
477
+ });
478
+ const byId = new Map(result.map((contour) => [contour.id, contour]));
479
+ const resolveDepth = (contour) => {
480
+ if (contour.parentId == null) return 0;
481
+ return resolveDepth(byId.get(contour.parentId)) + 1;
482
+ };
483
+ result.forEach((contour) => {
484
+ contour.depth = resolveDepth(contour);
485
+ contour.role = contour.depth % 2 === 0 ? "outer" : "hole";
486
+ });
487
+ return result;
488
+ }
489
+ function buildParts(contours) {
490
+ return contours.filter((contour) => contour.role === "outer").map((outer) => {
491
+ const holeContours = contours.filter((contour) => contour.parentId === outer.id && contour.role === "hole");
492
+ return {
493
+ outer: copyRing(outer.ring),
494
+ holes: holeContours.map((hole) => copyRing(hole.ring)),
495
+ bbox: { ...outer.bbox },
496
+ area: Math.abs(outer.area) - holeContours.reduce((sum, hole) => sum + Math.abs(hole.area), 0)
497
+ };
498
+ });
499
+ }
500
+ function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
501
+ return {
502
+ parts: geometry.parts.map((part, partIndex) => ({
503
+ outer: translateRing(part.outer, tx, ty),
504
+ holes: part.holes.map((hole) => translateRing(hole, tx, ty)),
505
+ bbox: translateRect(part.bbox, tx, ty),
506
+ area: part.area,
507
+ glyphPosition,
508
+ partIndex
509
+ })),
510
+ contours: geometry.contours.map((contour) => ({
511
+ id: `${glyphPosition}:${contour.id}`,
512
+ sourceId: contour.id,
513
+ glyphPosition,
514
+ parentId: contour.parentId == null ? null : `${glyphPosition}:${contour.parentId}`,
515
+ depth: contour.depth,
516
+ role: contour.role,
517
+ area: contour.area,
518
+ bbox: translateRect(contour.bbox, tx, ty),
519
+ ring: translateRing(contour.ring, tx, ty)
520
+ })),
521
+ bbox: translateRect(geometry.bbox, tx, ty)
522
+ };
523
+ }
524
+ function normalizeTextOptions(options = {}) {
525
+ const size = normalizePositive(options.size, 72);
526
+ return {
527
+ x: normalizeNumber(options.x, 0),
528
+ y: normalizeNumber(options.y, 0),
529
+ size,
530
+ flatten: normalizePositive(options.flatten, 1.25),
531
+ edgeEpsilon: options.edgeEpsilon == null ? defaultEdgeEpsilon(size) : normalizePositive(options.edgeEpsilon, defaultEdgeEpsilon(size)),
532
+ kerning: options.kerning !== false,
533
+ letterSpacing: options.letterSpacing == null ? void 0 : normalizeNumber(options.letterSpacing, 0),
534
+ tracking: options.tracking == null ? void 0 : normalizeNumber(options.tracking, 0),
535
+ script: options.script,
536
+ language: options.language,
537
+ features: options.features
538
+ };
539
+ }
540
+ function defaultEdgeEpsilon(size) {
541
+ return Math.min(1, Math.max(.01, size * .0025));
542
+ }
543
+ function toRenderOptions(opts) {
544
+ const renderOptions = { kerning: opts.kerning };
545
+ if (opts.letterSpacing != null) renderOptions.letterSpacing = opts.letterSpacing;
546
+ if (opts.tracking != null) renderOptions.tracking = opts.tracking;
547
+ if (opts.script) renderOptions.script = opts.script;
548
+ if (opts.language) renderOptions.language = opts.language;
549
+ if (opts.features) renderOptions.features = opts.features;
550
+ return renderOptions;
551
+ }
552
+ function toArrayBuffer(value) {
553
+ if (value instanceof ArrayBuffer) return value;
554
+ if (ArrayBuffer.isView(value)) return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
555
+ return value;
556
+ }
557
+ //#endregion
558
+ //#region src/paFont/shape.js
559
+ var PAShape = class {
560
+ constructor({ text, parts, contours, glyphs, bbox, metrics, edgeEpsilon }) {
561
+ this.text = text;
562
+ this.parts = parts;
563
+ this.bbox = bbox;
564
+ this.metrics = metrics;
565
+ this.edgeEpsilon = edgeEpsilon;
566
+ this.raw = {
567
+ contours,
568
+ glyphs
569
+ };
570
+ this._cache = {
571
+ glyphs: null,
572
+ openHoles: /* @__PURE__ */ new Map(),
573
+ resample: /* @__PURE__ */ new Map(),
574
+ shapes: /* @__PURE__ */ new Map(),
575
+ regionViews: null
576
+ };
577
+ }
578
+ get polygons() {
579
+ return this.parts.map((part) => [part.outer, ...part.holes]);
580
+ }
581
+ [Symbol.iterator]() {
582
+ return this._getRegionViews()[Symbol.iterator]();
583
+ }
584
+ glyphs() {
585
+ if (this._cache.glyphs) return this._cache.glyphs;
586
+ if (!Array.isArray(this.raw.glyphs) || this.raw.glyphs.length <= 1) {
587
+ this._cache.glyphs = [this];
588
+ return this._cache.glyphs;
589
+ }
590
+ this._cache.glyphs = this.raw.glyphs.map((_, glyphPosition) => createGlyphShape(this, glyphPosition));
591
+ return this._cache.glyphs;
592
+ }
593
+ toShape(options = {}) {
594
+ return resolveShapeVariant(this, options);
595
+ }
596
+ toRegions(options = {}) {
597
+ return createRegionCollection(this.toShape(options));
598
+ }
599
+ openHoles(width) {
600
+ const slitWidth = normalizePositive(width, 0);
601
+ if (slitWidth <= 0 || !this.parts.some((part) => part.holes.length > 0)) return this;
602
+ const cacheKey = toCacheKey(slitWidth);
603
+ const cached = this._cache.openHoles.get(cacheKey);
604
+ if (cached) return cached;
605
+ const geometryEpsilon = resolveGeometryEpsilon(slitWidth);
606
+ const shape = createDerivedShape(this, this.parts.map((part) => openPartWithSlit(part, slitWidth, geometryEpsilon)));
607
+ this._cache.openHoles.set(cacheKey, shape);
608
+ return shape;
609
+ }
610
+ resample(step) {
611
+ const spacing = normalizePositive(step, 0);
612
+ if (spacing <= 0) return this;
613
+ const cacheKey = toCacheKey(spacing);
614
+ const cached = this._cache.resample.get(cacheKey);
615
+ if (cached) return cached;
616
+ const shape = createDerivedShape(this, this.parts.map((part) => resamplePart(part, spacing)));
617
+ this._cache.resample.set(cacheKey, shape);
618
+ return shape;
619
+ }
620
+ hit(x, y, epsilon = this.edgeEpsilon) {
621
+ const point = [x, y];
622
+ if (!rectContainsPoint(this.bbox, point, epsilon)) return "outside";
623
+ let holeHit = false;
624
+ for (const part of this.parts) {
625
+ const result = hitPart(point, part, epsilon);
626
+ if (result === "edge" || result === "fill") return result;
627
+ if (result === "hole") holeHit = true;
628
+ }
629
+ return holeHit ? "hole" : "outside";
630
+ }
631
+ contains(x, y, epsilon = this.edgeEpsilon) {
632
+ return this.hit(x, y, epsilon) === "fill";
633
+ }
634
+ toPoints(options = {}) {
635
+ const opts = normalizePointOptions(options);
636
+ const shape = opts.openWidth > 0 ? this.toShape({ openWidth: opts.openWidth }) : this;
637
+ const step = opts.step;
638
+ const includeHoles = opts.includeHoles;
639
+ const sampled = [];
640
+ shape.parts.forEach((part, partIndex) => {
641
+ sampleRing(part.outer, step, (point) => {
642
+ sampled.push({
643
+ x: point[0],
644
+ y: point[1],
645
+ partIndex,
646
+ hole: false
647
+ });
648
+ });
649
+ if (!includeHoles) return;
650
+ part.holes.forEach((hole, holeIndex) => {
651
+ sampleRing(hole, step, (point) => {
652
+ sampled.push({
653
+ x: point[0],
654
+ y: point[1],
655
+ partIndex,
656
+ hole: true,
657
+ holeIndex
658
+ });
659
+ });
660
+ });
661
+ });
662
+ return sampled;
663
+ }
664
+ _getRegionViews() {
665
+ if (!this._cache.regionViews) this._cache.regionViews = this.parts.map((part) => Object.freeze({
666
+ outer: part.outer,
667
+ holes: part.holes,
668
+ bbox: part.bbox
669
+ }));
670
+ return this._cache.regionViews;
671
+ }
672
+ };
673
+ function createTextShape(layout, opts, fontInstance) {
674
+ const parts = [];
675
+ const contours = [];
676
+ const glyphs = [];
677
+ layout.glyphs.forEach((item, glyphPosition) => {
678
+ const translated = translateGlyphGeometry(fontInstance._getFlattenedGlyph(item.glyph, opts), item.x, item.y, glyphPosition);
679
+ parts.push(...translated.parts);
680
+ contours.push(...translated.contours);
681
+ glyphs.push({
682
+ position: glyphPosition,
683
+ glyphIndex: item.glyph.index,
684
+ x: item.x,
685
+ y: item.y,
686
+ size: item.size,
687
+ bbox: translated.bbox,
688
+ partCount: translated.parts.length
689
+ });
690
+ });
691
+ const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
692
+ return new PAShape({
693
+ text: layout.text,
694
+ parts,
695
+ contours,
696
+ glyphs,
697
+ bbox,
698
+ metrics: {
699
+ ...layout.metrics,
700
+ bbox
701
+ },
702
+ edgeEpsilon: opts.edgeEpsilon
703
+ });
704
+ }
705
+ function createGlyphShape(shape, glyphPosition) {
706
+ const glyphMeta = shape.raw.glyphs?.[glyphPosition] ?? null;
707
+ const parts = copyParts(shape.parts.filter((part) => part.glyphPosition === glyphPosition));
708
+ const contours = copyContours((shape.raw.contours ?? []).filter((contour) => contour.glyphPosition === glyphPosition));
709
+ const bbox = combineRects(parts.map((part) => part.bbox)) ?? (glyphMeta?.bbox ? { ...glyphMeta.bbox } : emptyRect());
710
+ return new PAShape({
711
+ text: extractGlyphText(shape.text, glyphPosition, shape.raw.glyphs?.length ?? 0),
712
+ parts,
713
+ contours,
714
+ glyphs: glyphMeta ? [{
715
+ ...glyphMeta,
716
+ bbox,
717
+ partCount: parts.length
718
+ }] : [],
719
+ bbox,
720
+ metrics: {
721
+ x: glyphMeta?.x ?? shape.metrics?.x ?? 0,
722
+ y: glyphMeta?.y ?? shape.metrics?.y ?? 0,
723
+ size: glyphMeta?.size ?? shape.metrics?.size ?? 0,
724
+ width: bbox.w,
725
+ bbox
726
+ },
727
+ edgeEpsilon: shape.edgeEpsilon
728
+ });
729
+ }
730
+ function createDerivedShape(shape, parts) {
731
+ const copiedParts = copyParts(parts);
732
+ const bbox = combineRects(copiedParts.map((part) => part.bbox)) ?? emptyRect();
733
+ const glyphs = (shape.raw.glyphs ?? []).map((glyph, glyphPosition) => {
734
+ const glyphParts = copiedParts.filter((part) => part.glyphPosition === glyphPosition);
735
+ return {
736
+ ...glyph,
737
+ bbox: combineRects(glyphParts.map((part) => part.bbox)) ?? (glyph.bbox ? { ...glyph.bbox } : emptyRect()),
738
+ partCount: glyphParts.length
739
+ };
740
+ });
741
+ return new PAShape({
742
+ text: shape.text,
743
+ parts: copiedParts,
744
+ contours: [],
745
+ glyphs,
746
+ bbox,
747
+ metrics: {
748
+ ...shape.metrics,
749
+ bbox
750
+ },
751
+ edgeEpsilon: shape.edgeEpsilon
752
+ });
753
+ }
754
+ function createRegionCollection(shape) {
755
+ return shape.parts.map((part) => ({
756
+ outer: copyRing(part.outer),
757
+ holes: part.holes.map((hole) => copyRing(hole)),
758
+ bbox: { ...part.bbox }
759
+ }));
760
+ }
761
+ function copyParts(parts) {
762
+ return parts.map((part) => copyPart(part));
763
+ }
764
+ function copyPart(part) {
765
+ return {
766
+ ...part,
767
+ outer: copyRing(part.outer),
768
+ holes: part.holes.map((hole) => copyRing(hole)),
769
+ anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
770
+ bbox: { ...part.bbox }
771
+ };
772
+ }
773
+ function copyContours(contours) {
774
+ return contours.map((contour) => ({
775
+ ...contour,
776
+ bbox: { ...contour.bbox },
777
+ ring: copyRing(contour.ring)
778
+ }));
779
+ }
780
+ function extractGlyphText(text, glyphPosition, glyphCount) {
781
+ if (glyphCount <= 1) return text;
782
+ return Array.from(text ?? "")[glyphPosition] ?? "";
783
+ }
784
+ function normalizePointOptions(options = {}) {
785
+ if (options == null) return {
786
+ step: 8,
787
+ openWidth: 0,
788
+ includeHoles: true
789
+ };
790
+ if (typeof options !== "object" || Array.isArray(options)) throw new TypeError("toPoints() expects an options object.");
791
+ return {
792
+ step: normalizePositive(options.step, 8),
793
+ openWidth: normalizePositive(options.openWidth, 0),
794
+ includeHoles: options.includeHoles !== false
795
+ };
796
+ }
797
+ function normalizeShapeOptions(options = {}) {
798
+ if (options == null) return {
799
+ step: 0,
800
+ openWidth: 0
801
+ };
802
+ if (typeof options !== "object" || Array.isArray(options)) throw new TypeError("toShape() and toRegions() expect an options object.");
803
+ return {
804
+ step: normalizePositive(options.step, 0),
805
+ openWidth: normalizePositive(options.openWidth, 0)
806
+ };
807
+ }
808
+ function resolveShapeVariant(shape, options = {}) {
809
+ const normalized = normalizeShapeOptions(options);
810
+ if (normalized.step <= 0 && normalized.openWidth <= 0) return shape;
811
+ const cacheKey = `${toCacheKey(normalized.step)}:${toCacheKey(normalized.openWidth)}`;
812
+ const cached = shape._cache.shapes.get(cacheKey);
813
+ if (cached) return cached;
814
+ let next = shape;
815
+ if (normalized.openWidth > 0) next = next.openHoles(normalized.openWidth);
816
+ if (normalized.step > 0) next = next.resample(normalized.step);
817
+ shape._cache.shapes.set(cacheKey, next);
818
+ return next;
819
+ }
820
+ function toCacheKey(value) {
821
+ return normalizePositive(value, 0).toFixed(6);
822
+ }
823
+ function resolveGeometryEpsilon(width) {
824
+ return Math.max(1e-4, normalizePositive(width, 1) * .001);
825
+ }
826
+ function openPartWithSlit(part, width, epsilon) {
827
+ if (!part.holes || part.holes.length === 0) return copyPart(part);
828
+ let outer = copyRing(part.outer);
829
+ const holes = part.holes.map((hole) => copyRing(hole));
830
+ const anchors = [];
831
+ while (holes.length > 0) {
832
+ let selectedHoleIndex = -1;
833
+ let selectedBridge = null;
834
+ holes.forEach((hole, holeIndex) => {
835
+ const blockers = holes.filter((_, index) => index !== holeIndex);
836
+ const candidate = findBestHoleBridge(outer, hole, blockers, epsilon);
837
+ if (candidate && (!selectedBridge || candidate.distance < selectedBridge.distance)) {
838
+ selectedBridge = candidate;
839
+ selectedHoleIndex = holeIndex;
840
+ }
841
+ });
842
+ if (!selectedBridge || selectedHoleIndex === -1) break;
843
+ const [hole] = holes.splice(selectedHoleIndex, 1);
844
+ const merged = mergeHoleIntoOuter(outer, hole, selectedBridge, width, epsilon);
845
+ outer = merged.ring;
846
+ anchors.push(...merged.anchors);
847
+ }
848
+ return {
849
+ ...part,
850
+ outer,
851
+ holes,
852
+ anchors,
853
+ bbox: ringBounds(outer),
854
+ area: Math.abs(signedArea(outer)) - holes.reduce((sum, hole) => sum + Math.abs(signedArea(hole)), 0)
855
+ };
856
+ }
857
+ function resamplePart(part, step) {
858
+ const outer = resampleRing(part.outer, step, part.anchors ?? []);
859
+ const holes = part.holes.map((hole) => resampleRing(hole, step));
860
+ return {
861
+ ...part,
862
+ outer,
863
+ holes,
864
+ anchors: part.anchors ? part.anchors.map((point) => [point[0], point[1]]) : void 0,
865
+ bbox: ringBounds(outer),
866
+ area: Math.abs(signedArea(outer)) - holes.reduce((sum, hole) => sum + Math.abs(signedArea(hole)), 0)
867
+ };
868
+ }
869
+ function findBestHoleBridge(outer, hole, blockers, epsilon) {
870
+ let best = null;
871
+ outer.forEach((outerPoint, outerIndex) => {
872
+ hole.forEach((holePoint, holeIndex) => {
873
+ if (!isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon)) return;
874
+ const candidate = {
875
+ outerIndex,
876
+ holeIndex,
877
+ distance: distance(outerPoint, holePoint)
878
+ };
879
+ if (!best || candidate.distance < best.distance) best = candidate;
880
+ });
881
+ });
882
+ if (best) return best;
883
+ return findNearestBridge(outer, hole);
884
+ }
885
+ function findNearestBridge(outer, hole) {
886
+ let best = null;
887
+ outer.forEach((outerPoint, outerIndex) => {
888
+ hole.forEach((holePoint, holeIndex) => {
889
+ const candidate = {
890
+ outerIndex,
891
+ holeIndex,
892
+ distance: distance(outerPoint, holePoint)
893
+ };
894
+ if (!best || candidate.distance < best.distance) best = candidate;
895
+ });
896
+ });
897
+ return best;
898
+ }
899
+ function isVisibleBridge(outer, hole, blockers, outerPoint, holePoint, outerIndex, holeIndex, epsilon) {
900
+ if (distance(outerPoint, holePoint) <= epsilon) return false;
901
+ const bridgeMidpoint = midpoint(outerPoint, holePoint);
902
+ if (!pointInRing(bridgeMidpoint, outer) || pointInRing(bridgeMidpoint, hole)) return false;
903
+ if (blockers.some((blocker) => pointInRing(bridgeMidpoint, blocker))) return false;
904
+ if (segmentIntersectsRing(outerPoint, holePoint, outer, new Set([outerIndex, mod(outerIndex - 1, outer.length)]), epsilon)) return false;
905
+ if (segmentIntersectsRing(outerPoint, holePoint, hole, new Set([holeIndex, mod(holeIndex - 1, hole.length)]), epsilon)) return false;
906
+ return !blockers.some((blocker) => segmentIntersectsRing(outerPoint, holePoint, blocker, /* @__PURE__ */ new Set(), epsilon));
907
+ }
908
+ function mergeHoleIntoOuter(outer, hole, bridge, width, epsilon) {
909
+ const slitWidth = clampSlitWidth(width, outer, hole, epsilon);
910
+ const outerCut = createRingCut(outer, bridge.outerIndex, slitWidth, epsilon);
911
+ const holeCut = createRingCut(hole, bridge.holeIndex, slitWidth, epsilon);
912
+ const outerPath = ringPath(outerCut.ring, outerCut.afterIndex, outerCut.beforeIndex);
913
+ const holePath = ringPath(holeCut.ring, holeCut.afterIndex, holeCut.beforeIndex);
914
+ const merged = [];
915
+ appendPath(merged, outerPath, epsilon);
916
+ appendPath(merged, holePath, epsilon);
917
+ if (merged.length > 1 && pointsAlmostEqual2D(merged[0], merged[merged.length - 1], epsilon)) merged.pop();
918
+ return {
919
+ ring: merged,
920
+ anchors: [
921
+ outerCut.ring[outerCut.beforeIndex],
922
+ holeCut.ring[holeCut.beforeIndex],
923
+ holeCut.ring[holeCut.afterIndex],
924
+ outerCut.ring[outerCut.afterIndex]
925
+ ].map((point) => [point[0], point[1]])
926
+ };
927
+ }
928
+ function clampSlitWidth(width, outer, hole, epsilon) {
929
+ const minPerimeter = Math.min(ringPerimeter(outer), ringPerimeter(hole));
930
+ return Math.max(epsilon * 2, Math.min(width, Math.max(minPerimeter * .24, epsilon * 2)));
931
+ }
932
+ function createRingCut(ring, anchorIndex, width, epsilon) {
933
+ return insertRingCutPoints(ring, moveAlongRing(ring, anchorIndex, -width * .5, epsilon), moveAlongRing(ring, anchorIndex, width * .5, epsilon), epsilon);
934
+ }
935
+ function moveAlongRing(ring, anchorIndex, offset, epsilon) {
936
+ if (!Number.isFinite(offset) || Math.abs(offset) <= epsilon) return {
937
+ point: [ring[anchorIndex][0], ring[anchorIndex][1]],
938
+ vertexIndex: anchorIndex,
939
+ t: 0
940
+ };
941
+ let remaining = Math.abs(offset);
942
+ let currentIndex = anchorIndex;
943
+ const direction = offset > 0 ? 1 : -1;
944
+ while (remaining > epsilon) {
945
+ const nextIndex = direction > 0 ? (currentIndex + 1) % ring.length : mod(currentIndex - 1, ring.length);
946
+ const segmentIndex = direction > 0 ? currentIndex : nextIndex;
947
+ const segmentStart = ring[segmentIndex];
948
+ const segmentEnd = ring[(segmentIndex + 1) % ring.length];
949
+ const segmentLength = distance(segmentStart, segmentEnd);
950
+ if (segmentLength <= epsilon) {
951
+ currentIndex = direction > 0 ? nextIndex : segmentIndex;
952
+ continue;
953
+ }
954
+ if (remaining < segmentLength - epsilon) {
955
+ const t = remaining / segmentLength;
956
+ const ratio = direction > 0 ? t : 1 - t;
957
+ return {
958
+ point: [segmentStart[0] + (segmentEnd[0] - segmentStart[0]) * ratio, segmentStart[1] + (segmentEnd[1] - segmentStart[1]) * ratio],
959
+ segmentIndex,
960
+ t: ratio
961
+ };
962
+ }
963
+ remaining -= segmentLength;
964
+ currentIndex = direction > 0 ? nextIndex : segmentIndex;
965
+ }
966
+ return {
967
+ point: [ring[currentIndex][0], ring[currentIndex][1]],
968
+ vertexIndex: currentIndex,
969
+ t: 0
970
+ };
971
+ }
972
+ function insertRingCutPoints(ring, beforeCut, afterCut, epsilon) {
973
+ const cuts = [normalizeCutLocation("before", ring, beforeCut, epsilon), normalizeCutLocation("after", ring, afterCut, epsilon)];
974
+ const insertsBySegment = /* @__PURE__ */ new Map();
975
+ const cutIndices = {};
976
+ const augmented = [];
977
+ cuts.forEach((cut) => {
978
+ if (cut.vertexIndex != null) return;
979
+ const existing = insertsBySegment.get(cut.segmentIndex) ?? [];
980
+ existing.push(cut);
981
+ insertsBySegment.set(cut.segmentIndex, existing);
982
+ });
983
+ for (let index = 0; index < ring.length; index += 1) {
984
+ augmented.push([ring[index][0], ring[index][1]]);
985
+ cuts.forEach((cut) => {
986
+ if (cut.vertexIndex === index) cutIndices[cut.name] = augmented.length - 1;
987
+ });
988
+ (insertsBySegment.get(index) ?? []).sort((a, b) => a.t - b.t).forEach((cut) => {
989
+ augmented.push([cut.point[0], cut.point[1]]);
990
+ cutIndices[cut.name] = augmented.length - 1;
991
+ });
992
+ }
993
+ return {
994
+ ring: augmented,
995
+ beforeIndex: cutIndices.before,
996
+ afterIndex: cutIndices.after
997
+ };
998
+ }
999
+ function normalizeCutLocation(name, ring, cut, epsilon) {
1000
+ if (cut.vertexIndex != null) return {
1001
+ ...cut,
1002
+ name
1003
+ };
1004
+ const segmentStart = ring[cut.segmentIndex];
1005
+ const segmentEnd = ring[(cut.segmentIndex + 1) % ring.length];
1006
+ if (distance(cut.point, segmentStart) <= epsilon) return {
1007
+ point: [segmentStart[0], segmentStart[1]],
1008
+ vertexIndex: cut.segmentIndex,
1009
+ t: 0,
1010
+ name
1011
+ };
1012
+ if (distance(cut.point, segmentEnd) <= epsilon) return {
1013
+ point: [segmentEnd[0], segmentEnd[1]],
1014
+ vertexIndex: (cut.segmentIndex + 1) % ring.length,
1015
+ t: 1,
1016
+ name
1017
+ };
1018
+ return {
1019
+ ...cut,
1020
+ name
1021
+ };
1022
+ }
1023
+ function ringPath(ring, startIndex, endIndex) {
1024
+ if (startIndex == null || endIndex == null) return copyRing(ring);
1025
+ const path = [ring[startIndex]];
1026
+ let cursor = startIndex;
1027
+ while (cursor !== endIndex) {
1028
+ cursor = (cursor + 1) % ring.length;
1029
+ path.push(ring[cursor]);
1030
+ }
1031
+ return path;
1032
+ }
1033
+ function appendPath(target, path, epsilon) {
1034
+ path.forEach((point) => {
1035
+ if (target.length === 0 || !pointsAlmostEqual2D(target[target.length - 1], point, epsilon)) target.push([point[0], point[1]]);
1036
+ });
1037
+ }
1038
+ function resampleRing(ring, step, anchors = []) {
1039
+ if (ring.length < 3) return copyRing(ring);
1040
+ const normalizedAnchors = normalizeAnchorPoints(ring, anchors);
1041
+ if (normalizedAnchors.length >= 2) return resampleRingWithAnchors(ring, step, normalizedAnchors);
1042
+ return resampleClosedPath(ring, step);
1043
+ }
1044
+ function normalizeAnchorPoints(ring, anchors) {
1045
+ const indices = anchors.map((anchor) => findRingPointIndex(ring, anchor)).filter((index) => index >= 0).sort((a, b) => a - b);
1046
+ return indices.filter((index, position) => index !== indices[position - 1]);
1047
+ }
1048
+ function findRingPointIndex(ring, point, epsilon = 1e-6) {
1049
+ for (let index = 0; index < ring.length; index += 1) if (pointsAlmostEqual2D(ring[index], point, epsilon)) return index;
1050
+ return -1;
1051
+ }
1052
+ function resampleRingWithAnchors(ring, step, anchorIndices) {
1053
+ const result = [];
1054
+ for (let index = 0; index < anchorIndices.length; index += 1) {
1055
+ const startIndex = anchorIndices[index];
1056
+ const endIndex = anchorIndices[(index + 1) % anchorIndices.length];
1057
+ appendPath(result, resampleOpenPath(ringPath(ring, startIndex, endIndex), step), 1e-6);
1058
+ }
1059
+ if (result.length > 1 && pointsAlmostEqual2D(result[0], result[result.length - 1], 1e-6)) result.pop();
1060
+ if (result.length < 3 || result.length >= ring.length) return copyRing(ring);
1061
+ return result;
1062
+ }
1063
+ function resampleClosedPath(ring, step) {
1064
+ const perimeter = ringPerimeter(ring);
1065
+ if (perimeter <= 1e-9) return copyRing(ring);
1066
+ const pointCount = Math.max(3, Math.round(perimeter / step));
1067
+ if (pointCount >= ring.length) return copyRing(ring);
1068
+ const sampled = [];
1069
+ for (let index = 0; index < pointCount; index += 1) {
1070
+ const point = pointAtClosedPathDistance(ring, perimeter * index / pointCount);
1071
+ if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
1072
+ }
1073
+ if (sampled.length < 3 || sampled.length >= ring.length) return copyRing(ring);
1074
+ return sampled;
1075
+ }
1076
+ function resampleOpenPath(path, step) {
1077
+ if (path.length <= 2) return path.map((point) => [point[0], point[1]]);
1078
+ const length = polylineLength(path);
1079
+ if (length <= step) return [[path[0][0], path[0][1]], [path[path.length - 1][0], path[path.length - 1][1]]];
1080
+ const divisionCount = Math.max(1, Math.round(length / step));
1081
+ const sampled = [];
1082
+ for (let index = 0; index <= divisionCount; index += 1) {
1083
+ const point = pointAtOpenPathDistance(path, length * index / divisionCount);
1084
+ if (sampled.length === 0 || !pointsAlmostEqual2D(sampled[sampled.length - 1], point, 1e-6)) sampled.push(point);
1085
+ }
1086
+ return sampled;
1087
+ }
1088
+ //#endregion
1089
+ //#region src/paFont/paFont.js
1090
+ var PAFont = class PAFont {
1091
+ constructor(font) {
1092
+ this.font = font;
1093
+ this.unitsPerEm = font.unitsPerEm ?? 1e3;
1094
+ this._glyphTopologyCache = /* @__PURE__ */ new Map();
1095
+ this._glyphFlatCache = /* @__PURE__ */ new Map();
1096
+ }
1097
+ static async load(source, options = {}) {
1098
+ const opts = normalizeLoadOptions(options);
1099
+ if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) return new PAFont((0, opentype_js.parse)(toArrayBuffer(source)));
1100
+ if (typeof window !== "undefined") return new PAFont((0, opentype_js.parse)(await fetchFontBytes(resolveBrowserFontSource(source, opts.base))));
1101
+ const { target, loadOptions } = await resolveNodeFontSource(source, opts.base);
1102
+ return new PAFont(await (0, opentype_js.load)(target, void 0, loadOptions));
1103
+ }
1104
+ text(value, options = {}) {
1105
+ const opts = normalizeTextOptions(options);
1106
+ return createTextShape(this._layoutText(String(value ?? ""), opts), opts, this);
1107
+ }
1108
+ glyph(value, options = {}) {
1109
+ const opts = normalizeTextOptions(options);
1110
+ const glyph = this.font.charToGlyph(String(value ?? ""));
1111
+ return createTextShape({
1112
+ text: String(value ?? ""),
1113
+ glyphs: [{
1114
+ glyph,
1115
+ x: opts.x,
1116
+ y: opts.y,
1117
+ size: opts.size,
1118
+ index: 0
1119
+ }],
1120
+ metrics: {
1121
+ width: (glyph.advanceWidth ?? 0) * (opts.size / this.unitsPerEm),
1122
+ x: opts.x,
1123
+ y: opts.y,
1124
+ size: opts.size
1125
+ }
1126
+ }, opts, this);
1127
+ }
1128
+ metrics(value, options = {}) {
1129
+ const opts = normalizeTextOptions(options);
1130
+ return measureText(this.font, String(value ?? ""), opts);
1131
+ }
1132
+ _getGlyphTopology(glyph) {
1133
+ const key = String(glyph.index);
1134
+ if (!this._glyphTopologyCache.has(key)) this._glyphTopologyCache.set(key, buildGlyphTopology(glyph, this.unitsPerEm));
1135
+ return this._glyphTopologyCache.get(key);
1136
+ }
1137
+ _getFlattenedGlyph(glyph, opts) {
1138
+ const key = `${glyph.index}:${opts.size}:${opts.flatten}`;
1139
+ if (!this._glyphFlatCache.has(key)) {
1140
+ const topology = this._getGlyphTopology(glyph);
1141
+ this._glyphFlatCache.set(key, flattenGlyphTopology(topology, {
1142
+ size: opts.size,
1143
+ flatten: opts.flatten
1144
+ }));
1145
+ }
1146
+ return this._glyphFlatCache.get(key);
1147
+ }
1148
+ _layoutText(value, opts) {
1149
+ return layoutGlyphs(this.font, value, opts);
1150
+ }
1151
+ };
1152
+ async function fetchFontBytes(source) {
1153
+ const response = await fetch(source);
1154
+ if (!response.ok) throw new Error(`Failed to load font from ${source}: ${response.status} ${response.statusText}`);
1155
+ const bytes = await response.arrayBuffer();
1156
+ const signature = readSignature(bytes);
1157
+ if ((response.headers.get("content-type") ?? "").includes("text/html") || isHtmlSignature(signature)) throw new Error(`Font URL returned HTML instead of font data: ${source}. Use a public font URL like "/assets/font.otf", or pass { base: import.meta.url } when loading a module-relative string path. Plain string paths in the browser are resolved from the current page URL.`);
1158
+ return bytes;
1159
+ }
1160
+ function normalizeLoadOptions(options = {}) {
1161
+ if (options == null) return {};
1162
+ if (typeof options !== "object" || Array.isArray(options)) throw new TypeError("PAFont.load() options must be an object.");
1163
+ return options;
1164
+ }
1165
+ function resolveBrowserFontSource(source, base) {
1166
+ if (source instanceof URL) return source.href;
1167
+ if (typeof source !== "string") throw new TypeError("PAFont.load() expects a string, URL, ArrayBuffer, or ArrayBufferView.");
1168
+ if (base == null) return source;
1169
+ return new URL(source, normalizeBase(base)).href;
1170
+ }
1171
+ async function resolveNodeFontSource(source, base) {
1172
+ if (source instanceof URL) return toNodeLoadTarget(source);
1173
+ if (typeof source !== "string") throw new TypeError("PAFont.load() expects a string, URL, ArrayBuffer, or ArrayBufferView.");
1174
+ if (base == null) {
1175
+ if (isLoadableUrlString(source)) return toNodeLoadTarget(new URL(source));
1176
+ return {
1177
+ target: source,
1178
+ loadOptions: void 0
1179
+ };
1180
+ }
1181
+ return toNodeLoadTarget(new URL(source, normalizeBase(base)));
1182
+ }
1183
+ function normalizeBase(base) {
1184
+ if (base instanceof URL) return base;
1185
+ if (typeof base === "string") return base;
1186
+ throw new TypeError("PAFont.load() option \"base\" must be a string or URL.");
1187
+ }
1188
+ async function toNodeLoadTarget(url) {
1189
+ if (url.protocol === "file:") return {
1190
+ target: fileUrlToPath(url),
1191
+ loadOptions: void 0
1192
+ };
1193
+ return {
1194
+ target: url.href,
1195
+ loadOptions: { isUrl: true }
1196
+ };
1197
+ }
1198
+ function fileUrlToPath(url) {
1199
+ const pathname = decodeURIComponent(url.pathname);
1200
+ if (/^\/[a-zA-Z]:/.test(pathname)) return pathname.slice(1);
1201
+ if (url.hostname) return `//${url.hostname}${pathname}`;
1202
+ return pathname;
1203
+ }
1204
+ function isLoadableUrlString(value) {
1205
+ return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("file://");
1206
+ }
1207
+ function readSignature(bytes) {
1208
+ const view = new Uint8Array(bytes, 0, Math.min(4, bytes.byteLength));
1209
+ let signature = "";
1210
+ for (const value of view) signature += String.fromCharCode(value);
1211
+ return signature.toLowerCase();
1212
+ }
1213
+ function isHtmlSignature(signature) {
1214
+ return signature === "<!do" || signature === "<htm";
1215
+ }
1216
+ //#endregion
1217
+ exports.PAFont = PAFont;
1218
+ exports.PAShape = PAShape;
1219
+ exports.default = PAFont;
1220
+
1221
+ //# sourceMappingURL=paFont.cjs.map