react-native-nano-icons 0.1.2 → 0.1.4
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/README.md +20 -164
- package/android/build.gradle +28 -0
- package/android/src/main/java/com/nanoicons/NanoIconView.kt +78 -0
- package/android/src/main/java/com/nanoicons/NanoIconViewManager.kt +84 -0
- package/android/src/main/java/com/nanoicons/NanoIconsPackage.kt +22 -0
- package/ios/NanoIconView.h +4 -0
- package/ios/NanoIconView.mm +286 -0
- package/lib/commonjs/cli/build.js +1 -1
- package/lib/commonjs/cli/config.d.ts +2 -2
- package/lib/commonjs/cli/config.js +7 -6
- package/lib/commonjs/scripts/cli.js +15 -5
- package/lib/commonjs/src/core/font/compile.d.ts +13 -2
- package/lib/commonjs/src/core/font/compile.js +49 -46
- package/lib/commonjs/src/core/pipeline/managers.js +19 -3
- package/lib/commonjs/src/core/pipeline/run.js +121 -32
- package/lib/commonjs/src/core/svg/layers.d.ts +16 -0
- package/lib/commonjs/src/core/svg/layers.js +27 -0
- package/lib/commonjs/src/core/svg/svg_dom.d.ts +29 -1
- package/lib/commonjs/src/core/svg/svg_dom.js +78 -2
- package/lib/commonjs/src/core/svg/svg_pathops.d.ts +11 -0
- package/lib/commonjs/src/core/svg/svg_pathops.js +209 -19
- package/lib/commonjs/src/core/types.d.ts +30 -15
- package/lib/module/core/font/compile.js +52 -41
- package/lib/module/core/font/compile.js.map +1 -1
- package/lib/module/core/pipeline/managers.js +17 -3
- package/lib/module/core/pipeline/managers.js.map +1 -1
- package/lib/module/core/pipeline/run.js +131 -44
- package/lib/module/core/pipeline/run.js.map +1 -1
- package/lib/module/core/shims/picosvg-0.22.3-py3-none-any.whl +0 -0
- package/lib/module/core/svg/layers.js +34 -0
- package/lib/module/core/svg/layers.js.map +1 -1
- package/lib/module/core/svg/svg_dom.js +91 -4
- package/lib/module/core/svg/svg_dom.js.map +1 -1
- package/lib/module/core/svg/svg_pathops.js +203 -19
- package/lib/module/core/svg/svg_pathops.js.map +1 -1
- package/lib/module/createNanoIconsSet.js +3 -79
- package/lib/module/createNanoIconsSet.js.map +1 -1
- package/lib/module/createNanoIconsSet.native.js +108 -0
- package/lib/module/createNanoIconsSet.native.js.map +1 -0
- package/lib/module/createNanoIconsSet.shared.js +91 -0
- package/lib/module/createNanoIconsSet.shared.js.map +1 -0
- package/lib/module/index.js +1 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/NanoIconViewNativeComponent.ts +15 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/shallowEqualColor.js +15 -0
- package/lib/module/utils/shallowEqualColor.js.map +1 -0
- package/lib/typescript/src/core/font/compile.d.ts +13 -2
- package/lib/typescript/src/core/font/compile.d.ts.map +1 -1
- package/lib/typescript/src/core/pipeline/managers.d.ts.map +1 -1
- package/lib/typescript/src/core/pipeline/run.d.ts.map +1 -1
- package/lib/typescript/src/core/svg/layers.d.ts +16 -0
- package/lib/typescript/src/core/svg/layers.d.ts.map +1 -1
- package/lib/typescript/src/core/svg/svg_dom.d.ts +29 -1
- package/lib/typescript/src/core/svg/svg_dom.d.ts.map +1 -1
- package/lib/typescript/src/core/svg/svg_pathops.d.ts +11 -0
- package/lib/typescript/src/core/svg/svg_pathops.d.ts.map +1 -1
- package/lib/typescript/src/core/types.d.ts +30 -15
- package/lib/typescript/src/core/types.d.ts.map +1 -1
- package/lib/typescript/src/createNanoIconsSet.d.ts +5 -18
- package/lib/typescript/src/createNanoIconsSet.d.ts.map +1 -1
- package/lib/typescript/src/createNanoIconsSet.native.d.ts +7 -0
- package/lib/typescript/src/createNanoIconsSet.native.d.ts.map +1 -0
- package/lib/typescript/src/createNanoIconsSet.shared.d.ts +11 -0
- package/lib/typescript/src/createNanoIconsSet.shared.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/NanoIconViewNativeComponent.d.ts +14 -0
- package/lib/typescript/src/specs/NanoIconViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +19 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils/shallowEqualColor.d.ts +4 -0
- package/lib/typescript/src/utils/shallowEqualColor.d.ts.map +1 -0
- package/package.json +22 -5
- package/react-native-nano-icons.podspec +18 -0
- package/scripts/cli.ts +14 -5
- package/src/core/font/compile.ts +65 -61
- package/src/core/pipeline/managers.ts +26 -3
- package/src/core/pipeline/run.ts +156 -38
- package/src/core/shims/picosvg-0.22.3-py3-none-any.whl +0 -0
- package/src/core/svg/layers.ts +44 -0
- package/src/core/svg/svg_dom.ts +96 -4
- package/src/core/svg/svg_pathops.ts +245 -27
- package/src/core/types.ts +21 -10
- package/src/createNanoIconsSet.native.tsx +140 -0
- package/src/createNanoIconsSet.shared.tsx +121 -0
- package/src/createNanoIconsSet.tsx +7 -126
- package/src/index.ts +1 -2
- package/src/specs/NanoIconViewNativeComponent.ts +15 -0
- package/src/types.ts +27 -0
- package/src/utils/shallowEqualColor.ts +17 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertEvenoddToWinding = convertEvenoddToWinding;
|
|
3
4
|
exports.buildPathopsBackend = buildPathopsBackend;
|
|
4
5
|
//** */
|
|
5
6
|
// ----- numeric helpers -----
|
|
@@ -381,9 +382,11 @@ function bestStartMinYMinX(contourCmds, VERB) {
|
|
|
381
382
|
}
|
|
382
383
|
return best;
|
|
383
384
|
}
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
// Containment helpers (module-level for reuse by fixPathWinding)
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
function buildVerbMap(PathKit) {
|
|
389
|
+
return {
|
|
387
390
|
MOVE: PathKit.MOVE_VERB ?? 0,
|
|
388
391
|
LINE: PathKit.LINE_VERB ?? 1,
|
|
389
392
|
QUAD: PathKit.QUAD_VERB ?? 2,
|
|
@@ -391,6 +394,206 @@ function buildPathopsBackend(PathKit) {
|
|
|
391
394
|
CUBIC: PathKit.CUBIC_VERB ?? 4,
|
|
392
395
|
CLOSE: PathKit.CLOSE_VERB ?? 5,
|
|
393
396
|
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Convert contour commands to a polyline by sampling curves.
|
|
400
|
+
* Used for ray-casting containment tests.
|
|
401
|
+
*/
|
|
402
|
+
function contourToPolyline(contourCmds, V, steps = 8) {
|
|
403
|
+
let cx = 0, cy = 0;
|
|
404
|
+
let sx = 0, sy = 0;
|
|
405
|
+
const pts = [];
|
|
406
|
+
for (const cmd of contourCmds) {
|
|
407
|
+
const v = cmd[0];
|
|
408
|
+
if (v === V.MOVE) {
|
|
409
|
+
cx = cmd[1];
|
|
410
|
+
cy = cmd[2];
|
|
411
|
+
sx = cx;
|
|
412
|
+
sy = cy;
|
|
413
|
+
pts.push([cx, cy]);
|
|
414
|
+
}
|
|
415
|
+
else if (v === V.LINE) {
|
|
416
|
+
cx = cmd[1];
|
|
417
|
+
cy = cmd[2];
|
|
418
|
+
pts.push([cx, cy]);
|
|
419
|
+
}
|
|
420
|
+
else if (v === V.QUAD) {
|
|
421
|
+
const x0 = cx, y0 = cy;
|
|
422
|
+
const x1 = cmd[1], y1 = cmd[2];
|
|
423
|
+
const x2 = cmd[3], y2 = cmd[4];
|
|
424
|
+
for (let i = 1; i <= steps; i++) {
|
|
425
|
+
const t = i / steps;
|
|
426
|
+
const mt = 1 - t;
|
|
427
|
+
pts.push([mt * mt * x0 + 2 * mt * t * x1 + t * t * x2, mt * mt * y0 + 2 * mt * t * y1 + t * t * y2]);
|
|
428
|
+
}
|
|
429
|
+
cx = x2;
|
|
430
|
+
cy = y2;
|
|
431
|
+
}
|
|
432
|
+
else if (v === V.CUBIC) {
|
|
433
|
+
const x0 = cx, y0 = cy;
|
|
434
|
+
const x1 = cmd[1], y1 = cmd[2];
|
|
435
|
+
const x2 = cmd[3], y2 = cmd[4];
|
|
436
|
+
const x3 = cmd[5], y3 = cmd[6];
|
|
437
|
+
for (let i = 1; i <= steps; i++) {
|
|
438
|
+
const t = i / steps;
|
|
439
|
+
const mt = 1 - t;
|
|
440
|
+
pts.push([
|
|
441
|
+
mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3,
|
|
442
|
+
mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3,
|
|
443
|
+
]);
|
|
444
|
+
}
|
|
445
|
+
cx = x3;
|
|
446
|
+
cy = y3;
|
|
447
|
+
}
|
|
448
|
+
else if (v === V.CLOSE) {
|
|
449
|
+
if (cx !== sx || cy !== sy)
|
|
450
|
+
pts.push([sx, sy]);
|
|
451
|
+
cx = sx;
|
|
452
|
+
cy = sy;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return pts;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Ray-casting point-in-polygon test.
|
|
459
|
+
*/
|
|
460
|
+
function pointInPolygon(px, py, poly) {
|
|
461
|
+
let inside = false;
|
|
462
|
+
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
463
|
+
const [xi, yi] = poly[i];
|
|
464
|
+
const [xj, yj] = poly[j];
|
|
465
|
+
if (yi > py !== yj > py &&
|
|
466
|
+
px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) {
|
|
467
|
+
inside = !inside;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return inside;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Get a representative point on the contour boundary (midpoint of first segment).
|
|
474
|
+
*/
|
|
475
|
+
function getContourSamplePoint(contourCmds, V) {
|
|
476
|
+
let cx = 0, cy = 0;
|
|
477
|
+
for (const cmd of contourCmds) {
|
|
478
|
+
const v = cmd[0];
|
|
479
|
+
if (v === V.MOVE) {
|
|
480
|
+
cx = cmd[1];
|
|
481
|
+
cy = cmd[2];
|
|
482
|
+
}
|
|
483
|
+
else if (v === V.LINE) {
|
|
484
|
+
return [(cx + cmd[1]) / 2, (cy + cmd[2]) / 2];
|
|
485
|
+
}
|
|
486
|
+
else if (v === V.QUAD) {
|
|
487
|
+
const t = 0.5, mt = 0.5;
|
|
488
|
+
return [
|
|
489
|
+
mt * mt * cx + 2 * mt * t * cmd[1] + t * t * cmd[3],
|
|
490
|
+
mt * mt * cy + 2 * mt * t * cmd[2] + t * t * cmd[4],
|
|
491
|
+
];
|
|
492
|
+
}
|
|
493
|
+
else if (v === V.CUBIC) {
|
|
494
|
+
const t = 0.5, mt = 0.5;
|
|
495
|
+
return [
|
|
496
|
+
mt ** 3 * cx + 3 * mt ** 2 * t * cmd[1] + 3 * mt * t ** 2 * cmd[3] + t ** 3 * cmd[5],
|
|
497
|
+
mt ** 3 * cy + 3 * mt ** 2 * t * cmd[2] + 3 * mt * t ** 2 * cmd[4] + t ** 3 * cmd[6],
|
|
498
|
+
];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Apply containment-based winding fix to contour objects.
|
|
505
|
+
* Even nesting depth = CCW (outer), odd = CW (hole).
|
|
506
|
+
*/
|
|
507
|
+
function applyContainmentWinding(contourObjs, V) {
|
|
508
|
+
const n = contourObjs.length;
|
|
509
|
+
if (n === 0)
|
|
510
|
+
return;
|
|
511
|
+
const polylines = contourObjs.map((obj) => contourToPolyline(obj.cmds, V));
|
|
512
|
+
const samplePts = contourObjs.map((obj) => getContourSamplePoint(obj.cmds, V));
|
|
513
|
+
const depths = new Array(n).fill(0);
|
|
514
|
+
for (let i = 0; i < n; i++) {
|
|
515
|
+
const pt = samplePts[i];
|
|
516
|
+
if (!pt)
|
|
517
|
+
continue;
|
|
518
|
+
for (let j = 0; j < n; j++) {
|
|
519
|
+
if (i === j)
|
|
520
|
+
continue;
|
|
521
|
+
if (pointInPolygon(pt[0], pt[1], polylines[j])) {
|
|
522
|
+
depths[i] = (depths[i] ?? 0) + 1;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const ensureOrient = (obj, wantCCW) => {
|
|
527
|
+
const a = approxSignedAreaFromContourCmds(obj.cmds, V);
|
|
528
|
+
const isCCW = a > 0;
|
|
529
|
+
if (wantCCW !== isCCW) {
|
|
530
|
+
obj.cmds = reverseClosedContourKeepStart(obj.cmds, obj.explicitCloseWanted, V);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
for (let i = 0; i < n; i++) {
|
|
534
|
+
ensureOrient(contourObjs[i], depths[i] % 2 === 0);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Convert a path `d` string with evenodd fill semantics to an equivalent
|
|
539
|
+
* path that renders identically under nonzero winding.
|
|
540
|
+
*
|
|
541
|
+
* Steps:
|
|
542
|
+
* 1. Parse via PathKit, set fill type to EVENODD, simplify (resolve topology)
|
|
543
|
+
* 2. Split into contours, compute containment depths
|
|
544
|
+
* 3. Fix winding: even depth = CCW (outer), odd depth = CW (hole)
|
|
545
|
+
* 4. Reconstruct d string
|
|
546
|
+
*/
|
|
547
|
+
function convertEvenoddToWinding(PathKit, d) {
|
|
548
|
+
const V = buildVerbMap(PathKit);
|
|
549
|
+
// 1. Parse and simplify with EVENODD fill type
|
|
550
|
+
const p = PathKit.FromSVGString(d);
|
|
551
|
+
if (!p)
|
|
552
|
+
return d;
|
|
553
|
+
const FILL_EVENODD = PathKit?.FillType?.EVENODD ?? PathKit?.FillType?.EVEN_ODD ?? 1;
|
|
554
|
+
p.setFillType(FILL_EVENODD);
|
|
555
|
+
p.simplify();
|
|
556
|
+
// Get the simplified SVG string and re-parse for command access
|
|
557
|
+
const simplified = p.toSVGString();
|
|
558
|
+
p.delete?.();
|
|
559
|
+
const p2 = PathKit.FromSVGString(simplified);
|
|
560
|
+
if (!p2)
|
|
561
|
+
return simplified;
|
|
562
|
+
const cmds = p2.toCmds();
|
|
563
|
+
p2.delete?.();
|
|
564
|
+
if (cmds.length === 0)
|
|
565
|
+
return simplified;
|
|
566
|
+
// 2. Split into contours
|
|
567
|
+
const contourObjs = splitContours(cmds, V).map((c) => {
|
|
568
|
+
const explicitCloseWanted = explicitCloseWantedFromCmds(c, V);
|
|
569
|
+
const cc = ensureClosed(c, V);
|
|
570
|
+
const a = approxSignedAreaFromContourCmds(cc, V);
|
|
571
|
+
return { cmds: cc, absA: Math.abs(a), explicitCloseWanted };
|
|
572
|
+
});
|
|
573
|
+
contourObjs.sort((x, y) => y.absA - x.absA);
|
|
574
|
+
// 3. Fix winding via containment analysis
|
|
575
|
+
applyContainmentWinding(contourObjs, V);
|
|
576
|
+
// 4. Reconstruct d string from fixed commands
|
|
577
|
+
const allCmds = contourObjs.flatMap((x) => x.cmds);
|
|
578
|
+
const parts = [];
|
|
579
|
+
for (const cmd of allCmds) {
|
|
580
|
+
const v = cmd[0];
|
|
581
|
+
if (v === V.MOVE)
|
|
582
|
+
parts.push(`M${roundN(cmd[1])} ${roundN(cmd[2])}`);
|
|
583
|
+
else if (v === V.LINE)
|
|
584
|
+
parts.push(`L${roundN(cmd[1])} ${roundN(cmd[2])}`);
|
|
585
|
+
else if (v === V.QUAD)
|
|
586
|
+
parts.push(`Q${roundN(cmd[1])} ${roundN(cmd[2])} ${roundN(cmd[3])} ${roundN(cmd[4])}`);
|
|
587
|
+
else if (v === V.CUBIC)
|
|
588
|
+
parts.push(`C${roundN(cmd[1])} ${roundN(cmd[2])} ${roundN(cmd[3])} ${roundN(cmd[4])} ${roundN(cmd[5])} ${roundN(cmd[6])}`);
|
|
589
|
+
else if (v === V.CLOSE)
|
|
590
|
+
parts.push('Z');
|
|
591
|
+
}
|
|
592
|
+
return parts.join(' ');
|
|
593
|
+
}
|
|
594
|
+
// proxy to for picosvg to interatc with pathkit
|
|
595
|
+
function buildPathopsBackend(PathKit) {
|
|
596
|
+
const VERB = buildVerbMap(PathKit);
|
|
394
597
|
const FILL_EVENODD = PathKit?.FillType?.EVENODD ?? PathKit?.FillType?.EVEN_ODD ?? 1;
|
|
395
598
|
const FILL_WINDING = PathKit?.FillType?.WINDING ?? PathKit?.FillType?.NONZERO ?? 0;
|
|
396
599
|
function cmdsViaSvgRoundtrip(h) {
|
|
@@ -413,22 +616,9 @@ function buildPathopsBackend(PathKit) {
|
|
|
413
616
|
});
|
|
414
617
|
// Big-to-small ordering gives deterministic outer→inner ordering.
|
|
415
618
|
contourObjs.sort((x, y) => y.absA - x.absA);
|
|
416
|
-
//
|
|
417
|
-
// -
|
|
418
|
-
|
|
419
|
-
const ensureOrient = (obj, wantCCW) => {
|
|
420
|
-
const a = approxSignedAreaFromContourCmds(obj.cmds, VERB);
|
|
421
|
-
const isCCW = a > 0;
|
|
422
|
-
if (wantCCW !== isCCW) {
|
|
423
|
-
obj.cmds = reverseClosedContourKeepStart(obj.cmds, obj.explicitCloseWanted, VERB);
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
if (contourObjs.length) {
|
|
427
|
-
ensureOrient(contourObjs[0], true);
|
|
428
|
-
for (let i = 1; i < contourObjs.length; i++) {
|
|
429
|
-
ensureOrient(contourObjs[i], false);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
619
|
+
// Containment-based winding: compute nesting depth per contour via
|
|
620
|
+
// ray-casting point-in-polygon. Even depth = outer (CCW), odd = hole (CW).
|
|
621
|
+
applyContainmentWinding(contourObjs, VERB);
|
|
432
622
|
// Rotate starts deterministically:
|
|
433
623
|
// 1) If we have recorded MOVE points, try to rotate contour to a move point that lies on it.
|
|
434
624
|
// 2) Otherwise, rotate to minY/minX point (helps stroke-ish paths)
|
|
@@ -99,22 +99,37 @@ export type PyodideModule = {
|
|
|
99
99
|
get: (key: string) => unknown;
|
|
100
100
|
};
|
|
101
101
|
};
|
|
102
|
-
export type GlyphLayer =
|
|
103
|
-
|
|
104
|
-
color: string;
|
|
105
|
-
};
|
|
106
|
-
export type GlyphEntry = {
|
|
107
|
-
adv: number;
|
|
108
|
-
layers: GlyphLayer[];
|
|
109
|
-
};
|
|
102
|
+
export type GlyphLayer = [codepoint: number, color: string];
|
|
103
|
+
export type GlyphEntry = [adv: number, layers: GlyphLayer[]];
|
|
110
104
|
export type IconsMap = Record<string, GlyphEntry>;
|
|
105
|
+
/**
|
|
106
|
+
* m - metadata,
|
|
107
|
+
* f - font family,
|
|
108
|
+
* u - units per em,
|
|
109
|
+
* z - safe zone,
|
|
110
|
+
* s - start unicode,
|
|
111
|
+
* h - hash,
|
|
112
|
+
* i - icons,
|
|
113
|
+
* adv - advance width,
|
|
114
|
+
*/
|
|
111
115
|
export type NanoGlyphMap = {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
m: {
|
|
117
|
+
f: string;
|
|
118
|
+
u: number;
|
|
119
|
+
z: number;
|
|
120
|
+
s: number;
|
|
121
|
+
h?: string;
|
|
122
|
+
};
|
|
123
|
+
i: IconsMap;
|
|
124
|
+
};
|
|
125
|
+
/** Accepts JSON-inferred types where arrays aren't tuples. */
|
|
126
|
+
export type NanoGlyphMapInput = {
|
|
127
|
+
m: {
|
|
128
|
+
f: string;
|
|
129
|
+
u: number;
|
|
130
|
+
z: number;
|
|
131
|
+
s: number;
|
|
132
|
+
h?: string;
|
|
118
133
|
};
|
|
119
|
-
|
|
134
|
+
i: Record<string, readonly unknown[]>;
|
|
120
135
|
};
|
|
@@ -2,32 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { once } from 'node:events';
|
|
6
5
|
import { forceTtfMetrics } from './metrics.js';
|
|
7
6
|
import svg2ttf from 'svg2ttf';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const codepoint = parseCodepointFromFilename(filename);
|
|
11
|
-
const name = path.basename(filename, '.svg');
|
|
12
|
-
return new Promise((resolve, reject) => {
|
|
13
|
-
const glyphStream = fs.createReadStream(svgPath);
|
|
14
|
-
glyphStream.metadata = {
|
|
15
|
-
name,
|
|
16
|
-
unicode: [String.fromCodePoint(codepoint)]
|
|
17
|
-
};
|
|
18
|
-
glyphStream.on('error', reject);
|
|
19
|
-
// Do not add fontStream.on("error", reject) here — one per glyph would exceed
|
|
20
|
-
// Node's default MaxListeners (10). Font stream errors are handled once below.
|
|
21
|
-
fontStream.write(glyphStream);
|
|
22
|
-
glyphStream.on('end', resolve);
|
|
23
|
-
});
|
|
7
|
+
function escapeXml(s) {
|
|
8
|
+
return s.replace(/&/g, '&').replace(/"/g, '"');
|
|
24
9
|
}
|
|
25
|
-
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build an SVG font XML string from pre-transformed glyph data.
|
|
13
|
+
*/
|
|
14
|
+
function buildSvgFontXml(opts) {
|
|
26
15
|
const {
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
fontName,
|
|
17
|
+
glyphs,
|
|
18
|
+
upm,
|
|
19
|
+
ascent,
|
|
20
|
+
descent
|
|
21
|
+
} = opts;
|
|
22
|
+
const glyphLines = glyphs.map(g => {
|
|
23
|
+
const hex = g.codepoint.toString(16);
|
|
24
|
+
const name = `u${hex.padStart(4, '0')}`;
|
|
25
|
+
return `<glyph glyph-name="${name}" unicode="&#x${hex};" horiz-adv-x="${g.advanceWidth}" d="${escapeXml(g.d)}"/>`;
|
|
26
|
+
});
|
|
27
|
+
return `<?xml version="1.0" standalone="no"?>
|
|
28
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
29
|
+
<svg xmlns="http://www.w3.org/2000/svg">
|
|
30
|
+
<defs>
|
|
31
|
+
<font id="${escapeXml(fontName)}" horiz-adv-x="${upm}">
|
|
32
|
+
<font-face font-family="${escapeXml(fontName)}" units-per-em="${upm}" ascent="${ascent}" descent="${-Math.abs(descent)}"/>
|
|
33
|
+
<missing-glyph horiz-adv-x="0"/>
|
|
34
|
+
${glyphLines.join('\n')}
|
|
35
|
+
</font>
|
|
36
|
+
</defs>
|
|
37
|
+
</svg>`;
|
|
38
|
+
}
|
|
39
|
+
export function parseCompileTtfFromGlyphsError(err, codepointToIcon) {
|
|
40
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
+
const cpMatch = msg.match(/glyph\s+"u([0-9a-fA-F]+)"/);
|
|
42
|
+
if (cpMatch) {
|
|
43
|
+
const cp = parseInt(cpMatch[1], 16);
|
|
44
|
+
const iconName = codepointToIcon.get(cp);
|
|
45
|
+
const detail = iconName ? `icon "${iconName}" (codepoint u${cpMatch[1]})` : `codepoint u${cpMatch[1]}`;
|
|
46
|
+
throw new Error(`Font compilation failed for ${detail}: ${msg}`);
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compile a TTF font from pre-transformed glyph data.
|
|
53
|
+
* Builds SVG font XML directly (no intermediate files), then converts via svg2ttf.
|
|
54
|
+
*/
|
|
55
|
+
export async function compileTtfFromGlyphs(opts) {
|
|
29
56
|
const {
|
|
30
|
-
|
|
57
|
+
glyphs,
|
|
31
58
|
outTtfPath,
|
|
32
59
|
fontName,
|
|
33
60
|
upm,
|
|
@@ -35,30 +62,14 @@ export async function compileTtfFromGlyphSVGs(opts) {
|
|
|
35
62
|
descent
|
|
36
63
|
} = opts;
|
|
37
64
|
const lineGap = opts.lineGap ?? 0;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const fontStream = new SVGIcons2SVGFontStream({
|
|
65
|
+
if (glyphs.length === 0) throw new Error('No glyphs to compile');
|
|
66
|
+
const svgFontString = buildSvgFontXml({
|
|
41
67
|
fontName,
|
|
42
|
-
|
|
43
|
-
|
|
68
|
+
glyphs,
|
|
69
|
+
upm,
|
|
44
70
|
ascent,
|
|
45
71
|
descent
|
|
46
72
|
});
|
|
47
|
-
const svgFontChunks = [];
|
|
48
|
-
fontStream.on('data', c => svgFontChunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
|
|
49
|
-
|
|
50
|
-
// Single error listener for the font stream; per-glyph errors are handled in writeGlyphStreamToFont.
|
|
51
|
-
let fontStreamReject;
|
|
52
|
-
const fontStreamErrorPromise = new Promise((_, rej) => {
|
|
53
|
-
fontStreamReject = rej;
|
|
54
|
-
});
|
|
55
|
-
fontStream.on('error', err => fontStreamReject(err));
|
|
56
|
-
for (const f of files) {
|
|
57
|
-
await Promise.race([writeGlyphStreamToFont(fontStream, path.join(glyphDir, f), f), fontStreamErrorPromise]);
|
|
58
|
-
}
|
|
59
|
-
fontStream.end();
|
|
60
|
-
await once(fontStream, 'end');
|
|
61
|
-
const svgFontString = Buffer.concat(svgFontChunks).toString('utf8');
|
|
62
73
|
const ttfRaw = svg2ttf(svgFontString);
|
|
63
74
|
const rawBuf = Buffer.from(ttfRaw.buffer);
|
|
64
75
|
const fixedBuf = forceTtfMetrics(rawBuf, upm, ascent, descent, lineGap);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["fs","path","
|
|
1
|
+
{"version":3,"names":["fs","path","forceTtfMetrics","svg2ttf","escapeXml","s","replace","buildSvgFontXml","opts","fontName","glyphs","upm","ascent","descent","glyphLines","map","g","hex","codepoint","toString","name","padStart","advanceWidth","d","Math","abs","join","parseCompileTtfFromGlyphsError","err","codepointToIcon","msg","Error","message","String","cpMatch","match","cp","parseInt","iconName","get","detail","compileTtfFromGlyphs","outTtfPath","lineGap","length","svgFontString","ttfRaw","rawBuf","Buffer","from","buffer","fixedBuf","mkdirSync","dirname","recursive","writeFileSync"],"sourceRoot":"../../../../src","sources":["core/font/compile.ts"],"mappings":";;AAAA,OAAOA,EAAE,MAAM,SAAS;AACxB,OAAOC,IAAI,MAAM,WAAW;AAE5B,SAASC,eAAe,QAAQ,cAAc;AAC9C,OAAOC,OAAO,MAAM,SAAS;AAS7B,SAASC,SAASA,CAACC,CAAS,EAAU;EACpC,OAAOA,CAAC,CAACC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAACA,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;AACzD;;AAEA;AACA;AACA;AACA,SAASC,eAAeA,CAACC,IAMxB,EAAU;EACT,MAAM;IAAEC,QAAQ;IAAEC,MAAM;IAAEC,GAAG;IAAEC,MAAM;IAAEC;EAAQ,CAAC,GAAGL,IAAI;EAEvD,MAAMM,UAAU,GAAGJ,MAAM,CAACK,GAAG,CAAEC,CAAC,IAAK;IACnC,MAAMC,GAAG,GAAGD,CAAC,CAACE,SAAS,CAACC,QAAQ,CAAC,EAAE,CAAC;IACpC,MAAMC,IAAI,GAAG,IAAIH,GAAG,CAACI,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;IACvC,OAAO,sBAAsBD,IAAI,iBAAiBH,GAAG,mBAAmBD,CAAC,CAACM,YAAY,QAAQlB,SAAS,CAACY,CAAC,CAACO,CAAC,CAAC,KAAK;EACnH,CAAC,CAAC;EAEF,OAAO;AACT;AACA;AACA;AACA,YAAYnB,SAAS,CAACK,QAAQ,CAAC,kBAAkBE,GAAG;AACpD,0BAA0BP,SAAS,CAACK,QAAQ,CAAC,mBAAmBE,GAAG,aAAaC,MAAM,cAAc,CAACY,IAAI,CAACC,GAAG,CAACZ,OAAO,CAAC;AACtH;AACA,EAAEC,UAAU,CAACY,IAAI,CAAC,IAAI,CAAC;AACvB;AACA;AACA,OAAO;AACP;AAEA,OAAO,SAASC,8BAA8BA,CAC5CC,GAAY,EACZC,eAAoC,EACpC;EACA,MAAMC,GAAG,GAAGF,GAAG,YAAYG,KAAK,GAAGH,GAAG,CAACI,OAAO,GAAGC,MAAM,CAACL,GAAG,CAAC;EAC5D,MAAMM,OAAO,GAAGJ,GAAG,CAACK,KAAK,CAAC,2BAA2B,CAAC;EACtD,IAAID,OAAO,EAAE;IACX,MAAME,EAAE,GAAGC,QAAQ,CAACH,OAAO,CAAC,CAAC,CAAC,EAAG,EAAE,CAAC;IACpC,MAAMI,QAAQ,GAAGT,eAAe,CAACU,GAAG,CAACH,EAAE,CAAC;IACxC,MAAMI,MAAM,GAAGF,QAAQ,GACnB,SAASA,QAAQ,iBAAiBJ,OAAO,CAAC,CAAC,CAAC,GAAG,GAC/C,cAAcA,OAAO,CAAC,CAAC,CAAC,EAAE;IAC9B,MAAM,IAAIH,KAAK,CAAC,+BAA+BS,MAAM,KAAKV,GAAG,EAAE,CAAC;EAClE;EACA,MAAMF,GAAG;AACX;;AAEA;AACA;AACA;AACA;AACA,OAAO,eAAea,oBAAoBA,CAACjC,IAQ1C,EAAiB;EAChB,MAAM;IAAEE,MAAM;IAAEgC,UAAU;IAAEjC,QAAQ;IAAEE,GAAG;IAAEC,MAAM;IAAEC;EAAQ,CAAC,GAAGL,IAAI;EACnE,MAAMmC,OAAO,GAAGnC,IAAI,CAACmC,OAAO,IAAI,CAAC;EAEjC,IAAIjC,MAAM,CAACkC,MAAM,KAAK,CAAC,EACrB,MAAM,IAAIb,KAAK,CAAC,sBAAsB,CAAC;EAEzC,MAAMc,aAAa,GAAGtC,eAAe,CAAC;IACpCE,QAAQ;IACRC,MAAM;IACNC,GAAG;IACHC,MAAM;IACNC;EACF,CAAC,CAAC;EAEF,MAAMiC,MAAM,GAAG3C,OAAO,CAAC0C,aAAa,CAAC;EACrC,MAAME,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACH,MAAM,CAACI,MAAM,CAAC;EAEzC,MAAMC,QAAQ,GAAGjD,eAAe,CAAC6C,MAAM,EAAEpC,GAAG,EAAEC,MAAM,EAAEC,OAAO,EAAE8B,OAAO,CAAC;EAEvE3C,EAAE,CAACoD,SAAS,CAACnD,IAAI,CAACoD,OAAO,CAACX,UAAU,CAAC,EAAE;IAAEY,SAAS,EAAE;EAAK,CAAC,CAAC;EAC3DtD,EAAE,CAACuD,aAAa,CAACb,UAAU,EAAES,QAAQ,CAAC;AACxC","ignoreList":[]}
|
|
@@ -47,14 +47,28 @@ export class PyodideManager {
|
|
|
47
47
|
await py.loadPackage(['micropip', 'lxml'], {
|
|
48
48
|
messageCallback: () => {}
|
|
49
49
|
});
|
|
50
|
+
|
|
51
|
+
// Resolve local picosvg wheel path for offline-first installation.
|
|
52
|
+
const picosvgWhlDir = path.join(getPackageRoot(), 'src', 'core', 'shims');
|
|
53
|
+
const picosvgWhl = (await fs.readdir(picosvgWhlDir)).find(f => f.startsWith('picosvg-') && f.endsWith('.whl'));
|
|
54
|
+
const localWhlUrl = picosvgWhl ? `file://${path.join(picosvgWhlDir, picosvgWhl)}` : null;
|
|
55
|
+
py.globals.set('_picosvg_local_whl', localWhlUrl);
|
|
50
56
|
await py.runPythonAsync(`
|
|
51
57
|
import sys
|
|
52
58
|
if "/" not in sys.path:
|
|
53
59
|
sys.path.insert(0, "/")
|
|
54
|
-
|
|
60
|
+
|
|
55
61
|
import micropip
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
|
|
63
|
+
_whl = _picosvg_local_whl
|
|
64
|
+
if _whl:
|
|
65
|
+
try:
|
|
66
|
+
await micropip.install(_whl, deps=False)
|
|
67
|
+
except Exception:
|
|
68
|
+
await micropip.install("picosvg", deps=False)
|
|
69
|
+
else:
|
|
70
|
+
await micropip.install("picosvg", deps=False)
|
|
71
|
+
|
|
58
72
|
import pathops
|
|
59
73
|
import picosvg
|
|
60
74
|
`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["loadPyodide","path","fs","buildPathopsBackend","getPackageRoot","__dirname","includes","sep","resolve","PathKitManager","instance","getInstance","PathKitInit","require","pathkitJsPath","pathkitBinDir","dirname","pathkitWasmPath","join","wasmBinary","readFile","pkInit","locateFile","file","PathKit","ready","PyodideManager","pyodideAsmPath","pyodideDir","py","indexURL","mountNodeFS","process","cwd","registerJsModule","pathopsPyPath","pathopsPy","FS","writeFile","loadPackage","messageCallback","
|
|
1
|
+
{"version":3,"names":["loadPyodide","path","fs","buildPathopsBackend","getPackageRoot","__dirname","includes","sep","resolve","PathKitManager","instance","getInstance","PathKitInit","require","pathkitJsPath","pathkitBinDir","dirname","pathkitWasmPath","join","wasmBinary","readFile","pkInit","locateFile","file","PathKit","ready","PyodideManager","pyodideAsmPath","pyodideDir","py","indexURL","mountNodeFS","process","cwd","registerJsModule","pathopsPyPath","pathopsPy","FS","writeFile","loadPackage","messageCallback","picosvgWhlDir","picosvgWhl","readdir","find","f","startsWith","endsWith","localWhlUrl","globals","set","runPythonAsync","picoFromFile","hostFilePath","content","svgContent","out","runPython"],"sourceRoot":"../../../../src","sources":["core/pipeline/managers.ts"],"mappings":"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","ignoreList":[]}
|