sf-symbols-svg 6.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Maxime Thirouin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # SF Symbols to SVG
2
+
3
+ <a href="https://github.com/MoOx/sf-symbols-svg?sponsor=1">
4
+ <img width="140" align="right" alt="Sponsoring button" src="https://github.com/moox/.github/raw/main/FUNDING.svg">
5
+ </a>
6
+
7
+ [![GitHub package.json version](https://img.shields.io/github/package-json/v/MoOx/sf-symbols-svg) ![npm downloads](https://img.shields.io/npm/dm/sf-symbols-svg)](https://www.npmjs.com/package/sf-symbols-svg)
8
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/MoOx/sf-symbols-svg/build.yml?branch=main)](https://github.com/MoOx/sf-symbols-svg/actions)
9
+ [![License](https://img.shields.io/github/license/MoOx/sf-symbols-svg)](https://github.com/MoOx/sf-symbols-svg)
10
+ ![My website moox.io](https://img.shields.io/badge/%F0%9F%8C%8D%20-https%3A%2F%2Fmoox.io-gray?style=social)
11
+ [![GitHub followers](https://img.shields.io/github/followers/MoOx?style=social&label=GitHub)](https://github.com/MoOx)
12
+ [![LinkedIn Follow](https://img.shields.io/badge/LinkedIn-%20?style=social&logo=invision&logoColor=%230077B5)](https://www.linkedin.com/in/maxthirouin/)
13
+ [![BlueSky Follow](https://img.shields.io/badge/BlueSky-%20?style=social&logo=bluesky)](https://bsky.app/profile/moox.io)
14
+ [![X Follow](https://img.shields.io/twitter/follow/MoOx?style=social&label=)](https://x.com/MoOx)
15
+
16
+ > SF Symbols to SVGs. Period.
17
+
18
+ [Apple SF Symbols](https://developer.apple.com/sf-symbols/) is an icon set with more thant 6,000 symbols.
19
+ Unfortunately, this are not available on the web.
20
+ This tool solve this.
21
+
22
+ > [!WARNING]
23
+ > This tool requires you to have the [_SF Pro Text_ font](https://developer.apple.com/fonts/) installed on your system or in a custom directory.
24
+
25
+ > [!TIP]
26
+ > SF Symbols to SVG can be coupled with [React from SVG](https://github.com/MoOx/react-from-svg) to generate React components from SVGs.
27
+
28
+ ## Usage
29
+
30
+ SF Symbols to SVG is available as a CLI tool:
31
+
32
+ ```console
33
+ npx sf-symbols-svg --help
34
+ ```
35
+
36
+ #### Available Options
37
+
38
+ ```console
39
+ --size, -s Font size for symbols (default: 24)
40
+ --padding, -p Padding in pixels (default: 2)
41
+ --weight, -w Font weights to include (default: regular)
42
+ Can specify multiple: -w regular -w bold
43
+ --output, -o Output directory (default: ./sf-symbols-svgs)
44
+ --fonts-dir, -f Directory containing SF Pro Text fonts (default: /Library/Fonts)
45
+ --sf-version SF Symbols version to use (default: latest)
46
+ --sources-dir Directory containing SF Symbols data files (default: ./sources)
47
+ --icons-list Path to a file containing a list of icons to process, one name per line (optional)
48
+ --help, -h Show this help message
49
+ --version Show version of SF Symbols supported by this tool.
50
+ ```
51
+
52
+ > [!NOTE]
53
+ > SF Symbols to SVG will always try to use the latest version of SF Symbols supported by this tool.
54
+ > You can check in the `sources/` directory to see which versions are supported.
55
+
56
+ #### Examples
57
+
58
+ ```console
59
+ # Generate SVGs for the latest version of SF Symbols, in 24x24 SVGs with 2px padding, in ./svg-symbols-svgs folder
60
+ npx sf-symbols-svg --weight all
61
+
62
+ # Generate SVGs with larger size and padding
63
+ sf-symbols-svg --size 32 --padding 4
64
+
65
+ # Generate SVGs for multiple weights
66
+ sf-symbols-svg --weight bold --weight black
67
+
68
+ # Specify custom output directory
69
+ sf-symbols-svg --output ./my-icons
70
+
71
+ # Specify a different SF Symbols version (if available)
72
+ sf-symbols-svg --sf-version 6.0
73
+
74
+ # Combine options
75
+ sf-symbols-svg --size 48 --padding 8 --weight light --weight regular --weight bold --output ./custom-icons --fonts-dir /Users/moox/Library/Fonts
76
+
77
+ # Process only specific icons
78
+ sf-symbols-svg --icons-list /path/to/your/icons-list.txt
79
+ ```
80
+
81
+ ## Creating a new version
82
+
83
+ ## About SF Symbols Versions and Font Compatibility
84
+
85
+ ### SF Symbols Versions
86
+
87
+ This tool automatically detects supported SF Symbols versions by scanning the `sources/` directory. Each version requires its own data files (`symbols.txt` and `names.txt`) which are already included in the repository for some versions.
88
+
89
+ The tool will automatically use the most recent version as the default, but you can specify a different version using the `--sf-version` option. If no matching versions are detected in the `sources/` directory, the tool will display an error message.
90
+
91
+ If you want to use a different version (when new SF Symbols versions are released):
92
+
93
+ 1. Create a new directory in `sources/{version}/` (example: `sources/7.0/`)
94
+ 2. Extract the character mappings:
95
+ - Get the [SF Symbols app](https://developer.apple.com/sf-symbols/) and open the app
96
+ - Select all symbols (`cmd + A` or `Edit` > `Select All`)
97
+ - Right click on the selection, and press `Copy all {x} symbols`
98
+ - Paste the symbols into a file at `sources/{version}/symbols.txt`
99
+ - Right click again on the selection, and press `Copy all {x} names`
100
+ - Paste the names into a file at `sources/{version}/names.txt`
101
+
102
+ That's it! The tool will automatically detect the new version and use it as the default (since it's the most recent).
103
+
104
+ ### Using a Custom Sources Directory
105
+
106
+ If you want to use a different directory for your SF Symbols data files, you can specify it with the `--sources-dir` option:
107
+
108
+ ```sh
109
+ sf-symbols-svg --sources-dir /path/to/your/sources
110
+ ```
111
+
112
+ The custom sources directory must follow the same structure as the default one:
113
+
114
+ ```
115
+ sources/
116
+ ├── 6.0/
117
+ │ ├── symbols.txt
118
+ │ └── names.txt
119
+ ├── 6.1/
120
+ │ ├── symbols.txt
121
+ │ └── names.txt
122
+ └── ...
123
+ ```
124
+
125
+ The tool will automatically detect available versions from the provided directory and use the most recent one as the default.
126
+
127
+ ## Processing Only Specific Icons
128
+
129
+ If you want to process only a limited subset of icons, you can create a text file with one icon name per line and use the `--icons-list` option:
130
+
131
+ ```sh
132
+ sf-symbols-svg --icons-list /path/to/your/icons-list.txt
133
+ ```
134
+
135
+ Example of an icons list file:
136
+
137
+ ```
138
+ moon.stars.fill
139
+ puzzlepiece
140
+ amplifier
141
+ figure.hiking
142
+ ```
143
+
144
+ This is particularly useful for:
145
+
146
+ - Testing the tool with a smaller set of icons
147
+ - Generating only the specific icons you need for your project
148
+ - Reducing processing time when you only need a few symbols
149
+
150
+ ### Font Compatibility
151
+
152
+ > [!WARNING] SF Symbols requires specific _SF Pro Text_ font versions that match the SF Symbols version you're using. If the font versions don't match, the symbols may not render correctly.
153
+
154
+ > [!CAUTION]
155
+ > The Apple SF Symbols app will display a warning at the top of the application if your installed fonts don't match the expected version. Make sure to check this warning and install the appropriate font version from [Apple's website](https://developer.apple.com/fonts/).
156
+
157
+ ### About Font Files
158
+
159
+ To use this script, you need to have the SF Pro Text fonts installed on your system or in a custom directory.
160
+
161
+ #### Using system-installed fonts (recommended)
162
+
163
+ If you have SF Pro Text fonts installed on your system (typically in `/Library/Fonts`), the script will automatically find and use them. This is the default behavior.
164
+
165
+ ````console
166
+ # Use fonts from the default location (/Library/Fonts)
167
+ sf-symbols-svg`
168
+
169
+ #### Using fonts from a custom directory
170
+
171
+ If your fonts are installed in a different location, you can specify it with the `--fonts-dir` option:
172
+
173
+ ```console
174
+ # Use fonts from a custom location
175
+ sf-symbols-svg --fonts-dir ~/Library/Fonts
176
+ ````
177
+
178
+ #### Installing SF Pro Text fonts
179
+
180
+ If you don't have the fonts installed:
181
+
182
+ 1. Download SF Pro font from [Apple's website](https://developer.apple.com/fonts/).
183
+ 2. Install the font using the provided installer.
184
+ 3. The fonts will be installed in `/Library/Fonts` by default.
185
+
186
+ ---
187
+
188
+ > [!NOTE]
189
+ > For legal reasons, this repository does not include the SF Pro Text font files. You must download and install them from Apple's website. Make sure to use font versions that are compatible with the SF Symbols version you are using (check for warnings in the SF Symbols app).
package/dist/index.js ADDED
@@ -0,0 +1,449 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import fs from "node:fs";
5
+ import opentype from "opentype.js";
6
+ import meow from "meow";
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const PATH_PRECISION = 2;
9
+ /**
10
+ * Default sources directory path
11
+ */
12
+ const DEFAULT_SOURCES_DIR = path.join(__dirname, "../sources");
13
+ /**
14
+ * Detects available versions based on folders in the sources directory
15
+ * @param {string} sourcesDir - Path to the sources directory
16
+ * @throws {Error} If no version is detected
17
+ */
18
+ function detectAvailableVersions(sourcesDir) {
19
+ try {
20
+ const items = fs.readdirSync(sourcesDir, { withFileTypes: true });
21
+ const versions = items
22
+ .filter((item) => item.isDirectory() && !item.name.startsWith("."))
23
+ .map((item) => item.name)
24
+ // Sort versions in descending order (to have the most recent first)
25
+ .sort((a, b) => {
26
+ // Semantic version comparison (e.g., "6.1" > "6.0")
27
+ const partsA = a.split(".").map(Number);
28
+ const partsB = b.split(".").map(Number);
29
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
30
+ const partA = partsA[i] || 0;
31
+ const partB = partsB[i] || 0;
32
+ if (partA !== partB) {
33
+ return partB - partA; // Descending order
34
+ }
35
+ }
36
+ return 0;
37
+ });
38
+ if (versions.length === 0) {
39
+ throw new Error(`No SF Symbols version detected in the sources directory. Please add at least one version folder containing symbols.txt and names.txt files.`);
40
+ }
41
+ return versions;
42
+ }
43
+ catch (error) {
44
+ if (error instanceof Error) {
45
+ throw error; // Propagate the error if it's already an Error instance
46
+ }
47
+ else {
48
+ throw new Error(`Error detecting versions: ${error}`);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * Get available versions from the default sources directory
54
+ * This is used only for CLI default values
55
+ */
56
+ function getDefaultVersions() {
57
+ try {
58
+ return detectAvailableVersions(DEFAULT_SOURCES_DIR);
59
+ }
60
+ catch (error) {
61
+ // Return empty array if no versions are detected in the default directory
62
+ return [];
63
+ }
64
+ }
65
+ /**
66
+ * Available SF Symbols versions from default sources (automatically detected)
67
+ */
68
+ const AVAILABLE_VERSIONS = getDefaultVersions();
69
+ /**
70
+ * Default version (the most recent one, or empty string if no versions are available)
71
+ */
72
+ const DEFAULT_VERSION = AVAILABLE_VERSIONS.length > 0 ? AVAILABLE_VERSIONS[0] : "";
73
+ /**
74
+ * Builds paths to data files for a specific version
75
+ * @param {string} version - SF Symbols version
76
+ * @param {string} sourcesDir - Path to the sources directory
77
+ */
78
+ function getVersionPaths(version, sourcesDir) {
79
+ return {
80
+ symbols: path.join(sourcesDir, `${version}/symbols.txt`),
81
+ names: path.join(sourcesDir, `${version}/names.txt`),
82
+ };
83
+ }
84
+ /**
85
+ * Builds file paths based on the output directory, version, and sources directory
86
+ */
87
+ function buildPaths(outputDir, version, sourcesDir) {
88
+ const versionPaths = getVersionPaths(version, sourcesDir);
89
+ return {
90
+ SYMBOLS: versionPaths.symbols,
91
+ NAMES: versionPaths.names,
92
+ SVG_OUTPUT: outputDir,
93
+ };
94
+ }
95
+ /**
96
+ * Checks if data files exist for a specific version
97
+ */
98
+ function checkDataFilesExist(version, sourcesDir) {
99
+ const paths = getVersionPaths(version, sourcesDir);
100
+ if (!fileExists(paths.symbols)) {
101
+ console.error(`symbols.txt file not found: ${paths.symbols}`);
102
+ return false;
103
+ }
104
+ if (!fileExists(paths.names)) {
105
+ console.error(`names.txt file not found: ${paths.names}`);
106
+ return false;
107
+ }
108
+ return true;
109
+ }
110
+ /**
111
+ * Writes a file to the filesystem, creating directories if needed
112
+ */
113
+ function writeFile(filePath, contents) {
114
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
115
+ fs.writeFileSync(filePath, contents);
116
+ }
117
+ /**
118
+ * Generates an SVG from a font character
119
+ */
120
+ function generateSymbolSvg(font, char, name, options) {
121
+ try {
122
+ const { fontSize, padding } = options;
123
+ const glyphIndex = font.charToGlyphIndex(char);
124
+ if (glyphIndex === 0)
125
+ return null;
126
+ const glyph = font.glyphs.get(glyphIndex);
127
+ if (!glyph)
128
+ return null;
129
+ const path = font.getPath(char, 0, 0, fontSize);
130
+ if (!path)
131
+ return null;
132
+ const bbox = path.getBoundingBox();
133
+ if (!bbox)
134
+ return null;
135
+ const symbolWidth = bbox.x2 - bbox.x1;
136
+ const symbolHeight = bbox.y2 - bbox.y1;
137
+ const availableWidth = fontSize - padding * 2;
138
+ const availableHeight = fontSize - padding * 2;
139
+ // Calculate the scale needed for the symbol to fit in the available area
140
+ // taking into account padding on all sides
141
+ let scale;
142
+ if (symbolWidth / availableWidth > symbolHeight / availableHeight) {
143
+ scale = availableWidth / symbolWidth;
144
+ }
145
+ else {
146
+ scale = availableHeight / symbolHeight;
147
+ }
148
+ const scaledWidth = symbolWidth * scale;
149
+ const scaledHeight = symbolHeight * scale;
150
+ // Calculate translations needed to center the symbol in the viewBox
151
+ const translateX = (fontSize - scaledWidth) / 2 - bbox.x1 * scale;
152
+ const translateY = (fontSize - scaledHeight) / 2 - bbox.y1 * scale;
153
+ const transformMatrix = [scale, 0, 0, scale, translateX, translateY];
154
+ const pathElement = path.toSVG(PATH_PRECISION);
155
+ const dMatch = pathElement.match(/d="([^"]*)"/i);
156
+ if (!dMatch)
157
+ return null;
158
+ const dContent = dMatch[1];
159
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${fontSize} ${fontSize}" width="${fontSize}" height="${fontSize}">
160
+ <title>${name}</title>
161
+ <path transform="matrix(${transformMatrix.join(", ")})" d="${dContent}" fill="currentColor"/>
162
+ </svg>`;
163
+ }
164
+ catch (error) {
165
+ console.error(`Error generating SVG for ${name}:`, error);
166
+ return null;
167
+ }
168
+ }
169
+ /**
170
+ * Creates a safe filename from a symbol name
171
+ */
172
+ function createSafeFilename(name) {
173
+ return name.replace(/[^a-z0-9\.]/gi, "_").toLowerCase();
174
+ }
175
+ /**
176
+ * Creates a filename for an SVG with appropriate weight suffix
177
+ */
178
+ function createSvgFilename(name, weight) {
179
+ const safeName = createSafeFilename(name);
180
+ if (weight === "regular") {
181
+ return `${safeName}.svg`;
182
+ }
183
+ return `${safeName}-${weight}.svg`;
184
+ }
185
+ /**
186
+ * Checks if a file exists
187
+ */
188
+ function fileExists(filePath) {
189
+ try {
190
+ return fs.statSync(filePath).isFile();
191
+ }
192
+ catch {
193
+ return false;
194
+ }
195
+ }
196
+ /**
197
+ * Finds the font file in the fonts directory
198
+ */
199
+ function findFontFile(fontsDir, weight) {
200
+ // Expected format for SF Pro Text
201
+ const fontName = `SF-Pro-Text-${weight.charAt(0).toUpperCase() + weight.slice(1)}.otf`;
202
+ const fontPath = path.join(fontsDir, fontName);
203
+ // Check if the file exists
204
+ if (fileExists(fontPath)) {
205
+ return fontPath;
206
+ }
207
+ return null;
208
+ }
209
+ /**
210
+ * Main function to generate SVG files directly from font files
211
+ */
212
+ async function makeSvgs(options) {
213
+ const { fontSize = 24, padding = 2, weights = ["regular"], outputDir: outputDirOption = path.join(process.cwd(), "sf-symbols-svgs"), fontsDir: fontsDirOption = "/Library/Fonts", version: versionOption = DEFAULT_VERSION, sourcesDir: sourcesDirOption = DEFAULT_SOURCES_DIR, iconsListFile: iconsListFileOption, } = options || {};
214
+ const fontsDir = String(fontsDirOption);
215
+ const outputDir = String(outputDirOption);
216
+ const sourcesDir = String(sourcesDirOption);
217
+ let availableVersions;
218
+ try {
219
+ availableVersions = detectAvailableVersions(sourcesDir);
220
+ }
221
+ catch (error) {
222
+ throw new Error(`No SF Symbols versions detected in ${sourcesDir}. Please provide a valid sources directory containing version folders with symbols.txt and names.txt files.`);
223
+ }
224
+ const version = versionOption ? String(versionOption) : availableVersions[0];
225
+ if (!fontsDir) {
226
+ throw new Error("fonts-dir option is required. Please specify the directory containing SF Pro Text fonts.");
227
+ }
228
+ try {
229
+ const versionStr = String(version);
230
+ const sourcesStr = String(sourcesDir);
231
+ const sourcesVersionDir = path.join(sourcesStr, versionStr);
232
+ const stats = fs.statSync(sourcesVersionDir);
233
+ if (!stats.isDirectory()) {
234
+ throw new Error(`${sourcesVersionDir} is not a directory`);
235
+ }
236
+ }
237
+ catch (error) {
238
+ throw new Error(`SF Symbols version '${version}' not found in ${sourcesDir}. Please make sure the directory exists and contains the required data files.`);
239
+ }
240
+ if (!checkDataFilesExist(String(version), sourcesDir)) {
241
+ throw new Error(`Data files for SF Symbols ${version} are missing in ${sourcesDir}/${version}/. Please check the README for setup instructions.`);
242
+ }
243
+ const PATHS = buildPaths(outputDir, String(version), sourcesDir);
244
+ console.log("Loading Fonts...");
245
+ const fonts = await Promise.all(weights.map(async (weight) => {
246
+ try {
247
+ const fontPath = findFontFile(fontsDir, weight);
248
+ if (!fontPath) {
249
+ console.error(`Font file for weight '${weight}' not found in ${fontsDir}`);
250
+ console.error(`Please make sure SF-Pro-Text-${weight.charAt(0).toUpperCase() + weight.slice(1)}.otf is installed in the specified fonts directory.`);
251
+ return null;
252
+ }
253
+ console.log(`Loading font: ${fontPath}`);
254
+ const font = await opentype.load(fontPath);
255
+ return { weight, font };
256
+ }
257
+ catch (error) {
258
+ console.error(`Error loading font weight ${weight}:`, error);
259
+ return null;
260
+ }
261
+ }));
262
+ const validFonts = fonts.filter((item) => !!item);
263
+ if (validFonts.length === 0) {
264
+ throw new Error("No valid fonts were loaded. Please check the weights and font files.");
265
+ }
266
+ console.log(`Fonts loaded: ${validFonts.map((f) => f.weight).join(", ")}`);
267
+ console.log("Capturing Paths... This takes a while");
268
+ let symbolsData = fs
269
+ .readFileSync(PATHS.SYMBOLS, { encoding: "utf-8" })
270
+ .match(/.{1,2}/g)
271
+ ?.filter((char) => char !== "") || [];
272
+ let names = fs
273
+ .readFileSync(PATHS.NAMES, { encoding: "utf8", flag: "r" })
274
+ .split(/\r?\n/)
275
+ .filter((char) => char !== "");
276
+ if (iconsListFileOption) {
277
+ try {
278
+ const iconsList = fs
279
+ .readFileSync(String(iconsListFileOption), {
280
+ encoding: "utf8",
281
+ flag: "r",
282
+ })
283
+ .split(/\r?\n/)
284
+ .filter((name) => name !== "");
285
+ if (iconsList.length === 0) {
286
+ console.warn(`Icons list file ${iconsListFileOption} is empty. Using all available icons.`);
287
+ }
288
+ else {
289
+ const filteredNamesIndices = [];
290
+ const filteredNames = [];
291
+ const filteredSymbols = [];
292
+ for (const iconName of iconsList) {
293
+ const index = names.indexOf(iconName);
294
+ if (index !== -1) {
295
+ filteredNamesIndices.push(index);
296
+ filteredNames.push(iconName);
297
+ if (index < symbolsData.length) {
298
+ const charItem = symbolsData[index];
299
+ if (charItem) {
300
+ filteredSymbols.push(charItem);
301
+ }
302
+ }
303
+ }
304
+ else {
305
+ console.warn(`Icon '${iconName}' not found in available icons.`);
306
+ }
307
+ }
308
+ if (filteredNames.length === 0) {
309
+ console.warn("None of the specified icons were found. Using all available icons.");
310
+ }
311
+ else {
312
+ console.log(`Processing ${filteredNames.length} icons from list file.`);
313
+ names = filteredNames;
314
+ symbolsData = filteredSymbols;
315
+ }
316
+ }
317
+ }
318
+ catch (error) {
319
+ console.error(`Error reading icons list file: ${error}`);
320
+ console.warn("Using all available icons.");
321
+ }
322
+ }
323
+ console.log(`Output directory: ${outputDir}`);
324
+ fs.mkdirSync(PATHS.SVG_OUTPUT, { recursive: true });
325
+ for (let namesIndex = 0; namesIndex < names.length; namesIndex++) {
326
+ const name = names[namesIndex];
327
+ if (!name)
328
+ continue;
329
+ console.log(`${namesIndex + 1}/${names.length} - ${name}`);
330
+ const char = symbolsData[namesIndex];
331
+ if (!char)
332
+ continue;
333
+ for (const { weight, font } of validFonts) {
334
+ const svgContent = generateSymbolSvg(font, char, name, {
335
+ fontSize,
336
+ padding,
337
+ });
338
+ if (!svgContent)
339
+ continue;
340
+ const svgFilename = createSvgFilename(name, weight);
341
+ const svgPath = path.join(PATHS.SVG_OUTPUT, svgFilename);
342
+ writeFile(svgPath, svgContent);
343
+ }
344
+ }
345
+ console.log("Done! Generated SVG files in", PATHS.SVG_OUTPUT);
346
+ }
347
+ const cli = meow(`
348
+ Usage
349
+ $ sf-symbols-svg [options]
350
+
351
+ Options
352
+ --size, -s Font size for symbols (default: 24)
353
+ --padding, -p Padding in pixels (default: 2)
354
+ --weight, -w Font weights to include (default: regular)
355
+ Can specify multiple: -w regular -w bold
356
+ Special value "all" to include all available weights
357
+ --output, -o Output directory (default: ./sf-symbols-svgs)
358
+ --fonts-dir, -f Directory containing SF Pro Text fonts (default: /Library/Fonts)
359
+ --sf-version SF Symbols version to use (default: latest)
360
+ --sources-dir Directory containing SF Symbols data files (default: ./sources)
361
+ --icons-list Path to a file containing a list of icons to process, one name per line (optional)
362
+ --help, -h Show this help message
363
+ --version Show version
364
+
365
+ Examples
366
+ $ sf-symbols-svg
367
+ $ sf-symbols-svg --size 32 --padding 4
368
+ $ sf-symbols-svg --weight bold --weight black
369
+ $ sf-symbols-svg --weight all
370
+ $ sf-symbols-svg --output ./my-icons
371
+ $ sf-symbols-svg --fonts-dir ~/Library/Fonts
372
+ $ sf-symbols-svg --sf-version 6.0
373
+ `, {
374
+ importMeta: import.meta,
375
+ flags: {
376
+ size: {
377
+ type: "number",
378
+ default: 24,
379
+ shortFlag: "s",
380
+ },
381
+ padding: {
382
+ type: "number",
383
+ default: 2,
384
+ shortFlag: "p",
385
+ },
386
+ weight: {
387
+ type: "string",
388
+ default: ["regular"],
389
+ isMultiple: true,
390
+ shortFlag: "w",
391
+ },
392
+ output: {
393
+ type: "string",
394
+ shortFlag: "o",
395
+ },
396
+ fontsDir: {
397
+ type: "string",
398
+ shortFlag: "f",
399
+ },
400
+ sfVersion: {
401
+ type: "string",
402
+ },
403
+ sourcesDir: {
404
+ type: "string",
405
+ description: "Directory containing SF Symbols data files",
406
+ },
407
+ iconsList: {
408
+ type: "string",
409
+ description: "Path to a file containing a list of icons to process (one per line)",
410
+ },
411
+ help: {
412
+ type: "boolean",
413
+ shortFlag: "h",
414
+ },
415
+ },
416
+ });
417
+ /**
418
+ * Available font weights
419
+ */
420
+ const FONT_WEIGHTS = [
421
+ "thin",
422
+ "ultraLight",
423
+ "light",
424
+ "regular",
425
+ "medium",
426
+ "semibold",
427
+ "bold",
428
+ "heavy",
429
+ "black",
430
+ ];
431
+ // Process weights option - handle "all" special value
432
+ let weights = cli.flags.weight;
433
+ if (weights.length === 1 && weights[0].toLowerCase() === "all") {
434
+ weights = FONT_WEIGHTS;
435
+ console.log(`Using all available weights: ${weights.join(", ")}`);
436
+ }
437
+ makeSvgs({
438
+ fontSize: cli.flags.size,
439
+ padding: cli.flags.padding,
440
+ weights: weights,
441
+ outputDir: cli.flags.output,
442
+ fontsDir: cli.flags.fontsDir,
443
+ version: cli.flags.sfVersion,
444
+ sourcesDir: cli.flags.sourcesDir,
445
+ iconsListFile: cli.flags.iconsList,
446
+ }).catch((error) => {
447
+ console.error("Error generating SVGs:", error);
448
+ process.exit(1);
449
+ });