react-native-nano-icons 0.0.0 → 0.1.0

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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/app.plugin.js +1 -0
  4. package/lib/commonjs/cli/build.d.ts +28 -0
  5. package/lib/commonjs/cli/build.js +83 -0
  6. package/lib/commonjs/cli/config.d.ts +9 -0
  7. package/lib/commonjs/cli/config.js +25 -0
  8. package/lib/commonjs/cli/index.d.ts +4 -0
  9. package/lib/commonjs/cli/index.js +13 -0
  10. package/lib/commonjs/cli/link.d.ts +11 -0
  11. package/lib/commonjs/cli/link.js +135 -0
  12. package/lib/commonjs/cli/logger.d.ts +27 -0
  13. package/lib/commonjs/cli/logger.js +83 -0
  14. package/lib/commonjs/index.node.js +8 -0
  15. package/lib/commonjs/plugin/src/buildFonts.d.ts +7 -0
  16. package/lib/commonjs/plugin/src/buildFonts.js +23 -0
  17. package/lib/commonjs/plugin/src/index.d.ts +5 -0
  18. package/lib/commonjs/plugin/src/index.js +43 -0
  19. package/lib/commonjs/plugin/src/types.d.ts +31 -0
  20. package/lib/commonjs/plugin/src/types.js +2 -0
  21. package/lib/commonjs/plugin/src/withNanoIconsFontLinking.d.ts +14 -0
  22. package/lib/commonjs/plugin/src/withNanoIconsFontLinking.js +92 -0
  23. package/lib/commonjs/scripts/cli.js +28 -0
  24. package/lib/commonjs/src/const/colors.d.ts +1 -0
  25. package/lib/commonjs/src/const/colors.js +153 -0
  26. package/lib/commonjs/src/core/font/compile.d.ts +9 -0
  27. package/lib/commonjs/src/core/font/compile.js +68 -0
  28. package/lib/commonjs/src/core/font/metrics.d.ts +1 -0
  29. package/lib/commonjs/src/core/font/metrics.js +33 -0
  30. package/lib/commonjs/src/core/pipeline/config.d.ts +14 -0
  31. package/lib/commonjs/src/core/pipeline/config.js +17 -0
  32. package/lib/commonjs/src/core/pipeline/index.d.ts +3 -0
  33. package/lib/commonjs/src/core/pipeline/index.js +7 -0
  34. package/lib/commonjs/src/core/pipeline/managers.d.ts +11 -0
  35. package/lib/commonjs/src/core/pipeline/managers.js +89 -0
  36. package/lib/commonjs/src/core/pipeline/run.d.ts +14 -0
  37. package/lib/commonjs/src/core/pipeline/run.js +99 -0
  38. package/lib/commonjs/src/core/svg/layers.d.ts +24 -0
  39. package/lib/commonjs/src/core/svg/layers.js +42 -0
  40. package/lib/commonjs/src/core/svg/svg_dom.d.ts +22 -0
  41. package/lib/commonjs/src/core/svg/svg_dom.js +84 -0
  42. package/lib/commonjs/src/core/svg/svg_pathops.d.ts +21 -0
  43. package/lib/commonjs/src/core/svg/svg_pathops.js +611 -0
  44. package/lib/commonjs/src/core/types.d.ts +120 -0
  45. package/lib/commonjs/src/core/types.js +2 -0
  46. package/lib/commonjs/src/utils/fingerPrint.d.ts +1 -0
  47. package/lib/commonjs/src/utils/fingerPrint.js +20 -0
  48. package/lib/commonjs/src/utils/parse.d.ts +2 -0
  49. package/lib/commonjs/src/utils/parse.js +64 -0
  50. package/lib/module/const/colors.js +153 -0
  51. package/lib/module/const/colors.js.map +1 -0
  52. package/lib/module/core/font/compile.js +70 -0
  53. package/lib/module/core/font/compile.js.map +1 -0
  54. package/lib/module/core/font/metrics.js +37 -0
  55. package/lib/module/core/font/metrics.js.map +1 -0
  56. package/lib/module/core/pipeline/config.js +20 -0
  57. package/lib/module/core/pipeline/config.js.map +1 -0
  58. package/lib/module/core/pipeline/index.js +5 -0
  59. package/lib/module/core/pipeline/index.js.map +1 -0
  60. package/lib/module/core/pipeline/managers.js +80 -0
  61. package/lib/module/core/pipeline/managers.js.map +1 -0
  62. package/lib/module/core/pipeline/run.js +114 -0
  63. package/lib/module/core/pipeline/run.js.map +1 -0
  64. package/lib/module/core/shims/pathops.py +181 -0
  65. package/lib/module/core/svg/layers.js +51 -0
  66. package/lib/module/core/svg/layers.js.map +1 -0
  67. package/lib/module/core/svg/svg_dom.js +83 -0
  68. package/lib/module/core/svg/svg_dom.js.map +1 -0
  69. package/lib/module/core/svg/svg_pathops.js +566 -0
  70. package/lib/module/core/svg/svg_pathops.js.map +1 -0
  71. package/lib/module/core/tsconfig.json +32 -0
  72. package/lib/module/core/types.js +2 -0
  73. package/lib/module/core/types.js.map +1 -0
  74. package/lib/module/createNanoIconsSet.js +84 -0
  75. package/lib/module/createNanoIconsSet.js.map +1 -0
  76. package/lib/module/index.js +5 -0
  77. package/lib/module/index.js.map +1 -0
  78. package/lib/module/index.node.js +13 -0
  79. package/lib/module/index.node.js.map +1 -0
  80. package/lib/module/package.json +1 -0
  81. package/lib/module/utils/fingerPrint.js +17 -0
  82. package/lib/module/utils/fingerPrint.js.map +1 -0
  83. package/lib/module/utils/parse.js +53 -0
  84. package/lib/module/utils/parse.js.map +1 -0
  85. package/lib/typescript/__tests__/build.unit.test.d.ts +3 -0
  86. package/lib/typescript/__tests__/build.unit.test.d.ts.map +1 -0
  87. package/lib/typescript/__tests__/clippath.e2e.test.d.ts +3 -0
  88. package/lib/typescript/__tests__/clippath.e2e.test.d.ts.map +1 -0
  89. package/lib/typescript/__tests__/fingerprint.unit.test.d.ts +3 -0
  90. package/lib/typescript/__tests__/fingerprint.unit.test.d.ts.map +1 -0
  91. package/lib/typescript/__tests__/pipeline.e2e.test.d.ts +3 -0
  92. package/lib/typescript/__tests__/pipeline.e2e.test.d.ts.map +1 -0
  93. package/lib/typescript/__tests__/placement.unit.test.d.ts +3 -0
  94. package/lib/typescript/__tests__/placement.unit.test.d.ts.map +1 -0
  95. package/lib/typescript/__tests__/svg_dom.unit.test.d.ts +3 -0
  96. package/lib/typescript/__tests__/svg_dom.unit.test.d.ts.map +1 -0
  97. package/lib/typescript/cli/build.d.ts +29 -0
  98. package/lib/typescript/cli/build.d.ts.map +1 -0
  99. package/lib/typescript/cli/logger.d.ts +28 -0
  100. package/lib/typescript/cli/logger.d.ts.map +1 -0
  101. package/lib/typescript/package.json +1 -0
  102. package/lib/typescript/src/const/colors.d.ts +2 -0
  103. package/lib/typescript/src/const/colors.d.ts.map +1 -0
  104. package/lib/typescript/src/core/font/compile.d.ts +10 -0
  105. package/lib/typescript/src/core/font/compile.d.ts.map +1 -0
  106. package/lib/typescript/src/core/font/metrics.d.ts +2 -0
  107. package/lib/typescript/src/core/font/metrics.d.ts.map +1 -0
  108. package/lib/typescript/src/core/pipeline/config.d.ts +15 -0
  109. package/lib/typescript/src/core/pipeline/config.d.ts.map +1 -0
  110. package/lib/typescript/src/core/pipeline/index.d.ts +4 -0
  111. package/lib/typescript/src/core/pipeline/index.d.ts.map +1 -0
  112. package/lib/typescript/src/core/pipeline/managers.d.ts +12 -0
  113. package/lib/typescript/src/core/pipeline/managers.d.ts.map +1 -0
  114. package/lib/typescript/src/core/pipeline/run.d.ts +15 -0
  115. package/lib/typescript/src/core/pipeline/run.d.ts.map +1 -0
  116. package/lib/typescript/src/core/svg/layers.d.ts +25 -0
  117. package/lib/typescript/src/core/svg/layers.d.ts.map +1 -0
  118. package/lib/typescript/src/core/svg/svg_dom.d.ts +23 -0
  119. package/lib/typescript/src/core/svg/svg_dom.d.ts.map +1 -0
  120. package/lib/typescript/src/core/svg/svg_pathops.d.ts +22 -0
  121. package/lib/typescript/src/core/svg/svg_pathops.d.ts.map +1 -0
  122. package/lib/typescript/src/core/types.d.ts +121 -0
  123. package/lib/typescript/src/core/types.d.ts.map +1 -0
  124. package/lib/typescript/src/createNanoIconsSet.d.ts +19 -0
  125. package/lib/typescript/src/createNanoIconsSet.d.ts.map +1 -0
  126. package/lib/typescript/src/index.d.ts +3 -0
  127. package/lib/typescript/src/index.d.ts.map +1 -0
  128. package/lib/typescript/src/index.node.d.ts +2 -0
  129. package/lib/typescript/src/index.node.d.ts.map +1 -0
  130. package/lib/typescript/src/utils/fingerPrint.d.ts +2 -0
  131. package/lib/typescript/src/utils/fingerPrint.d.ts.map +1 -0
  132. package/lib/typescript/src/utils/parse.d.ts +3 -0
  133. package/lib/typescript/src/utils/parse.d.ts.map +1 -0
  134. package/package.json +160 -1
  135. package/plugin/src/buildFonts.ts +29 -0
  136. package/plugin/src/index.ts +68 -0
  137. package/plugin/src/types.ts +33 -0
  138. package/plugin/src/withNanoIconsFontLinking.ts +119 -0
  139. package/plugin/tsconfig.json +9 -0
  140. package/scripts/cli.ts +34 -0
  141. package/scripts/tsconfig.json +25 -0
  142. package/src/const/colors.ts +150 -0
  143. package/src/core/font/compile.ts +96 -0
  144. package/src/core/font/metrics.ts +44 -0
  145. package/src/core/pipeline/config.ts +24 -0
  146. package/src/core/pipeline/index.ts +3 -0
  147. package/src/core/pipeline/managers.ts +114 -0
  148. package/src/core/pipeline/run.ts +151 -0
  149. package/src/core/shims/pathops.py +181 -0
  150. package/src/core/svg/layers.ts +66 -0
  151. package/src/core/svg/svg_dom.ts +99 -0
  152. package/src/core/svg/svg_pathops.ts +796 -0
  153. package/src/core/tsconfig.json +32 -0
  154. package/src/core/types.ts +138 -0
  155. package/src/createNanoIconsSet.tsx +131 -0
  156. package/src/index.node.ts +14 -0
  157. package/src/index.ts +7 -0
  158. package/src/utils/fingerPrint.ts +20 -0
  159. package/src/utils/parse.ts +67 -0
@@ -0,0 +1,150 @@
1
+ export const CSS_NAMED_COLORS: Record<string, [number, number, number]> = {
2
+ aliceblue: [240, 248, 255],
3
+ antiquewhite: [250, 235, 215],
4
+ aqua: [0, 255, 255],
5
+ aquamarine: [127, 255, 212],
6
+ azure: [240, 255, 255],
7
+ beige: [245, 245, 220],
8
+ bisque: [255, 228, 196],
9
+ black: [0, 0, 0],
10
+ blanchedalmond: [255, 235, 205],
11
+ blue: [0, 0, 255],
12
+ blueviolet: [138, 43, 226],
13
+ brown: [165, 42, 42],
14
+ burlywood: [222, 184, 135],
15
+ cadetblue: [95, 158, 160],
16
+ chartreuse: [127, 255, 0],
17
+ chocolate: [210, 105, 30],
18
+ coral: [255, 127, 80],
19
+ cornflowerblue: [100, 149, 237],
20
+ cornsilk: [255, 248, 220],
21
+ crimson: [220, 20, 60],
22
+ cyan: [0, 255, 255],
23
+ darkblue: [0, 0, 139],
24
+ darkcyan: [0, 139, 139],
25
+ darkgoldenrod: [184, 134, 11],
26
+ darkgray: [169, 169, 169],
27
+ darkgreen: [0, 100, 0],
28
+ darkgrey: [169, 169, 169],
29
+ darkkhaki: [189, 183, 107],
30
+ darkmagenta: [139, 0, 139],
31
+ darkolivegreen: [85, 107, 47],
32
+ darkorange: [255, 140, 0],
33
+ darkorchid: [153, 50, 204],
34
+ darkred: [139, 0, 0],
35
+ darksalmon: [233, 150, 122],
36
+ darkseagreen: [143, 188, 143],
37
+ darkslateblue: [72, 61, 139],
38
+ darkslategray: [47, 79, 79],
39
+ darkslategrey: [47, 79, 79],
40
+ darkturquoise: [0, 206, 209],
41
+ darkviolet: [148, 0, 211],
42
+ deeppink: [255, 20, 147],
43
+ deepskyblue: [0, 191, 255],
44
+ dimgray: [105, 105, 105],
45
+ dimgrey: [105, 105, 105],
46
+ dodgerblue: [30, 144, 255],
47
+ firebrick: [178, 34, 34],
48
+ floralwhite: [255, 250, 240],
49
+ forestgreen: [34, 139, 34],
50
+ fuchsia: [255, 0, 255],
51
+ gainsboro: [220, 220, 220],
52
+ ghostwhite: [248, 248, 255],
53
+ gold: [255, 215, 0],
54
+ goldenrod: [218, 165, 32],
55
+ gray: [128, 128, 128],
56
+ green: [0, 128, 0],
57
+ greenyellow: [173, 255, 47],
58
+ grey: [128, 128, 128],
59
+ honeydew: [240, 255, 240],
60
+ hotpink: [255, 105, 180],
61
+ indianred: [205, 92, 92],
62
+ indigo: [75, 0, 130],
63
+ ivory: [255, 255, 240],
64
+ khaki: [240, 230, 140],
65
+ lavender: [230, 230, 250],
66
+ lavenderblush: [255, 240, 245],
67
+ lawngreen: [124, 252, 0],
68
+ lemonchiffon: [255, 250, 205],
69
+ lightblue: [173, 216, 230],
70
+ lightcoral: [240, 128, 128],
71
+ lightcyan: [224, 255, 255],
72
+ lightgoldenrodyellow: [250, 250, 210],
73
+ lightgray: [211, 211, 211],
74
+ lightgreen: [144, 238, 144],
75
+ lightgrey: [211, 211, 211],
76
+ lightpink: [255, 182, 193],
77
+ lightsalmon: [255, 160, 122],
78
+ lightseagreen: [32, 178, 170],
79
+ lightskyblue: [135, 206, 250],
80
+ lightslategray: [119, 136, 153],
81
+ lightslategrey: [119, 136, 153],
82
+ lightsteelblue: [176, 196, 222],
83
+ lightyellow: [255, 255, 224],
84
+ lime: [0, 255, 0],
85
+ limegreen: [50, 205, 50],
86
+ linen: [250, 240, 230],
87
+ magenta: [255, 0, 255],
88
+ maroon: [128, 0, 0],
89
+ mediumaquamarine: [102, 205, 170],
90
+ mediumblue: [0, 0, 205],
91
+ mediumorchid: [186, 85, 211],
92
+ mediumpurple: [147, 112, 219],
93
+ mediumseagreen: [60, 179, 113],
94
+ mediumslateblue: [123, 104, 238],
95
+ mediumspringgreen: [0, 250, 154],
96
+ mediumturquoise: [72, 209, 204],
97
+ mediumvioletred: [199, 21, 133],
98
+ midnightblue: [25, 25, 112],
99
+ mintcream: [245, 255, 250],
100
+ mistyrose: [255, 228, 225],
101
+ moccasin: [255, 228, 181],
102
+ navajowhite: [255, 222, 173],
103
+ navy: [0, 0, 128],
104
+ oldlace: [253, 245, 230],
105
+ olive: [128, 128, 0],
106
+ olivedrab: [107, 142, 35],
107
+ orange: [255, 165, 0],
108
+ orangered: [255, 69, 0],
109
+ orchid: [218, 112, 214],
110
+ palegoldenrod: [238, 232, 170],
111
+ palegreen: [152, 251, 152],
112
+ paleturquoise: [175, 238, 238],
113
+ palevioletred: [219, 112, 147],
114
+ papayawhip: [255, 239, 213],
115
+ peachpuff: [255, 218, 185],
116
+ peru: [205, 133, 63],
117
+ pink: [255, 192, 203],
118
+ plum: [221, 160, 221],
119
+ powderblue: [176, 224, 230],
120
+ purple: [128, 0, 128],
121
+ rebeccapurple: [102, 51, 153],
122
+ red: [255, 0, 0],
123
+ rosybrown: [188, 143, 143],
124
+ royalblue: [65, 105, 225],
125
+ saddlebrown: [139, 69, 19],
126
+ salmon: [250, 128, 114],
127
+ sandybrown: [244, 164, 96],
128
+ seagreen: [46, 139, 87],
129
+ seashell: [255, 245, 238],
130
+ sienna: [160, 82, 45],
131
+ silver: [192, 192, 192],
132
+ skyblue: [135, 206, 235],
133
+ slateblue: [106, 90, 205],
134
+ slategray: [112, 128, 144],
135
+ slategrey: [112, 128, 144],
136
+ snow: [255, 250, 250],
137
+ springgreen: [0, 255, 127],
138
+ steelblue: [70, 130, 180],
139
+ tan: [210, 180, 140],
140
+ teal: [0, 128, 128],
141
+ thistle: [216, 191, 216],
142
+ tomato: [255, 99, 71],
143
+ turquoise: [64, 224, 208],
144
+ violet: [238, 130, 238],
145
+ wheat: [245, 222, 179],
146
+ white: [255, 255, 255],
147
+ whitesmoke: [245, 245, 245],
148
+ yellow: [255, 255, 0],
149
+ yellowgreen: [154, 205, 50],
150
+ };
@@ -0,0 +1,96 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { once } from 'node:events';
4
+ import { type Transform } from 'node:stream';
5
+
6
+ import { forceTtfMetrics } from './metrics.js';
7
+ import svg2ttf from 'svg2ttf';
8
+ import { parseCodepointFromFilename } from '../../utils/parse.js';
9
+
10
+ async function writeGlyphStreamToFont(
11
+ fontStream: Transform,
12
+ svgPath: string,
13
+ filename: string
14
+ ): Promise<void> {
15
+ const codepoint = parseCodepointFromFilename(filename);
16
+ const name = path.basename(filename, '.svg');
17
+
18
+ return new Promise((resolve, reject) => {
19
+ const glyphStream = fs.createReadStream(svgPath);
20
+
21
+ (glyphStream as any).metadata = {
22
+ name,
23
+ unicode: [String.fromCodePoint(codepoint)],
24
+ };
25
+
26
+ glyphStream.on('error', reject);
27
+ // Do not add fontStream.on("error", reject) here — one per glyph would exceed
28
+ // Node's default MaxListeners (10). Font stream errors are handled once below.
29
+ fontStream.write(glyphStream);
30
+ glyphStream.on('end', resolve);
31
+ });
32
+ }
33
+
34
+ export async function compileTtfFromGlyphSVGs(opts: {
35
+ glyphDir: string;
36
+ outTtfPath: string;
37
+ fontName: string;
38
+ upm: number;
39
+ ascent: number;
40
+ descent: number;
41
+ lineGap?: number;
42
+ }): Promise<void> {
43
+ const { SVGIcons2SVGFontStream } = await import('svgicons2svgfont');
44
+
45
+ const { glyphDir, outTtfPath, fontName, upm, ascent, descent } = opts;
46
+ const lineGap = opts.lineGap ?? 0;
47
+
48
+ const files = fs
49
+ .readdirSync(glyphDir)
50
+ .filter((f) => /^u[0-9a-fA-F]+\.svg$/.test(f))
51
+ .sort(
52
+ (a, b) => parseCodepointFromFilename(a) - parseCodepointFromFilename(b)
53
+ );
54
+
55
+ if (files.length === 0)
56
+ throw new Error(`No glyph SVGs found in: ${glyphDir}`);
57
+
58
+ const fontStream = new SVGIcons2SVGFontStream({
59
+ fontName,
60
+ fontHeight: upm,
61
+ normalize: false,
62
+ ascent,
63
+ descent,
64
+ });
65
+
66
+ const svgFontChunks: Buffer[] = [];
67
+ fontStream.on('data', (c: Buffer | string) =>
68
+ svgFontChunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c))
69
+ );
70
+
71
+ // Single error listener for the font stream; per-glyph errors are handled in writeGlyphStreamToFont.
72
+ let fontStreamReject: (err: Error) => void;
73
+ const fontStreamErrorPromise = new Promise<never>((_, rej) => {
74
+ fontStreamReject = rej;
75
+ });
76
+ fontStream.on('error', (err: Error) => fontStreamReject(err));
77
+
78
+ for (const f of files) {
79
+ await Promise.race([
80
+ writeGlyphStreamToFont(fontStream, path.join(glyphDir, f), f),
81
+ fontStreamErrorPromise,
82
+ ]);
83
+ }
84
+
85
+ fontStream.end();
86
+ await once(fontStream, 'end');
87
+
88
+ const svgFontString = Buffer.concat(svgFontChunks).toString('utf8');
89
+ const ttfRaw = svg2ttf(svgFontString);
90
+ const rawBuf = Buffer.from(ttfRaw.buffer);
91
+
92
+ const fixedBuf = forceTtfMetrics(rawBuf, upm, ascent, descent, lineGap);
93
+
94
+ fs.mkdirSync(path.dirname(outTtfPath), { recursive: true });
95
+ fs.writeFileSync(outTtfPath, fixedBuf);
96
+ }
@@ -0,0 +1,44 @@
1
+ import { Font } from 'fonteditor-core';
2
+
3
+ export function forceTtfMetrics(
4
+ ttfBuffer: Buffer,
5
+ upm: number,
6
+ ascent: number,
7
+ descent: number,
8
+ lineGap: number
9
+ ): Buffer {
10
+ const font = Font.create(ttfBuffer, {
11
+ type: 'ttf',
12
+ hinting: false,
13
+ compound2simple: false,
14
+ combinePath: false,
15
+ });
16
+
17
+ const data = font.get();
18
+
19
+ data.head.unitsPerEm = upm;
20
+
21
+ data.hhea.ascent = ascent;
22
+ data.hhea.descent = -Math.abs(descent);
23
+ data.hhea.lineGap = lineGap;
24
+
25
+ data['OS/2'].usWinAscent = ascent;
26
+ data['OS/2'].usWinDescent = Math.abs(descent);
27
+ data['OS/2'].sTypoAscender = ascent;
28
+ data['OS/2'].sTypoDescender = -Math.abs(descent);
29
+ data['OS/2'].sTypoLineGap = lineGap;
30
+
31
+ // Set USE_TYPO_METRICS flag (bit 7) in fsSelection.
32
+ // This tells renderers to use sTypoAscender/sTypoDescender/sTypoLineGap for
33
+ // line height calculations instead of the Win metrics (usWinAscent/usWinDescent).
34
+ // Without this flag, Windows and some Android renderers ignore the Typo values
35
+ // set above and fall back to Win metrics, causing icons to appear clipped or
36
+ // misaligned vertically. The bitwise OR preserves all other existing style flags
37
+ // (italic, bold, etc.) while ensuring this bit is always on.
38
+ data['OS/2'].fsSelection = (data['OS/2'].fsSelection || 0) | (1 << 7);
39
+
40
+ font.set(data);
41
+
42
+ const out = font.write({ type: 'ttf', hinting: false }) as ArrayBuffer;
43
+ return Buffer.from(out);
44
+ }
@@ -0,0 +1,24 @@
1
+ import fs from 'node:fs';
2
+
3
+ export type PipelineConfig = {
4
+ fontFamily: string;
5
+ upm: number;
6
+ safeZone: number;
7
+ startUnicode: number;
8
+ };
9
+
10
+ export type PipelinePaths = {
11
+ inputDir: string;
12
+ outputDir: string;
13
+ tempDir: string;
14
+ };
15
+
16
+ export function ensureEmptyDir(dir: string): void {
17
+ if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
18
+ fs.mkdirSync(dir, { recursive: true });
19
+ }
20
+
21
+ /** Create directory if it does not exist; do not remove existing contents (for shared output dirs). */
22
+ export function ensureDir(dir: string): void {
23
+ fs.mkdirSync(dir, { recursive: true });
24
+ }
@@ -0,0 +1,3 @@
1
+ export { runPipeline, type PipelineResult } from './run.js';
2
+ export type { PipelineConfig, PipelinePaths } from './config.js';
3
+ export { ensureEmptyDir } from './config.js';
@@ -0,0 +1,114 @@
1
+ import { loadPyodide } from 'pyodide';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs/promises';
4
+ import type { PathKitModule, PyodideModule } from '../types.js';
5
+ import { buildPathopsBackend } from '../svg/svg_pathops.js';
6
+
7
+ /** Package root (where package.json lives). */
8
+ function getPackageRoot(): string {
9
+ // Compiled to lib/commonjs/src/core/pipeline/ — 5 dirs up is package root.
10
+ // Running from source (Jest/ts-node) in src/core/pipeline/ — 3 dirs up is package root.
11
+ return __dirname.includes(`${path.sep}lib${path.sep}`)
12
+ ? path.resolve(__dirname, '../../../../..')
13
+ : path.resolve(__dirname, '../../..');
14
+ }
15
+
16
+ export class PathKitManager {
17
+ private static instance: PathKitModule | null = null;
18
+
19
+ static async getInstance(): Promise<PathKitModule> {
20
+ if (this.instance) return this.instance;
21
+
22
+ const PathKitInit = require('pathkit-wasm/bin/pathkit.js') as (
23
+ opts: unknown
24
+ ) => any;
25
+ const pathkitJsPath = require.resolve(
26
+ 'pathkit-wasm/bin/pathkit.js'
27
+ ) as string;
28
+ const pathkitBinDir = path.dirname(pathkitJsPath);
29
+ const pathkitWasmPath = path.join(pathkitBinDir, 'pathkit.wasm');
30
+
31
+ const wasmBinary = await fs.readFile(pathkitWasmPath);
32
+
33
+ const pkInit = PathKitInit({
34
+ wasmBinary,
35
+ locateFile: (file: string) => path.join(pathkitBinDir, file),
36
+ });
37
+
38
+ const PathKit: PathKitModule = await (typeof pkInit?.ready === 'function'
39
+ ? pkInit.ready()
40
+ : pkInit);
41
+ this.instance = PathKit;
42
+ return PathKit;
43
+ }
44
+ }
45
+
46
+ export class PyodideManager {
47
+ private static instance: PyodideModule | null = null;
48
+
49
+ static async getInstance(): Promise<PyodideModule> {
50
+ if (this.instance) return this.instance;
51
+
52
+ const pyodideAsmPath = require.resolve('pyodide/pyodide.asm.js') as string;
53
+ const pyodideDir = path.dirname(pyodideAsmPath) + path.sep;
54
+
55
+ const py = (await loadPyodide({
56
+ indexURL: pyodideDir,
57
+ })) as unknown as PyodideModule;
58
+ py.mountNodeFS('/app', process.cwd());
59
+
60
+ const PathKit = await PathKitManager.getInstance();
61
+ py.registerJsModule('_pathops_js', buildPathopsBackend(PathKit));
62
+
63
+ const pathopsPyPath = path.join(
64
+ getPackageRoot(),
65
+ 'src',
66
+ 'core',
67
+ 'shims',
68
+ 'pathops.py'
69
+ );
70
+
71
+ const pathopsPy = await fs.readFile(pathopsPyPath, 'utf8');
72
+ py.FS.writeFile('/pathops.py', pathopsPy);
73
+
74
+ await py.loadPackage(['micropip', 'lxml'], { messageCallback: () => {} });
75
+
76
+ await py.runPythonAsync(`
77
+ import sys
78
+ if "/" not in sys.path:
79
+ sys.path.insert(0, "/")
80
+
81
+ import micropip
82
+ await micropip.install("picosvg", deps=False)
83
+
84
+ import pathops
85
+ import picosvg
86
+ `);
87
+
88
+ this.instance = py;
89
+ return py;
90
+ }
91
+
92
+ static async picoFromFile(
93
+ hostFilePath: string,
94
+ content?: string
95
+ ): Promise<string> {
96
+ const py = await this.getInstance();
97
+ const svgContent = content ?? (await fs.readFile(hostFilePath, 'utf-8'));
98
+ py.globals.set('_svg_content', svgContent);
99
+ const out = py.runPython(`
100
+ from picosvg.svg import SVG
101
+ svg = SVG.fromstring(_svg_content)
102
+ pico = svg.topicosvg()
103
+ pico.tostring(pretty_print=True)
104
+ `);
105
+ return out;
106
+ }
107
+ }
108
+
109
+ export async function picoFromFile(
110
+ hostFilePath: string,
111
+ content?: string
112
+ ): Promise<string> {
113
+ return PyodideManager.picoFromFile(hostFilePath, content);
114
+ }
@@ -0,0 +1,151 @@
1
+ import fs from 'node:fs';
2
+ import fsp from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ import { compileTtfFromGlyphSVGs } from '../font/compile.js';
6
+ import { picoFromFile } from './managers.js';
7
+
8
+ import {
9
+ ensureDir,
10
+ ensureEmptyDir,
11
+ type PipelineConfig,
12
+ type PipelinePaths,
13
+ } from './config.js';
14
+ import {
15
+ parseFlattenedSvg,
16
+ preprocessSvg,
17
+ shouldSkipPath,
18
+ validateSvg,
19
+ } from '../svg/svg_dom.js';
20
+ import { computePlacement, writeLayerSvg } from '../svg/layers.js';
21
+ import type { GlyphEntry, NanoGlyphMap } from '../types.js';
22
+ import type { NanoLogger } from '../types.js';
23
+
24
+ export type PipelineResult = {
25
+ ttfPath: string;
26
+ glyphmapPath: string;
27
+ };
28
+
29
+ /**
30
+ * Run the font pipeline with given config and paths.
31
+ * Uses the singleton Pyodide/PathKit instance (initialized on first call).
32
+ */
33
+ export async function runPipeline(
34
+ config: PipelineConfig,
35
+ paths: PipelinePaths,
36
+ options?: { logger?: NanoLogger; inputHash?: string }
37
+ ): Promise<PipelineResult> {
38
+ const startTime = Date.now();
39
+ const logger = options?.logger;
40
+
41
+ logger?.update(`Building "${config.fontFamily}"…`);
42
+
43
+ ensureEmptyDir(paths.tempDir);
44
+ ensureDir(paths.outputDir);
45
+
46
+ const files = (await fsp.readdir(paths.inputDir)).filter((f) =>
47
+ f.toLowerCase().endsWith('.svg')
48
+ );
49
+
50
+ const glyphMap: NanoGlyphMap = {
51
+ meta: {
52
+ fontFamily: config.fontFamily,
53
+ upm: config.upm,
54
+ safeZone: config.safeZone,
55
+ startUnicode: config.startUnicode,
56
+ },
57
+ icons: {},
58
+ };
59
+
60
+ let currentUnicode = config.startUnicode;
61
+
62
+ for (const file of files) {
63
+ const iconName = path.parse(file).name;
64
+ const filePath = path.join(paths.inputDir, file);
65
+
66
+ logger?.info(`Processing ${file}`);
67
+
68
+ const rawContent = await fsp.readFile(filePath, 'utf-8');
69
+
70
+ const validation = validateSvg(rawContent);
71
+ if (validation.valid === false) {
72
+ logger?.warn(
73
+ `Skipping "${config.fontFamily}:${file}": ${validation.reason}`
74
+ );
75
+ continue;
76
+ }
77
+
78
+ const preprocessed = preprocessSvg(rawContent);
79
+ const flattenedSvg = await picoFromFile(filePath, preprocessed);
80
+ const parsed = parseFlattenedSvg(flattenedSvg);
81
+
82
+ const { vx, vy, scale, xOff, yOff, adv } = computePlacement({
83
+ upm: config.upm,
84
+ safeZone: config.safeZone,
85
+ viewBox: parsed.viewBox,
86
+ });
87
+
88
+ const entry: GlyphEntry = { adv, layers: [] };
89
+
90
+ for (const p of parsed.paths) {
91
+ if (shouldSkipPath(p.d, p.fill)) continue;
92
+
93
+ const cp = currentUnicode++;
94
+
95
+ await writeLayerSvg({
96
+ tempDir: paths.tempDir,
97
+ upm: config.upm,
98
+ adv,
99
+ vx,
100
+ vy,
101
+ scale,
102
+ xOff,
103
+ yOff,
104
+ d: p.d,
105
+ codepoint: cp,
106
+ });
107
+
108
+ entry.layers.push({ codepoint: cp, color: p.fill || 'black' });
109
+ }
110
+
111
+ if (entry.layers.length > 0) {
112
+ glyphMap.icons[iconName] = entry;
113
+ }
114
+ }
115
+
116
+ const glyphmapPath = path.join(
117
+ paths.outputDir,
118
+ `${config.fontFamily}.glyphmap.json`
119
+ );
120
+
121
+ if (options?.inputHash) {
122
+ glyphMap.meta.hash = options.inputHash;
123
+ }
124
+ await fsp.writeFile(glyphmapPath, JSON.stringify(glyphMap, null, 2), 'utf8');
125
+
126
+ logger?.info(`Compiling TTF…`);
127
+ const ttfPath = path.join(paths.outputDir, `${config.fontFamily}.ttf`);
128
+
129
+ await compileTtfFromGlyphSVGs({
130
+ glyphDir: paths.tempDir,
131
+ outTtfPath: ttfPath,
132
+ fontName: config.fontFamily,
133
+ upm: config.upm,
134
+ ascent: config.upm,
135
+ descent: 0,
136
+ });
137
+
138
+ if (fs.existsSync(paths.tempDir)) {
139
+ fs.rmSync(paths.tempDir, { recursive: true, force: true });
140
+ }
141
+
142
+ const iconCount = Object.keys(glyphMap.icons).length;
143
+ const elapsed = Date.now() - startTime;
144
+ logger?.succeed(
145
+ `Built ${config.fontFamily}.ttf [${iconCount} icon${
146
+ iconCount === 1 ? '' : 's'
147
+ } in ${elapsed}ms]`
148
+ );
149
+
150
+ return { ttfPath, glyphmapPath };
151
+ }