├── .npmignore ├── examples ├── example#211 │ ├── .gitignore │ ├── .svgtofontrc.js │ ├── package.json │ └── svg │ │ ├── adobe.svg │ │ ├── git.svg │ │ └── stylelint.svg ├── example │ ├── .gitignore │ ├── favicon.png │ ├── svg │ │ ├── demo.svg │ │ ├── adobe.svg │ │ ├── left.svg │ │ ├── git.svg │ │ └── stylelint.svg │ ├── simple.mjs │ └── index.mjs ├── templates │ ├── .gitignore │ ├── favicon.png │ ├── .svgtofontrc.js │ ├── svg │ │ ├── adobe.svg │ │ ├── git.svg │ │ └── stylelint.svg │ ├── simple.mjs │ ├── styles │ │ ├── _{{filename}}.less.template │ │ ├── _{{filename}}.styl.template │ │ ├── _{{filename}}.scss.template │ │ ├── _{{filename}}.module.less.template │ │ └── _{{filename}}.css.template │ └── index.mjs └── example#233 │ ├── simple.mjs │ └── svg │ ├── adobe.svg │ └── git.svg ├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── renovate.json ├── src ├── styles │ ├── _{{filename}}.less │ ├── _{{filename}}.css │ ├── _{{filename}}.module.less │ ├── _{{filename}}.scss │ └── _{{filename}}.styl ├── log.ts ├── cli.ts ├── website │ └── index.njk ├── generate.ts ├── utils.ts └── index.ts ├── tsconfig.json ├── LICENSE ├── .gitignore ├── test └── index.test.js ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /examples/example#211/.gitignore: -------------------------------------------------------------------------------- 1 | font -------------------------------------------------------------------------------- /examples/example/.gitignore: -------------------------------------------------------------------------------- 1 | example -------------------------------------------------------------------------------- /examples/templates/.gitignore: -------------------------------------------------------------------------------- 1 | dist2 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 2 6 | -------------------------------------------------------------------------------- /examples/example/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/svgtofont/HEAD/examples/example/favicon.png -------------------------------------------------------------------------------- /examples/templates/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/svgtofont/HEAD/examples/templates/favicon.png -------------------------------------------------------------------------------- /examples/templates/.svgtofontrc.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | export default { 4 | "dist": path.resolve(process.cwd(), "examples/templates/dist2") 5 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: jaywcjlove 2 | buy_me_a_coffee: jaywcjlove 3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"] 4 | -------------------------------------------------------------------------------- /examples/example#211/.svgtofontrc.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | /** 3 | * @type {import('svgtofont').SvgToFontOptions} 4 | */ 5 | export default { 6 | fontName: "iconfont", 7 | css: { 8 | hasTimestamp: "v1.2.3" 9 | } 10 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchPackagePatterns": ["*"], 9 | "rangeStrategy": "replace" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /examples/example/svg/demo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/example#211/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ex211", 3 | "version": "6.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "svgtofont --sources ./svg --output ./font" 8 | }, 9 | "keywords": [], 10 | "author": "jaywcjlove", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "svgtofont": "^6.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/styles/_{{filename}}.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | {{fontFamily}} 3 | } 4 | 5 | {% if nameAsUnicode %}.{{prefix}}{% else %}[class^="{{prefix}}-"], [class*=" {{prefix}}-"]{% endif %} { 6 | font-family: '{{fontname}}' !important;{{fontSize}} 7 | font-style:normal; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | {% if not nameAsUnicode %} 13 | {{cssString}} 14 | {% endif %} -------------------------------------------------------------------------------- /src/styles/_{{filename}}.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | {{fontFamily}} 3 | } 4 | 5 | {% if nameAsUnicode %}.{{prefix}}{% else %}[class^="{{prefix}}-"], [class*=" {{prefix}}-"]{% endif %} { 6 | font-family: '{{fontname}}' !important;{{fontSize}} 7 | font-style:normal; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | {% if not nameAsUnicode %} 13 | {{cssString}} 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /examples/example#233/simple.mjs: -------------------------------------------------------------------------------- 1 | import svgtofont from '../../lib/index.js'; 2 | import path from 'path'; 3 | 4 | svgtofont({ 5 | src: path.resolve(process.cwd(), "svg"), // svg path 6 | dist: path.resolve(process.cwd(), "dist"), // output path 7 | fontName: 'pst-font', // font name 8 | css: true, // Create CSS files. 9 | }).then(() => { 10 | console.log('done!'); 11 | }).catch((error) => { 12 | console.error('Error:', error); 13 | }); -------------------------------------------------------------------------------- /src/styles/_{{filename}}.module.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | {{fontFamily}} 3 | } 4 | 5 | {% if nameAsUnicode %}.{{prefix}}{% else %}[class^="{{prefix}}-"], [class*=" {{prefix}}-"]{% endif %} { 6 | font-family: '{{fontname}}' !important;{{fontSize}} 7 | font-style:normal; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | {% if not nameAsUnicode %} 13 | :global { 14 | {{cssString}} 15 | } 16 | {% endif %} -------------------------------------------------------------------------------- /examples/example/svg/adobe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/templates/svg/adobe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/example#211/svg/adobe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/example#233/svg/adobe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/example/simple.mjs: -------------------------------------------------------------------------------- 1 | import svgtofont from '../../lib/index.js'; 2 | import path from 'path'; 3 | 4 | const rootPath = path.resolve(process.cwd(), "examples", "example"); 5 | 6 | svgtofont({ 7 | src: path.resolve(rootPath, "svg"), // svg path 8 | dist: path.resolve(rootPath, "example"), // output path 9 | fontName: "svgtofont", // font name 10 | css: true, // Create CSS files. 11 | startNumber: 20000, // unicode start number 12 | emptyDist: true, 13 | }).then(() => { 14 | console.log("done!!!!"); 15 | }); -------------------------------------------------------------------------------- /src/styles/_{{filename}}.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | {{fontFamily}} 3 | } 4 | 5 | {% if nameAsUnicode %}.{{prefix}}{% else %}[class^="{{prefix}}-"], [class*=" {{prefix}}-"]{% endif %} { 6 | font-family: '{{fontname}}' !important;{{fontSize}} 7 | font-style:normal; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | {% if not nameAsUnicode %} 13 | {{ cssString }} 14 | {% for name, value in infoData %} 15 | ${{prefix}}-{{ name }}: '{{ value.encodedCode }}'; 16 | {%- endfor %} 17 | {% endif %} -------------------------------------------------------------------------------- /src/styles/_{{filename}}.styl: -------------------------------------------------------------------------------- 1 | @font-face { 2 | {{fontFamily}} 3 | } 4 | 5 | {% if nameAsUnicode %}.{{prefix}}{% else %}[class^="{{prefix}}-"], [class*=" {{prefix}}-"]{% endif %} { 6 | font-family: '{{fontname}}' !important;{{fontSize}} 7 | font-style:normal; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | {% if not nameAsUnicode %} 13 | {{ cssString }} 14 | {% for name, value in infoData %} 15 | ${{prefix}}-{{ name }} = '{{ value.encodedCode }}' 16 | {%- endfor %} 17 | {% endif %} -------------------------------------------------------------------------------- /examples/templates/simple.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import svgtofont from '../../lib/index.js'; 3 | 4 | const rootPath = path.resolve(process.cwd(), "examples", "templates"); 5 | 6 | svgtofont({ 7 | config: { 8 | cwd: rootPath, 9 | }, 10 | src: path.resolve(rootPath, "svg"), // svg path 11 | dist: path.resolve(rootPath, "dist3"), // output path 12 | fontName: "svgtofont", // font name 13 | css: true, // Create CSS files. 14 | startNumber: 20000, // unicode start number 15 | emptyDist: true, 16 | }).then(() => { 17 | console.log("done!!!!"); 18 | }); -------------------------------------------------------------------------------- /examples/example/svg/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/example/svg/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/example#211/svg/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/example#233/svg/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/templates/svg/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ESNext", 5 | "allowSyntheticDefaultImports": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "esModuleInterop": true, 8 | "declaration": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "sourceMap": true, 14 | "strict": false, 15 | "skipLibCheck": true, 16 | "outDir": "lib", 17 | "baseUrl": "." 18 | }, 19 | "include": [ 20 | "src/**/*" 21 | , "test/index.test.mjs", "test/index.test.js" ], 22 | "exclude": [ 23 | "node_modules", 24 | "**/*.spec.ts", 25 | "**/*.test.ts" 26 | ] 27 | } -------------------------------------------------------------------------------- /src/log.ts: -------------------------------------------------------------------------------- 1 | export class Log { 2 | _disabled?:boolean; 3 | constructor(disabled?: boolean) { 4 | this.disabled = disabled || false 5 | } 6 | get disabled () { 7 | return this._disabled; 8 | } 9 | set disabled(val: boolean) { 10 | this._disabled = val; 11 | } 12 | log = (message?: any, ...optionalParams: any[]) => { 13 | if (this.logger) this.logger(message); 14 | if (this.disabled) return () => {} 15 | return console.log(message, ...optionalParams) 16 | } 17 | error = (message?: any, ...optionalParams: any[]) => { 18 | if (this.logger) this.logger(message); 19 | if (this.disabled) return () => {} 20 | return console.error(message, ...optionalParams) 21 | } 22 | logger = (message?: string) => {} 23 | } 24 | 25 | export const log = new Log(); -------------------------------------------------------------------------------- /examples/templates/styles/_{{filename}}.less.template: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "{{fontname}}"; 2 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}'); /* IE9*/ 3 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url("{{cssPath}}{{fontname}}.woff2?t={{timestamp}}") format("woff2"), 5 | url("{{cssPath}}{{fontname}}.woff?t={{timestamp}}") format("woff"), 6 | url('{{cssPath}}{{fontname}}.ttf?t={{timestamp}}') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('{{cssPath}}{{fontname}}.svg?t={{timestamp}}#{{fontname}}') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | [class^="{{prefix}}-"], [class*=" {{prefix}}-"] { 11 | font-family: '{{fontname}}' !important;{{fontSize}} 12 | font-style:normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | {{cssString}} -------------------------------------------------------------------------------- /examples/templates/styles/_{{filename}}.styl.template: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "{{fontname}}"; 2 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}'); /* IE9*/ 3 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url("{{cssPath}}{{fontname}}.woff2?t={{timestamp}}") format("woff2"), 5 | url("{{cssPath}}{{fontname}}.woff?t={{timestamp}}") format("woff"), 6 | url('{{cssPath}}{{fontname}}.ttf?t={{timestamp}}') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('{{cssPath}}{{fontname}}.svg?t={{timestamp}}#{{fontname}}') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | [class^="{{prefix}}-"], [class*=" {{prefix}}-"] { 11 | font-family: '{{fontname}}' !important;{{fontSize}} 12 | font-style:normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | {{cssString}} -------------------------------------------------------------------------------- /examples/templates/styles/_{{filename}}.scss.template: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "{{fontname}}"; 2 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}'); /* IE9*/ 3 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url("{{cssPath}}{{fontname}}.woff2?t={{timestamp}}") format("woff2"), 5 | url("{{cssPath}}{{fontname}}.woff?t={{timestamp}}") format("woff"), 6 | url('{{cssPath}}{{fontname}}.ttf?t={{timestamp}}') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('{{cssPath}}{{fontname}}.svg?t={{timestamp}}#{{fontname}}') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | [class^="{{prefix}}-"], [class*=" {{prefix}}-"] { 11 | font-family: '{{fontname}}' !important;{{fontSize}} 12 | font-style:normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | {{cssString}} 18 | {{cssToVars}} 19 | -------------------------------------------------------------------------------- /examples/templates/styles/_{{filename}}.module.less.template: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "{{fontname}}"; 2 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}'); /* IE9*/ 3 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url("{{cssPath}}{{fontname}}.woff2?t={{timestamp}}") format("woff2"), 5 | url("{{cssPath}}{{fontname}}.woff?t={{timestamp}}") format("woff"), 6 | url('{{cssPath}}{{fontname}}.ttf?t={{timestamp}}') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('{{cssPath}}{{fontname}}.svg?t={{timestamp}}#{{fontname}}') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | [class^="{{prefix}}-"], [class*=" {{prefix}}-"] { 11 | font-family: '{{fontname}}' !important;{{fontSize}} 12 | font-style:normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | :global { 18 | {{cssString}} 19 | } -------------------------------------------------------------------------------- /examples/templates/styles/_{{filename}}.css.template: -------------------------------------------------------------------------------- 1 | /* Hello CSS! */ 2 | @font-face { 3 | font-family: "{{fontname}}"; 4 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}'); /* IE9*/ 5 | src: url('{{cssPath}}{{fontname}}.eot?t={{timestamp}}#iefix') format('embedded-opentype'), /* IE6-IE8 */ 6 | url("{{cssPath}}{{fontname}}.woff2?t={{timestamp}}") format("woff2"), 7 | url("{{cssPath}}{{fontname}}.woff?t={{timestamp}}") format("woff"), 8 | url('{{cssPath}}{{fontname}}.ttf?t={{timestamp}}') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 9 | url('{{cssPath}}{{fontname}}.svg?t={{timestamp}}#{{fontname}}') format('svg'); /* iOS 4.1- */ 10 | } 11 | 12 | [class^="{{prefix}}-"], [class*=" {{prefix}}-"] { 13 | font-family: '{{fontname}}' !important;{{fontSize}} 14 | font-style:normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | {{cssString}} 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 小弟调调™ 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. 22 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import FS from 'fs-extra'; 4 | import { Arguments } from 'yargs'; 5 | import yargs from 'yargs' 6 | import { hideBin } from 'yargs/helpers' 7 | import path from 'path'; 8 | import svgtofont from './index.js'; 9 | import { log } from './log.js'; 10 | 11 | type ArgvResult = Arguments<{ 12 | sources: string; 13 | output: string; 14 | fontName: string; 15 | }> 16 | 17 | const argv = yargs(hideBin(process.argv)) 18 | .alias('s', 'sources') 19 | .describe('s', 'The root from which all sources are relative.') 20 | .alias('o', 'output') 21 | .describe('o', 'Output directory.') 22 | .alias('f', 'fontName') 23 | .describe('f', 'Font Name.') 24 | .demandOption(['output', 'sources']) 25 | .help('h') 26 | .alias('h', 'help') 27 | .epilog('copyright 2019') 28 | .argv as ArgvResult; 29 | 30 | const sourcesPath = path.resolve(process.cwd(), argv.sources); 31 | const outputPath = path.resolve(process.cwd(), argv.output); 32 | 33 | if (!FS.pathExistsSync(sourcesPath)) { 34 | log.error('The directory does not exist!', sourcesPath); 35 | process.exit(); 36 | } 37 | 38 | if (!FS.pathExistsSync(outputPath)) { 39 | FS.mkdirpSync(outputPath); 40 | } 41 | 42 | svgtofont({ 43 | src: sourcesPath, // svg path 44 | dist: outputPath, // output path 45 | // emptyDist: true, // Clear output directory contents 46 | fontName: (argv.fontName) || "svgfont", // font name 47 | css: true, // Create CSS files. 48 | outSVGReact: true, 49 | outSVGReactNative: false, 50 | outSVGVue: true, 51 | outSVGPath: true, 52 | svgicons2svgfont: { 53 | fontHeight: 1000, 54 | normalize: true, 55 | }, 56 | }) 57 | .then(() => { 58 | log.log('done!'); 59 | }).catch((err) => { 60 | log.log('SvgToFont:ERR:', err); 61 | }); 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node 2 | # Edit at https://www.gitignore.io/?templates=node 3 | dist 4 | examples/example/dist 5 | examples/templates/dist 6 | examples/example#211/font 7 | lib 8 | package-lock.json 9 | yarn.lock 10 | __snapshots__ 11 | 12 | ### Node ### 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | 21 | # Diagnostic reports (https://nodejs.org/api/report.html) 22 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 23 | 24 | # Runtime data 25 | pids 26 | *.pid 27 | *.seed 28 | *.pid.lock 29 | 30 | # Directory for instrumented libs generated by jscoverage/JSCover 31 | lib-cov 32 | 33 | # Coverage directory used by tools like istanbul 34 | coverage 35 | 36 | # nyc test coverage 37 | .nyc_output 38 | 39 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 40 | .grunt 41 | 42 | # Bower dependency directory (https://bower.io/) 43 | bower_components 44 | 45 | # node-waf configuration 46 | .lock-wscript 47 | 48 | # Compiled binary addons (https://nodejs.org/api/addons.html) 49 | build/Release 50 | 51 | # Dependency directories 52 | node_modules/ 53 | jspm_packages/ 54 | 55 | # TypeScript v1 declaration files 56 | typings/ 57 | 58 | # Optional npm cache directory 59 | .npm 60 | 61 | # Optional eslint cache 62 | .eslintcache 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # next.js build output 81 | .next 82 | 83 | # nuxt.js build output 84 | .nuxt 85 | 86 | # vuepress build output 87 | .vuepress/dist 88 | 89 | # Serverless directories 90 | .serverless/ 91 | 92 | # FuseBox cache 93 | .fusebox/ 94 | 95 | # DynamoDB Local files 96 | .dynamodb/ 97 | 98 | # End of https://www.gitignore.io/api/node 99 | 100 | .DS_Store 101 | .cache 102 | .vscode 103 | .idea 104 | .env 105 | 106 | *.bak 107 | *.tem 108 | *.temp 109 | #.swp 110 | *.*~ 111 | ~*.* 112 | 113 | # IDEA 114 | *.iml 115 | *.ipr 116 | *.iws 117 | .idea/ 118 | -------------------------------------------------------------------------------- /examples/example/svg/stylelint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/templates/svg/stylelint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/example#211/svg/stylelint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import pkg from '../package.json'; 5 | 6 | // const __filename = fileURLToPath(import.meta.url); 7 | // const __dirname = path.dirname(__filename); 8 | // const pkg = fs.readJSONSync(path.resolve(__dirname, "../package.json")); 9 | 10 | // const fs = require('fs-extra'); 11 | // const path = require('path'); 12 | // const svgtofont = require('../lib/index.js'); 13 | 14 | it('example test case.', async () => { 15 | const dist = path.resolve(process.cwd(), 'examples', 'example', 'dist'); 16 | const fileNames = await fs.readdir(dist); 17 | expect(fileNames).toEqual([ 18 | 'font-class.html', 19 | 'index.html', 20 | 'react', 21 | 'reactNative', 22 | 'svgtofont.css', 23 | 'svgtofont.d.ts', 24 | 'svgtofont.eot', 25 | 'svgtofont.json', 26 | 'svgtofont.less', 27 | 'svgtofont.module.less', 28 | 'svgtofont.scss', 29 | 'svgtofont.styl', 30 | 'svgtofont.svg', 31 | 'svgtofont.symbol.svg', 32 | 'svgtofont.ttf', 33 | 'svgtofont.woff', 34 | 'svgtofont.woff2', 35 | 'symbol.html', 36 | 'vue', 37 | ]); 38 | }); 39 | 40 | it('example simple test case.', async () => { 41 | const dist = path.resolve(process.cwd(), 'examples', 'example', 'example'); 42 | const fileNames = await fs.readdir(dist); 43 | expect(fileNames).toEqual([ 44 | 'svgtofont.css', 45 | 'svgtofont.eot', 46 | 'svgtofont.less', 47 | 'svgtofont.module.less', 48 | 'svgtofont.scss', 49 | 'svgtofont.styl', 50 | 'svgtofont.svg', 51 | 'svgtofont.symbol.svg', 52 | 'svgtofont.ttf', 53 | 'svgtofont.woff', 54 | 'svgtofont.woff2', 55 | ]); 56 | }) 57 | 58 | it('templates templates test case.', async () => { 59 | const dist = path.resolve(process.cwd(), 'examples', 'templates', 'dist2'); 60 | const fileNames = await fs.readdir(dist); 61 | expect(fileNames).toEqual([ 62 | 'font-class.html', 63 | 'index.html', 64 | 'react', 65 | 'reactNative', 66 | 'svgtofont.css', 67 | 'svgtofont.eot', 68 | 'svgtofont.json', 69 | 'svgtofont.less', 70 | 'svgtofont.module.less', 71 | 'svgtofont.scss', 72 | 'svgtofont.styl', 73 | 'svgtofont.svg', 74 | 'svgtofont.symbol.svg', 75 | 'svgtofont.ttf', 76 | 'svgtofont.woff', 77 | 'svgtofont.woff2', 78 | 'symbol.html', 79 | 'vue' 80 | ]); 81 | const css = await fs.readFile(path.resolve(dist, 'svgtofont.css')); 82 | expect(css.toString().indexOf('Hello CSS!') > -1).toBeTruthy(); 83 | }) 84 | -------------------------------------------------------------------------------- /examples/example/index.mjs: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import fs from 'fs-extra'; 4 | 5 | import svgtofont from '../../lib/index.js'; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | const pkg = fs.readJSONSync(path.resolve(__dirname, "../../package.json")); 10 | 11 | console.log(pkg.name); // 输出: example 12 | console.log(pkg.version); // 输出: 1.0.0 13 | 14 | const rootPath = path.resolve(process.cwd(), "examples", "example"); 15 | 16 | svgtofont({ 17 | src: path.resolve(rootPath, "svg"), // svg path 18 | dist: path.resolve(rootPath, "dist"), // output path 19 | // emptyDist: true, // Clear output directory contents 20 | fontName: "svgtofont", // font name 21 | css: true, // Create CSS files. 22 | outSVGReact: true, 23 | outSVGReactNative: true, 24 | outSVGPath: true, 25 | outSVGVue: true, 26 | startNumber: 20000, // unicode start number 27 | svgicons2svgfont: { 28 | fontHeight: 1000, 29 | normalize: true 30 | }, 31 | typescript: true, 32 | // website = null, no demo html files 33 | website: { 34 | // Add a Github corner to your website 35 | // Like: https://github.com/uiwjs/react-github-corners 36 | corners: { 37 | url: 'https://github.com/jaywcjlove/svgtofont', 38 | width: 62, // default: 60 39 | height: 62, // default: 60 40 | bgColor: '#dc3545' // default: '#151513' 41 | }, 42 | index: "unicode", // Enum{"font-class", "unicode", "symbol"} 43 | title: "svgtofont", 44 | favicon: path.resolve(rootPath, "favicon.png"), 45 | // Must be a .svg format image. Missing here to ensure the example works without it. 46 | // logo: path.resolve(rootPath, "svg", "git.svg"), 47 | version: pkg.version, 48 | meta: { 49 | description: "Converts SVG fonts to TTF/EOT/WOFF/WOFF2/SVG format.", 50 | keywords: "svgtofont,TTF,EOT,WOFF,WOFF2,SVG" 51 | }, 52 | description: ``, 53 | links: [ 54 | { 55 | title: "GitHub", 56 | url: "https://github.com/jaywcjlove/svgtofont" 57 | }, 58 | { 59 | title: "Feedback", 60 | url: "https://github.com/jaywcjlove/svgtofont/issues" 61 | }, 62 | { 63 | title: "Font Class Demo", 64 | url: "font-class.html" 65 | }, 66 | { 67 | title: "Symbol Demo", 68 | url: "symbol.html" 69 | }, 70 | { 71 | title: "Unicode Demo", 72 | url: "index.html" 73 | } 74 | ], 75 | footerInfo: `Licensed under MIT. (Yes it's free and open-sourced)` 76 | } 77 | }) 78 | .then(() => { 79 | console.log("Example::::done!"); 80 | }); 81 | -------------------------------------------------------------------------------- /examples/templates/index.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { fileURLToPath } from 'url'; 4 | import svgtofont from '../../lib/index.js'; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename); 8 | const pkg = fs.readJSONSync(path.resolve(__dirname, "../../package.json")); 9 | 10 | const rootPath = path.resolve(process.cwd(), "examples", "templates"); 11 | /** 12 | * @type {import('../../lib/index.js').SvgToFontOptions} 13 | */ 14 | const options = { 15 | config: { 16 | cwd: rootPath, 17 | }, 18 | src: path.resolve(rootPath, "svg"), // svg path 19 | dist: path.resolve(rootPath, "dist"), // output path 20 | // emptyDist: true, // Clear output directory contents 21 | styleTemplates: path.resolve(rootPath, "styles"), 22 | fontName: "svgtofont", // font name 23 | css: true, // Create CSS files. 24 | outSVGReact: true, 25 | outSVGReactNative: true, 26 | outSVGPath: true, 27 | outSVGVue: true, 28 | startNumber: 20000, // unicode start number 29 | svgicons2svgfont: { 30 | fontHeight: 1000, 31 | normalize: true 32 | }, 33 | useCSSVars: true, 34 | // website = null, no demo html files 35 | website: { 36 | // Add a Github corner to your website 37 | // Like: https://github.com/uiwjs/react-github-corners 38 | corners: { 39 | url: 'https://github.com/jaywcjlove/svgtofont', 40 | width: 62, // default: 60 41 | height: 62, // default: 60 42 | bgColor: '#dc3545' // default: '#151513' 43 | }, 44 | index: "unicode", // Enum{"font-class", "unicode", "symbol"} 45 | title: "svgtofont", 46 | favicon: path.resolve(rootPath, "favicon.png"), 47 | // Must be a .svg format image. 48 | logo: path.resolve(rootPath, "svg", "git.svg"), 49 | version: pkg.version, 50 | meta: { 51 | description: "Converts SVG fonts to TTF/EOT/WOFF/WOFF2/SVG format.", 52 | keywords: "svgtofont,TTF,EOT,WOFF,WOFF2,SVG" 53 | }, 54 | description: ``, 55 | links: [ 56 | { 57 | title: "GitHub", 58 | url: "https://github.com/jaywcjlove/svgtofont" 59 | }, 60 | { 61 | title: "Feedback", 62 | url: "https://github.com/jaywcjlove/svgtofont/issues" 63 | }, 64 | { 65 | title: "Font Class Demo", 66 | url: "font-class.html" 67 | }, 68 | { 69 | title: "Symbol Demo", 70 | url: "symbol.html" 71 | }, 72 | { 73 | title: "Unicode Demo", 74 | url: "index.html" 75 | } 76 | ], 77 | footerInfo: `Licensed under MIT. (Yes it's free and open-sourced)` 78 | } 79 | } 80 | 81 | svgtofont(options).then(() => { 82 | console.log("done!"); 83 | }); 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svgtofont", 3 | "version": "6.5.0", 4 | "description": "Converts SVG to TTF/EOT/WOFF/WOFF2/SVG format fonts.", 5 | "homepage": "https://jaywcjlove.github.io/svgtofont/", 6 | "funding": "https://jaywcjlove.github.io/#/sponsor", 7 | "main": "./lib/index.js", 8 | "typings": "./lib/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "default": "./lib/index.js", 12 | "types": "./lib/index.d.ts" 13 | }, 14 | "./lib/utils": { 15 | "default": "./lib/utils.js", 16 | "types": "./lib/utils.d.ts" 17 | } 18 | }, 19 | "type": "module", 20 | "bin": { 21 | "svgtofont": "lib/cli.js" 22 | }, 23 | "scripts": { 24 | "prepare": "npm run build", 25 | "start": "node lib/index.js", 26 | "watch": "tsbb watch src/*.ts", 27 | "build": "tsbb build src/*.ts", 28 | "example": "node examples/example/index.mjs", 29 | "example:cli": "node lib/cli.js --sources ./examples/example/svg --output ./dist --fontName uiw-font", 30 | "example:simple": "node examples/example/simple.mjs", 31 | "example:templates": "node examples/templates/index.mjs", 32 | "pretest": "npm run example && npm run example:simple && npm run example:templates", 33 | "checked": "tsc --noEmit", 34 | "test": "tsbb test", 35 | "coverage": "tsbb test --coverage" 36 | }, 37 | "author": "Kenny ", 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/jaywcjlove/svgtofont.git" 41 | }, 42 | "svgtofont": { 43 | "css": { 44 | "fontSize": false 45 | } 46 | }, 47 | "keywords": [ 48 | "webfont", 49 | "font", 50 | "icon", 51 | "iconfont", 52 | "font-face", 53 | "compress", 54 | "minify", 55 | "font-cli", 56 | "ttf", 57 | "woff", 58 | "eot", 59 | "svg", 60 | "ttf2eot", 61 | "ttf2woff", 62 | "ttf2svg", 63 | "svg2ttf", 64 | "css", 65 | "base64" 66 | ], 67 | "license": "MIT", 68 | "files": [ 69 | "lib", 70 | "src" 71 | ], 72 | "engines": { 73 | "node": ">=18.0.0" 74 | }, 75 | "jest": { 76 | "transformIgnorePatterns": [ 77 | "/node_modules/?!(.*)" 78 | ], 79 | "moduleNameMapper": { 80 | "^(\\.{1,2}/.*)\\.js$": "$1" 81 | } 82 | }, 83 | "dependencies": { 84 | "auto-config-loader": "^2.0.0", 85 | "cheerio": "~1.0.0", 86 | "colors-cli": "~1.0.28", 87 | "fs-extra": "~11.2.0", 88 | "image2uri": "^2.1.2", 89 | "nunjucks": "^3.2.4", 90 | "svg2ttf": "~6.0.3", 91 | "svgicons2svgfont": "~15.0.0", 92 | "svgo": "~3.3.0", 93 | "ttf2eot": "~3.1.0", 94 | "ttf2woff": "~3.0.0", 95 | "ttf2woff2": "~8.0.0", 96 | "yargs": "^17.7.2" 97 | }, 98 | "devDependencies": { 99 | "@types/fs-extra": "^11.0.1", 100 | "@types/nunjucks": "^3.2.6", 101 | "@types/svg2ttf": "~5.0.1", 102 | "@types/ttf2eot": "~2.0.0", 103 | "@types/ttf2woff": "~2.0.2", 104 | "tsbb": "^4.4.0" 105 | }, 106 | "peerDependencies": { 107 | "@types/svg2ttf": "~5.0.1" 108 | }, 109 | "peerDependenciesMeta": { 110 | "@types/svg2ttf": { 111 | "optional": true 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-deploy: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: write 11 | id-token: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | registry-url: 'https://registry.npmjs.org' 18 | 19 | - run: npm install 20 | - run: npm run checked 21 | - run: npm run example 22 | - run: npm run example:cli 23 | - run: npm run example:simple 24 | - run: npm run example:templates 25 | - run: npm run coverage 26 | - run: cp -rf examples/example/dist coverage/example 27 | - run: cp -rf examples/templates/dist2 coverage/templates 28 | 29 | - name: Create idoc config. 30 | run: | 31 | cat > idoc.yml << EOF 32 | site: "SVG To Font {{version}}" 33 | menus: 34 | Home: index.html 35 | Sponsor: 36 | url: https://wangchujiang.com/#/sponsor 37 | target: __blank 38 | Demo: 39 | url: example/font-class.html 40 | target: __blank 41 | EOF 42 | 43 | - run: npm install idoc@1 -g 44 | - run: idoc --output coverage 45 | 46 | # - name: Converts Markdown to HTML 47 | # uses: jaywcjlove/markdown-to-html-cli@main 48 | # with: 49 | # output: coverage/index.html 50 | # github-corners: https://github.com/jaywcjlove/svgtofont.git 51 | 52 | - uses: jaywcjlove/coverage-badges-cli@main 53 | 54 | - name: Generate Contributors Images 55 | uses: jaywcjlove/github-action-contributors@main 56 | with: 57 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) 58 | avatarSize: 42 59 | output: coverage/CONTRIBUTORS.svg 60 | 61 | - name: Create Tag 62 | id: create_tag 63 | uses: jaywcjlove/create-tag-action@main 64 | with: 65 | package-path: ./package.json 66 | 67 | - name: get tag version 68 | id: tag_version 69 | uses: jaywcjlove/changelog-generator@main 70 | 71 | - name: Deploy 72 | uses: peaceiris/actions-gh-pages@v4 73 | with: 74 | commit_message: ${{steps.tag_version.outputs.tag}} ${{ github.event.head_commit.message }} 75 | github_token: ${{ secrets.GITHUB_TOKEN }} 76 | publish_dir: ./coverage 77 | 78 | - name: Generate Changelog 79 | id: changelog 80 | uses: jaywcjlove/changelog-generator@main 81 | if: steps.create_tag.outputs.successful 82 | with: 83 | head-ref: ${{steps.create_tag.outputs.version}} 84 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 85 | 86 | - name: ♻️ Create Release 87 | uses: jaywcjlove/create-tag-action@main 88 | with: 89 | package-path: ./package.json 90 | release: true 91 | body: | 92 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) [![](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/svgtofont@${{steps.create_tag.outputs.versionNumber}}/file/README.md) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/svgtofont)](https://bundlephobia.com/result?p=svgtofont@${{steps.create_tag.outputs.versionNumber}}) [![npm version](https://img.shields.io/npm/v/svgtofont.svg)](https://www.npmjs.com/package/svgtofont) 93 | 94 | Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/svgtofont/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html 95 | Comparing Changes: ${{ steps.changelog.outputs.compareurl }} 96 | 97 | ```bash 98 | npm i svgtofont 99 | ``` 100 | 101 | ${{ steps.changelog.outputs.changelog }} 102 | 103 | # - name: 📦 svgtofont publish to NPM 104 | # uses: JS-DevTools/npm-publish@v1 105 | # with: 106 | # token: ${{ secrets.NPM_TOKEN }} 107 | # package: ./package.json 108 | 109 | - name: 📦 svgtofont publish to NPM 110 | run: npm publish --access public --provenance 111 | continue-on-error: true 112 | env: 113 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /src/website/index.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ _title }} 6 | 7 | {% for k, v in meta|default({}) %} 8 | 9 | {% endfor %} 10 | {% if favicon %} 11 | 12 | {% endif %} 13 | {% if _type === 'font-class' and _link %} 14 | 15 | {% endif %} 16 | 115 | 116 | 117 | 118 | {% if corners and corners.url %} 119 | 120 | 125 | 126 | {% endif %} 127 |
128 | {% if typeof(logo) === 'string' %} 129 | 132 | {% endif %} 133 |

{{ _title }}{{ version }}

134 |
135 | {{ description|default(meta.description) }} 136 |
137 |

138 | {% for linkItem in links|default([]) %} 139 | {{ linkItem.title }}{% if not loop.last %} · {% endif %} 140 | {% endfor %} 141 |

142 |
143 |
144 |
    145 | {{ _IconHtml|safe }} 146 |
147 |
148 | 153 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /src/generate.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import { optimize } from 'svgo'; 4 | import { filterSvgFiles, toPascalCase } from './utils.js'; 5 | import { type SvgToFontOptions } from './'; 6 | 7 | /** 8 | * Generate Icon SVG Path Source 9 | * .json 10 | */ 11 | export async function generateIconsSource(options: SvgToFontOptions = {}){ 12 | const ICONS_PATH = filterSvgFiles(options.src) 13 | const data = await buildPathsObject(ICONS_PATH, options); 14 | const outPath = path.join(options.dist, `${options.fontName}.json`); 15 | await fs.outputFile(outPath, `{${data}\n}`); 16 | return outPath; 17 | } 18 | 19 | /** 20 | * Loads SVG file for each icon, extracts path strings `d="path-string"`, 21 | * and constructs map of icon name to array of path strings. 22 | * @param {array} files 23 | */ 24 | async function buildPathsObject(files: string[], options: SvgToFontOptions = {}) { 25 | const svgoOptions = options.svgoOptions || {}; 26 | return Promise.all( 27 | files.map(async filepath => { 28 | const name = path.basename(filepath, '.svg'); 29 | const svg = fs.readFileSync(filepath, 'utf-8'); 30 | const pathStrings = optimize(svg, { 31 | path: filepath, 32 | ...options, 33 | plugins: [ 34 | 'convertTransform', 35 | ...(svgoOptions.plugins || []) 36 | // 'convertShapeToPath' 37 | ], 38 | }); 39 | const str: string[] = (pathStrings.data.match(/ d="[^"]+"/g) || []).map(s => s.slice(3)); 40 | return `\n"${name}": [${str.join(',\n')}]`; 41 | }), 42 | ); 43 | } 44 | 45 | const reactSource = (name: string, size: string, fontName: string, source: string) => `import React from 'react'; 46 | export const ${name} = props => ( 47 | ${source} 48 | ); 49 | `; 50 | 51 | const reactTypeSource = (name: string) => `import React from 'react'; 52 | export declare const ${name}: (props: React.SVGProps) => JSX.Element; 53 | `; 54 | 55 | /** 56 | * Generate React Icon 57 | * .json 58 | */ 59 | export async function generateReactIcons(options: SvgToFontOptions = {}) { 60 | const ICONS_PATH = filterSvgFiles(options.src); 61 | const data = await outputReactFile(ICONS_PATH, options); 62 | const outPath = path.join(options.dist, 'react', 'index.js'); 63 | fs.outputFileSync(outPath, data.join('\n')); 64 | fs.outputFileSync(outPath.replace(/\.js$/, '.d.ts'), data.join('\n')); 65 | return outPath; 66 | } 67 | 68 | async function outputReactFile(files: string[], options: SvgToFontOptions = {}) { 69 | const svgoOptions = options.svgoOptions || {}; 70 | const fontSizeOpt = typeof options.css !== 'boolean' && options.css.fontSize 71 | const fontSize = typeof fontSizeOpt === 'boolean' ? (fontSizeOpt === true ? '16px' : '') : fontSizeOpt; 72 | const fontName = options.classNamePrefix || options.fontName 73 | return Promise.all( 74 | files.map(async filepath => { 75 | let name = toPascalCase(path.basename(filepath, '.svg')); 76 | if (/^[rR]eact$/.test(name)) { 77 | name = name + toPascalCase(fontName); 78 | } 79 | const svg = fs.readFileSync(filepath, 'utf-8'); 80 | const pathData = optimize(svg, { 81 | path: filepath, 82 | ...svgoOptions, 83 | plugins: [ 84 | 'removeXMLNS', 85 | 'removeEmptyAttrs', 86 | 'convertTransform', 87 | // 'convertShapeToPath', 88 | // 'removeViewBox' 89 | ...(svgoOptions.plugins || []) 90 | ] 91 | }); 92 | const str: string[] = (pathData.data.match(/ d="[^"]+"/g) || []).map(s => s.slice(3)); 93 | const outDistPath = path.join(options.dist, 'react', `${name}.js`); 94 | const pathStrings = str.map((d, i) => ``); 95 | const comName = isNaN(Number(name.charAt(0))) ? name : toPascalCase(fontName) + name; 96 | fs.outputFileSync(outDistPath, reactSource(comName, fontSize, fontName, pathStrings.join(',\n'))); 97 | fs.outputFileSync(outDistPath.replace(/\.js$/, '.d.ts'), reactTypeSource(comName)); 98 | return `export * from './${name}';`; 99 | }), 100 | ); 101 | } 102 | 103 | const reactNativeSource = (fontName: string, defaultSize: number, iconMap: Map) => `import { Text } from 'react-native'; 104 | 105 | const icons = ${JSON.stringify(Object.fromEntries(iconMap))}; 106 | 107 | export const ${fontName} = ({iconName, ...rest}) => { 108 | return ( 109 | {icons[iconName]} 110 | ); 111 | }; 112 | `; 113 | 114 | const reactNativeTypeSource = (name: string, iconMap: Map) => `import { TextStyle } from 'react-native'; 115 | 116 | export type ${name}IconNames = ${[...iconMap.keys()].reduce((acc, key, index) => { 117 | if (index === 0) { 118 | acc = `'${key}'` 119 | } else { 120 | acc += ` | '${key}'` 121 | } 122 | return acc; 123 | }, `${'string'}`)} 124 | 125 | export interface ${name}Props extends Omit { 126 | iconName: ${name}IconNames 127 | } 128 | 129 | export declare const ${name}: (props: ${name}Props) => JSX.Element; 130 | `; 131 | 132 | /** 133 | * Generate ReactNative Icon 134 | * .json 135 | */ 136 | export function generateReactNativeIcons(options: SvgToFontOptions = {}, unicodeObject: Record) { 137 | const ICONS_PATH = filterSvgFiles(options.src); 138 | outputReactNativeFile(ICONS_PATH, options, unicodeObject); 139 | } 140 | 141 | function outputReactNativeFile(files: string[], options: SvgToFontOptions = {}, unicodeObject: Record) { 142 | const fontSizeOpt = typeof options.css !== 'boolean' && options.css.fontSize; 143 | const fontSize = typeof fontSizeOpt === 'boolean' ? 16 : parseInt(fontSizeOpt); 144 | const fontName = options.classNamePrefix || options.fontName 145 | const iconMap = new Map(); 146 | files.map(filepath => { 147 | const baseFileName = path.basename(filepath, '.svg'); 148 | iconMap.set(baseFileName, unicodeObject[baseFileName]) 149 | }); 150 | const outDistPath = path.join(options.dist, 'reactNative', `${fontName}.jsx`); 151 | const comName = isNaN(Number(fontName.charAt(0))) ? fontName : toPascalCase(fontName) + name; 152 | fs.outputFileSync(outDistPath, reactNativeSource(comName, fontSize, iconMap)); 153 | fs.outputFileSync(outDistPath.replace(/\.jsx$/, '.d.ts'), reactNativeTypeSource(comName, iconMap)); 154 | } 155 | 156 | /** 157 | * Generate Vue Icon 158 | * .json 159 | */ 160 | export async function generateVueIcons(options: SvgToFontOptions = {}) { 161 | const ICONS_PATH = filterSvgFiles(options.src); 162 | const data = await outputVueFile(ICONS_PATH, options); 163 | const outPath = path.join(options.dist, 'vue', 'index.js'); 164 | fs.outputFileSync(outPath, data.join('\n')); 165 | fs.outputFileSync(outPath.replace(/\.js$/, '.d.ts'), data.join('\n')); 166 | return outPath; 167 | } 168 | 169 | async function outputVueFile(files: string[], options: SvgToFontOptions = {}) { 170 | const svgoOptions = options.svgoOptions || {}; 171 | const fontSizeOpt = typeof options.css !== 'boolean' && options.css.fontSize 172 | const fontSize = typeof fontSizeOpt === 'boolean' ? (fontSizeOpt === true ? '16px' : '') : fontSizeOpt; 173 | const fontName = options.classNamePrefix || options.fontName 174 | return Promise.all( 175 | files.map(async filepath => { 176 | let name = toPascalCase(path.basename(filepath, '.svg')); 177 | if (/^[vV]ue$/.test(name)) { 178 | name = name + toPascalCase(fontName); 179 | } 180 | const svg = fs.readFileSync(filepath, 'utf-8'); 181 | const pathData = optimize(svg, { 182 | path: filepath, 183 | ...svgoOptions, 184 | plugins: [ 185 | 'removeXMLNS', 186 | 'removeEmptyAttrs', 187 | 'convertTransform', 188 | // 'convertShapeToPath', 189 | // 'removeViewBox' 190 | ...(svgoOptions.plugins || []) 191 | ] 192 | }); 193 | const str: string[] = (pathData.data.match(/ d="[^"]+"/g) || []).map(s => s.slice(3)); 194 | const outDistPath = path.join(options.dist, 'vue', `${name}.js`); 195 | const pathStrings = str.map((d, i) => ``); 196 | const comName = isNaN(Number(name.charAt(0))) ? name : toPascalCase(fontName) + name; 197 | fs.outputFileSync(outDistPath, vueSource(comName, fontSize, fontName, pathStrings.join(',\n'))); 198 | fs.outputFileSync(outDistPath.replace(/\.js$/, '.d.ts'), vueTypeSource(comName)); 199 | return `export * from './${name}';`; 200 | }), 201 | ); 202 | } 203 | 204 | const vueSource = (name: string, size: string, fontName: string, source: string) => `import { defineComponent, h } from 'vue'; 205 | 206 | export const ${name} = defineComponent({ 207 | name: '${name}', 208 | props: { 209 | class: { 210 | type: String, 211 | default: '' 212 | } 213 | }, 214 | setup(props, { attrs }) { 215 | return () => h( 216 | 'svg', 217 | { 218 | viewBox: '0 0 20 20', 219 | ${size ? `width: '${size}', height: '${size}',` : ''} 220 | class: \`${fontName} \${props.class}\`, 221 | ...attrs 222 | }, 223 | [ 224 | ${source 225 | .split('\n') 226 | .filter(Boolean) 227 | .map(path => { 228 | const attrPairs = []; 229 | const attrRegex = /([a-zA-Z\-:]+)=("[^"]*"|'[^']*'|[^\s"']+)/g; 230 | let match; 231 | const pathContent = path.replace(/^$/g, ''); 232 | while ((match = attrRegex.exec(pathContent)) !== null) { 233 | const key = match[1]; 234 | const value = match[2]; 235 | attrPairs.push(`"${key}": ${value}`); 236 | } 237 | return `h('path', {${attrPairs.join(', ')}})`; 238 | }) 239 | .join(',\n ') 240 | } 241 | ] 242 | ); 243 | } 244 | }); 245 | `; 246 | 247 | const vueTypeSource = (name: string) => `import type { DefineComponent } from 'vue'; 248 | declare const ${name}: DefineComponent>; 249 | export { ${name} }; 250 | `; 251 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { SVGIcons2SVGFontStream } from 'svgicons2svgfont'; 2 | import fs, { ReadStream } from 'fs-extra'; 3 | import path from 'path'; 4 | import color from 'colors-cli'; 5 | import { load } from 'cheerio'; 6 | import svg2ttf from 'svg2ttf'; 7 | import ttf2eot from 'ttf2eot'; 8 | import ttf2woff from 'ttf2woff'; 9 | import ttf2woff2 from 'ttf2woff2'; 10 | import nunjucks from 'nunjucks'; 11 | import { merge } from 'auto-config-loader'; 12 | import { type SvgToFontOptions } from './'; 13 | import { log } from './log.js'; 14 | 15 | let UnicodeObj: Record = {}; 16 | /** 17 | * Unicode Private Use Area start. 18 | * https://en.wikipedia.org/wiki/Private_Use_Areas 19 | */ 20 | let startUnicode = 0xea01; 21 | 22 | /** 23 | * SVG to SVG font 24 | */ 25 | export function createSVG(options: SvgToFontOptions = {}): Promise> { 26 | startUnicode = options.startUnicode 27 | UnicodeObj = {} 28 | return new Promise(async (resolve, reject) => { 29 | const fontStream = new SVGIcons2SVGFontStream({ 30 | ...options.svgicons2svgfont 31 | }); 32 | 33 | function writeFontStream(svgPath: string) { 34 | // file name 35 | let _name = path.basename(svgPath, ".svg"); 36 | const glyph = fs.createReadStream(svgPath) as ReadStream & { metadata: { unicode: string[], name: string } }; 37 | 38 | const curUnicode = String.fromCodePoint(startUnicode); 39 | const [_curUnicode, _startUnicode] = options.getIconUnicode 40 | ? (options.getIconUnicode(_name, curUnicode, startUnicode) || [curUnicode]) : [curUnicode]; 41 | 42 | if (_startUnicode) startUnicode = _startUnicode; 43 | 44 | const unicode: string[] = [_curUnicode]; 45 | if (curUnicode === _curUnicode && (!_startUnicode || startUnicode === _startUnicode)) startUnicode++; 46 | 47 | UnicodeObj[_name] = unicode[0]; 48 | if (!!options.useNameAsUnicode) { 49 | unicode[0] = _name; 50 | UnicodeObj[_name] = _name; 51 | } 52 | if (!!options.addLigatures) { 53 | unicode.push(_name) 54 | } 55 | glyph.metadata = { unicode, name: _name }; 56 | fontStream.write(glyph); 57 | } 58 | 59 | const DIST_PATH = path.join(options.dist, options.fontName + ".svg"); 60 | // Setting the font destination 61 | fontStream.pipe(fs.createWriteStream(DIST_PATH)) 62 | .on("finish", () => { 63 | log.log(`${color.green('SUCCESS')} ${color.blue_bt('SVG')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); 64 | resolve(UnicodeObj); 65 | }) 66 | .on("error", (err) => { 67 | if (err) { 68 | reject(err); 69 | } 70 | }); 71 | filterSvgFiles(options.src).forEach((svg: string) => { 72 | if (typeof svg !== 'string') return false; 73 | writeFontStream(svg); 74 | }); 75 | 76 | // Do not forget to end the stream 77 | fontStream.end(); 78 | }); 79 | } 80 | 81 | /** 82 | * Converts a string to pascal case. 83 | * 84 | * @example 85 | * 86 | * ```js 87 | * toPascalCase('some_database_field_name'); // 'SomeDatabaseFieldName' 88 | * toPascalCase('Some label that needs to be pascalized'); 89 | * // 'SomeLabelThatNeedsToBePascalized' 90 | * toPascalCase('some-javascript-property'); // 'SomeJavascriptProperty' 91 | * toPascalCase('some-mixed_string with spaces_underscores-and-hyphens'); 92 | * // 'SomeMixedStringWithSpacesUnderscoresAndHyphens' 93 | * ``` 94 | */ 95 | export const toPascalCase = (str: string) => 96 | str 97 | .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) 98 | .map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase()) 99 | .join(''); 100 | 101 | /* 102 | * Filter svg files 103 | * @return {Array} svg files 104 | */ 105 | export function filterSvgFiles(svgFolderPath: string): string[] { 106 | let files = fs.readdirSync(svgFolderPath, 'utf-8'); 107 | let svgArr = []; 108 | if (!files) { 109 | throw new Error(`Error! Svg folder is empty.${svgFolderPath}`); 110 | } 111 | 112 | for (let i in files) { 113 | if (typeof files[i] !== 'string' || path.extname(files[i]) !== '.svg') continue; 114 | if (!~svgArr.indexOf(files[i])) { 115 | svgArr.push(path.join(svgFolderPath, files[i])); 116 | } 117 | } 118 | return svgArr; 119 | } 120 | 121 | export function snakeToUppercase(str: string) { 122 | return str.split(/[-_]/) 123 | .map(partial => partial.charAt(0).toUpperCase() + partial.slice(1)) 124 | .join('') 125 | } 126 | 127 | export type TypescriptOptions = { 128 | extension?: 'd.ts' | 'ts' | 'tsx', 129 | enumName?: string 130 | } 131 | 132 | /** 133 | * Create typescript declarations for icon classnames 134 | */ 135 | export async function createTypescript(options: Omit & { typescript: TypescriptOptions | true }) { 136 | const tsOptions = options.typescript === true ? {} : options.typescript; 137 | const uppercaseFontName = snakeToUppercase(options.fontName); 138 | const { extension = 'd.ts', enumName = uppercaseFontName } = tsOptions; 139 | const DIST_PATH = path.join(options.dist, `${options.fontName}.${extension}`); 140 | const fileNames = filterSvgFiles(options.src).map(svgPath => path.basename(svgPath, path.extname(svgPath))); 141 | await fs.writeFile( 142 | DIST_PATH, 143 | [ 144 | `export enum ${enumName} {`, 145 | ...fileNames.map(name => ` ${snakeToUppercase(name)} = "${options.classNamePrefix}-${name}",`), 146 | '}', 147 | `export type ${enumName}Classname = ${fileNames.map(name => `"${options.classNamePrefix}-${name}"`).join(' | ')}`, 148 | `export type ${enumName}Icon = ${fileNames.map(name => `"${name}"`).join(' | ')}`, 149 | `export const ${enumName}Prefix = "${options.classNamePrefix}-"`, 150 | ].join('\n'), 151 | ); 152 | log.log(`${color.green('SUCCESS')} Created ${DIST_PATH}`); 153 | } 154 | 155 | /** 156 | * SVG font to TTF 157 | */ 158 | export function createTTF(options: SvgToFontOptions = {}): Promise { 159 | return new Promise((resolve, reject) => { 160 | options.svg2ttf = options.svg2ttf || {}; 161 | const DIST_PATH = path.join(options.dist, options.fontName + ".ttf"); 162 | let ttf = svg2ttf(fs.readFileSync(path.join(options.dist, options.fontName + ".svg"), "utf8"), options.svg2ttf); 163 | const ttfBuf = Buffer.from(ttf.buffer); 164 | fs.writeFile(DIST_PATH, ttfBuf, (err: NodeJS.ErrnoException) => { 165 | if (err) { 166 | return reject(err); 167 | } 168 | log.log(`${color.green('SUCCESS')} ${color.blue_bt('TTF')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); 169 | resolve(ttfBuf); 170 | }); 171 | }); 172 | }; 173 | 174 | /** 175 | * TTF font to EOT 176 | */ 177 | export function createEOT(options: SvgToFontOptions = {}, ttf: Buffer) { 178 | return new Promise((resolve, reject) => { 179 | const DIST_PATH = path.join(options.dist, options.fontName + '.eot'); 180 | const eot = Buffer.from(ttf2eot(ttf).buffer); 181 | 182 | fs.writeFile(DIST_PATH, eot, (err: NodeJS.ErrnoException) => { 183 | if (err) { 184 | return reject(err); 185 | } 186 | log.log(`${color.green('SUCCESS')} ${color.blue_bt('EOT')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); 187 | resolve(eot); 188 | }); 189 | }); 190 | }; 191 | 192 | /** 193 | * TTF font to WOFF 194 | */ 195 | export function createWOFF(options: SvgToFontOptions = {}, ttf: Buffer) { 196 | return new Promise((resolve, reject) => { 197 | const DIST_PATH = path.join(options.dist, options.fontName + ".woff"); 198 | const woff = Buffer.from(ttf2woff(ttf).buffer); 199 | fs.writeFile(DIST_PATH, woff, (err) => { 200 | if (err) { 201 | return reject(err); 202 | } 203 | log.log(`${color.green('SUCCESS')} ${color.blue_bt('WOFF')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); 204 | resolve(woff); 205 | }); 206 | }); 207 | }; 208 | 209 | /** 210 | * TTF font to WOFF2 211 | */ 212 | export function createWOFF2(options: SvgToFontOptions = {}, ttf: Buffer) { 213 | return new Promise((resolve, reject) => { 214 | const DIST_PATH = path.join(options.dist, options.fontName + ".woff2"); 215 | const woff2 = Buffer.from(ttf2woff2(ttf).buffer); 216 | fs.writeFile(DIST_PATH, woff2, (err) => { 217 | if (err) { 218 | return reject(err); 219 | } 220 | log.log(`${color.green('SUCCESS')} ${color.blue_bt('WOFF2')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); 221 | resolve({ 222 | path: DIST_PATH 223 | }); 224 | }); 225 | }); 226 | }; 227 | 228 | /** 229 | * Create SVG Symbol 230 | */ 231 | export function createSvgSymbol(options: SvgToFontOptions = {}) { 232 | const DIST_PATH = path.join(options.dist, `${options.fontName}.symbol.svg`); 233 | const $ = load(''); 234 | return new Promise((resolve, reject) => { 235 | filterSvgFiles(options.src).forEach(svgPath => { 236 | const fileName = path.basename(svgPath, path.extname(svgPath)); 237 | let file = fs.readFileSync(svgPath, "utf8"); 238 | 239 | // trim xml declaration 240 | file = file.replace(/<\?xml.*?\?>\s*/g, '').trim(); 241 | 242 | const svgNode = $(file); 243 | const symbolNode = $(""); 244 | symbolNode.attr("viewBox", svgNode.attr("viewBox")); 245 | symbolNode.attr("id", `${options.classNamePrefix}-${fileName}`); 246 | symbolNode.append(svgNode.html()); 247 | $('svg').append(symbolNode); 248 | }); 249 | 250 | fs.writeFile(DIST_PATH, $.html("svg"), (err) => { 251 | if (err) { 252 | return reject(err); 253 | } 254 | log.log(`${color.green('SUCCESS')} ${color.blue_bt('Svg Symbol')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); 255 | resolve({ 256 | path: DIST_PATH, 257 | svg: $.html("svg") 258 | }); 259 | }); 260 | }); 261 | }; 262 | 263 | export type CSSOptions = { 264 | /** 265 | * Output the css file to the specified directory 266 | */ 267 | output?: string; 268 | /** 269 | * Which files are exported. 270 | */ 271 | include?: RegExp; 272 | /** 273 | * Setting font size. 274 | */ 275 | fontSize?: string | boolean; 276 | /** 277 | * Set the path in the css file 278 | * https://github.com/jaywcjlove/svgtofont/issues/48#issuecomment-739547189 279 | */ 280 | cssPath?: string; 281 | /** 282 | * Set file name 283 | * https://github.com/jaywcjlove/svgtofont/issues/48#issuecomment-739547189 284 | */ 285 | fileName?: string; 286 | /** 287 | * Ad hoc template variables. 288 | */ 289 | templateVars?: Record; 290 | /** 291 | * When including CSS files in a CSS file, 292 | * you can add a timestamp parameter or custom text to the file path to prevent browser caching issues and ensure style updates are applied. @default true 293 | * @example `path/to/iconfont.css?t=1612345678` 294 | */ 295 | hasTimestamp?: boolean | string; 296 | } 297 | 298 | // As we are processing css files, we need to eacape HTML entities. 299 | const safeNunjucks = nunjucks.configure({ autoescape: false }); 300 | 301 | /** 302 | * Copy template files 303 | */ 304 | export async function copyTemplate(inDir: string, outDir: string, { _opts, ...vars }: Record & { _opts: CSSOptions }) { 305 | const files = await fs.readdir(inDir, { withFileTypes: true }); 306 | const context = { 307 | ...(_opts.templateVars || {}), 308 | ...vars, 309 | cssPath: _opts.cssPath || '', 310 | filename: _opts.fileName || vars.fontname, 311 | } 312 | await fs.ensureDir(outDir); 313 | for (const file of files) { 314 | if (!file.isFile()) continue; 315 | if (_opts.include && !(new RegExp(_opts.include)).test(file.name)) continue; 316 | let newFileName = file.name.replace(/\.template$/, '').replace(/^_/, ''); 317 | for (const key in context) newFileName = newFileName.replace(`{{${key}}}`, `${context[key]}`); 318 | const template = await fs.readFile(path.join(inDir, file.name), 'utf8'); 319 | const content = safeNunjucks.renderString(template, context); 320 | const filePath = path.join(outDir, newFileName) 321 | await fs.writeFile(filePath, content); 322 | log.log(`${color.green('SUCCESS')} Created ${filePath} `); 323 | } 324 | }; 325 | 326 | /** 327 | * Create HTML 328 | */ 329 | export function createHTML(templatePath: string, data: Record): string { 330 | return nunjucks.renderString(fs.readFileSync(templatePath, 'utf8'), { 331 | ...data, 332 | Date: Date, 333 | JSON: JSON, 334 | Math: Math, 335 | Number: Number, 336 | Object: Object, 337 | RegExp: RegExp, 338 | String: String, 339 | typeof: (v: any) => typeof v, 340 | }); 341 | }; 342 | 343 | export function generateFontFaceCSS(fontName: string, cssPath: string, timestamp: number, excludeFormat: string[], hasTimestamp: boolean | string = true): string { 344 | const timestamString = hasTimestamp === true ? `?t=${timestamp}` : (typeof hasTimestamp == 'string' ? `?t=${hasTimestamp}` : undefined); 345 | const formats = [ 346 | { ext: 'eot', format: 'embedded-opentype', ieFix: true }, 347 | { ext: 'woff2', format: 'woff2' }, 348 | { ext: 'woff', format: 'woff' }, 349 | { ext: 'ttf', format: 'truetype' }, 350 | { ext: 'svg', format: 'svg' } 351 | ]; 352 | let cssString = ` font-family: "${fontName}";\n`; 353 | if (!excludeFormat.includes('eot')) { 354 | cssString += ` src: url('${cssPath}${fontName}.eot${timestamString || ''}'); /* IE9*/\n`; 355 | } 356 | cssString += ' src: '; 357 | const srcParts = formats 358 | .filter(format => !excludeFormat.includes(format.ext)) 359 | .map(format => { 360 | if (format.ext === 'eot') { 361 | return `url('${cssPath}${fontName}.eot${timestamString || '?'}#iefix') format('${format.format}') /* IE6-IE8 */`; 362 | } 363 | return `url('${cssPath}${fontName}.${format.ext}${timestamString || ''}') format('${format.format}')`; 364 | }); 365 | cssString += srcParts.join(',\n ') + ';'; 366 | return cssString; 367 | } 368 | 369 | export const getDefaultOptions = (options: SvgToFontOptions): SvgToFontOptions => { 370 | return merge({ 371 | dist: path.resolve(process.cwd(), 'fonts'), 372 | src: path.resolve(process.cwd(), 'svg'), 373 | startUnicode: 0xea01, 374 | svg2ttf: {}, 375 | svgicons2svgfont: { 376 | fontName: 'iconfont', 377 | }, 378 | fontName: 'iconfont', 379 | symbolNameDelimiter: '-', 380 | }, options); 381 | }; 382 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | import fs from 'fs-extra'; 4 | import image2uri from 'image2uri'; 5 | import { type SVGIcons2SVGFontStreamOptions } from 'svgicons2svgfont'; 6 | import color from 'colors-cli'; 7 | import { autoConf, merge, type AutoConfOption } from 'auto-config-loader'; 8 | import type { FontOptions } from 'svg2ttf'; 9 | import type { Config } from 'svgo'; 10 | import { log } from './log.js'; 11 | import { generateIconsSource, generateReactIcons, generateReactNativeIcons, generateVueIcons } from './generate.js'; 12 | import { createSVG, createTTF, createEOT, createWOFF, createWOFF2, createSvgSymbol, copyTemplate, type CSSOptions, createHTML, createTypescript, type TypescriptOptions } from './utils.js'; 13 | import { generateFontFaceCSS, getDefaultOptions } from './utils.js'; 14 | 15 | const __filename = fileURLToPath(import.meta.url); 16 | const __dirname = path.dirname(__filename); 17 | 18 | export type SvgToFontOptions = { 19 | /** Support for .svgtofontrc and more configuration files. */ 20 | config?: AutoConfOption 21 | /** A value of `false` disables logging */ 22 | log?: boolean; 23 | /** log callback function */ 24 | logger?: (message: string) => void; 25 | /** 26 | * The output directory. 27 | * @default fonts 28 | * @example 29 | * ``` 30 | * path.join(process.cwd(), 'fonts') 31 | * ``` 32 | */ 33 | dist?: string; 34 | /** 35 | * svg path 36 | * @default svg 37 | * @example 38 | * ``` 39 | * path.join(process.cwd(), 'svg') 40 | * ``` 41 | */ 42 | src?: string; 43 | /** 44 | * The font family name you want. 45 | * @default iconfont 46 | */ 47 | fontName?: string; 48 | /** 49 | * Create CSS/LESS/Scss/Styl files, default `true`. 50 | */ 51 | css?: boolean | CSSOptions; 52 | /** 53 | * Output `./dist/react/`, SVG generates `react` components. 54 | */ 55 | outSVGReact?: boolean; 56 | /** 57 | * Output `./dist/reactNative/`, SVG generates `reactNative` component. 58 | */ 59 | outSVGReactNative?: boolean; 60 | /** 61 | * Output `./dist/vue/`, SVG generates `vue` components. 62 | */ 63 | outSVGVue?: boolean; 64 | /** 65 | * Output `./dist/svgtofont.json`, The content is as follows: 66 | * @example 67 | * ```js 68 | * { 69 | * "adobe": ["M14.868 3H23v19L14.868 3zM1 3h8.8.447z...."], 70 | * "git": ["M2.6 10.59L8.38 4.8l1.69 1.7c-.24c-.6.34-1 .99-1..."], 71 | * "stylelint": ["M129.74 243.648c28-100.5.816c2.65..."] 72 | * } 73 | * ``` 74 | */ 75 | outSVGPath?: boolean; 76 | /** 77 | * Output `./dist/info.json`, The content is as follows: 78 | * @example 79 | * ```js 80 | * { 81 | * "adobe": { 82 | * "encodedCode": "\\ea01", 83 | * "prefix": "svgtofont", 84 | * "className": "svgtofont-adobe", 85 | * "unicode": "" 86 | * }, 87 | * ..... 88 | * } 89 | * ``` 90 | */ 91 | generateInfoData?: boolean; 92 | /** 93 | * This is the setting for [svgicons2svgfont](https://github.com/nfroidure/svgicons2svgfont/tree/dd713bea4f97afa59f7dba6a21ff7f22db565bcf#api) 94 | */ 95 | svgicons2svgfont?: Partial; 96 | /** Some options can be configured with svgoOptions though it. [svgo](https://github.com/svg/svgo#configuration) */ 97 | svgoOptions?: Config; 98 | /** 99 | * Create font class name prefix, default value font name. 100 | * @default fontName 101 | */ 102 | classNamePrefix?: SvgToFontOptions['fontName']; 103 | /** 104 | * Symbol Name Delimiter, @default `-` 105 | */ 106 | symbolNameDelimiter?: string; 107 | /** 108 | * Directory of custom templates. 109 | */ 110 | styleTemplates?: string; 111 | /** 112 | * unicode start number 113 | * @default 10000 114 | */ 115 | startUnicode?: number; 116 | /** Get Icon Unicode */ 117 | getIconUnicode?: (name: string, unicode: string, startUnicode: number) => [string, number]; 118 | /** 119 | * should the name(file name) be used as unicode? this switch allows for the support of ligatures. 120 | * @default false 121 | */ 122 | useNameAsUnicode?: boolean; 123 | /** 124 | * adds possibility to use name (file name) in addition to codepoints. adds support of ligatures. 125 | * @default false 126 | */ 127 | addLigatures?: boolean 128 | /** 129 | * consoles whenever {{ cssString }} template outputs unicode characters or css vars 130 | * @default false 131 | */ 132 | useCSSVars?: boolean; 133 | /** 134 | * Clear output directory contents 135 | * @default false 136 | */ 137 | emptyDist?: boolean; 138 | /** 139 | * This is the setting for [svg2ttf](https://github.com/fontello/svg2ttf/tree/c33a126920f46b030e8ce960cc7a0e38a6946bbc#svg2ttfsvgfontstring-options---buf) 140 | */ 141 | svg2ttf?: FontOptions; 142 | /** 143 | * You can configure which font files to exclude from generation. By default, all font files will be generated. 144 | * https://github.com/jaywcjlove/svgtofont/issues/238 145 | */ 146 | excludeFormat?: Array<"eot" | "woff" | "woff2" | "ttf" | "svg" | "symbol.svg">; 147 | website?: { 148 | /** 149 | * Add a Github corner to your website 150 | * @like https://github.com/uiwjs/react-github-corners 151 | */ 152 | corners?: { 153 | /** 154 | * @example `https://github.com/jaywcjlove/svgtofont` 155 | */ 156 | url?: string, 157 | /** 158 | * @default 60 159 | */ 160 | width?: number, 161 | /** 162 | * @default 60 163 | */ 164 | height?: number, 165 | /** 166 | * @default #151513 167 | */ 168 | bgColor?: '#dc3545' 169 | }, 170 | /** 171 | * @default unicode 172 | */ 173 | index?: 'font-class' | 'unicode' | 'symbol'; 174 | /** 175 | * website title 176 | */ 177 | title?: string; 178 | /** 179 | * @example 180 | * ```js 181 | * path.resolve(rootPath, "favicon.png") 182 | * ``` 183 | */ 184 | favicon?: string; 185 | /** 186 | * Must be a .svg format image. 187 | * @example 188 | * ```js 189 | * path.resolve(rootPath, "svg", "git.svg") 190 | * ``` 191 | */ 192 | logo?: string, 193 | version?: string, 194 | meta?: { 195 | description?: string; 196 | keywords?: string; 197 | }, 198 | description?: string; 199 | template?: string; 200 | footerInfo?: string; 201 | links: Array<{ 202 | title: string; 203 | url: string; 204 | }>; 205 | }; 206 | 207 | /** 208 | * Create typescript file with declarations for icon classnames 209 | * @default false 210 | */ 211 | typescript?: boolean | TypescriptOptions 212 | } 213 | 214 | export type IconInfo = { 215 | prefix: string; 216 | symbol: string; 217 | unicode: string; 218 | className: string; 219 | encodedCode: string | number; 220 | } 221 | export type InfoData = Record> 222 | 223 | const loadConfig = async (options: SvgToFontOptions): Promise => { 224 | const defaultOptions = getDefaultOptions(options); 225 | const data = await autoConf('svgtofont', { 226 | mustExist: true, 227 | default: defaultOptions, 228 | ...options.config, 229 | }); 230 | return merge(defaultOptions, data); 231 | }; 232 | 233 | const handlePkgConfig = (options: SvgToFontOptions): SvgToFontOptions => { 234 | const pkgPath = path.join(process.cwd(), 'package.json'); 235 | if (fs.pathExistsSync(pkgPath)) { 236 | const pkg = fs.readJSONSync(pkgPath); 237 | if (pkg.svgtofont) { 238 | const cssOptions = options.css; 239 | options = merge(options, pkg.svgtofont); 240 | if (pkg.svgtofont.css && cssOptions && typeof cssOptions === 'object') { 241 | options.css = merge(cssOptions, pkg.svgtofont.css); 242 | } 243 | } 244 | if (options.website && pkg.version) { 245 | options.website.version = options.website.version ?? pkg.version; 246 | } 247 | } 248 | return options; 249 | }; 250 | 251 | export default async (options: SvgToFontOptions = {}) => { 252 | options = await loadConfig(options); 253 | options = handlePkgConfig(options); 254 | if (options.log === undefined) options.log = true; 255 | log.disabled = !options.log; 256 | if (options.logger && typeof options.logger === 'function') log.logger = options.logger; 257 | 258 | options.svgicons2svgfont.fontName = options.fontName; 259 | options.classNamePrefix = options.classNamePrefix || options.fontName; 260 | 261 | const excludeFormat = options.excludeFormat || []; 262 | 263 | const fontSizeOpt = typeof options.css !== 'boolean' && options.css.fontSize; 264 | const fontSize = typeof fontSizeOpt === 'boolean' ? (fontSizeOpt === true ? 'font-size: 16px;' : '') : `font-size: ${fontSizeOpt};`; 265 | // If you generate a font you need to generate a style. 266 | if (options.website && !options.css) options.css = true; 267 | 268 | const infoDataPath = path.resolve(options.dist, 'info.json'); 269 | try { 270 | if (options.emptyDist) { 271 | await fs.emptyDir(options.dist); 272 | } 273 | // Ensures that the directory exists. 274 | await fs.ensureDir(options.dist); 275 | const unicodeObject = await createSVG(options); 276 | 277 | /** @deprecated */ 278 | let cssToVars: string[] = []; 279 | let cssString: string[] = []; 280 | let cssRootVars: string[] = []; 281 | let cssIconHtml: string[] = []; 282 | let unicodeHtml: string[] = []; 283 | let symbolHtml: string[] = []; 284 | const prefix = options.classNamePrefix || options.fontName; 285 | const infoData: InfoData = {} 286 | 287 | Object.keys(unicodeObject).forEach((name, index, self) => { 288 | if (!infoData[name]) infoData[name] = {}; 289 | const _code = unicodeObject[name]; 290 | let symbolName = options.classNamePrefix + options.symbolNameDelimiter + name 291 | let iconPart = symbolName + '">'; 292 | let encodedCodes: string | number = _code.codePointAt(0); 293 | 294 | if (options.useNameAsUnicode) { 295 | symbolName = name; 296 | iconPart = prefix + '">' + name; 297 | encodedCodes = [..._code].map(x => x.codePointAt(0)).join(';&#'); 298 | } else { 299 | cssToVars.push(`$${symbolName}: "\\${encodedCodes.toString(16)}";\n`); 300 | if (options.useCSSVars) { 301 | if (index === 0) cssRootVars.push(`:root {\n`) 302 | cssRootVars.push(`--${symbolName}: "\\${encodedCodes.toString(16)}";\n`); 303 | cssString.push(`.${symbolName}::before { content: var(--${symbolName}); }\n`); 304 | if (index === self.length - 1) cssRootVars.push(`}\n`) 305 | } else { 306 | cssString.push(`.${symbolName}::before { content: "\\${encodedCodes.toString(16)}"; }\n`); 307 | } 308 | } 309 | infoData[name].encodedCode = `\\${encodedCodes.toString(16)}`; 310 | infoData[name].prefix = prefix; 311 | infoData[name].className = symbolName; 312 | infoData[name].unicode = `&#${encodedCodes};`; 313 | cssIconHtml.push(`
  • ${name}

  • `); 314 | unicodeHtml.push(`
  • ${_code}

    ${name}

    &#${encodedCodes};
  • `); 315 | symbolHtml.push(` 316 |
  • 317 | 320 |

    ${symbolName}

    321 |
  • 322 | `); 323 | }); 324 | 325 | if (options.useCSSVars) { 326 | cssString = [...cssRootVars, ...cssString] 327 | } 328 | 329 | if (options.generateInfoData) { 330 | await fs.writeJSON(infoDataPath, infoData, { spaces: 2 }); 331 | log.log(`${color.green('SUCCESS')} Created ${infoDataPath} `); 332 | } 333 | 334 | const ttf = await createTTF(options); 335 | if (!excludeFormat.includes('eot')) await createEOT(options, ttf); 336 | if (!excludeFormat.includes('woff')) await createWOFF(options, ttf); 337 | if (!excludeFormat.includes('woff2')) await createWOFF2(options, ttf); 338 | if (!excludeFormat.includes('symbol.svg')) await createSvgSymbol(options); 339 | 340 | const ttfPath = path.join(options.dist, options.fontName + ".ttf"); 341 | if (excludeFormat.includes('ttf')) { 342 | fs.removeSync(ttfPath); 343 | } 344 | const svgPath = path.join(options.dist, options.fontName + ".svg"); 345 | if (excludeFormat.includes('svg')) { 346 | fs.removeSync(svgPath) 347 | } 348 | 349 | if (options.css) { 350 | const styleTemplatePath = options.styleTemplates || path.resolve(__dirname, 'styles') 351 | const outDir = typeof options.css === 'object' ? options.css.output || options.dist : options.dist; 352 | const hasTimestamp = typeof options.css === 'object' ? options.css.hasTimestamp : true; 353 | 354 | const cssOptions = typeof options.css === 'object' ? options.css : {}; 355 | const fontFamilyString = generateFontFaceCSS(options.fontName, cssOptions.cssPath || "", Date.now(), excludeFormat, hasTimestamp); 356 | 357 | await copyTemplate(styleTemplatePath, outDir, { 358 | fontname: options.fontName, 359 | cssString: cssString.join(''), 360 | cssToVars: cssToVars.join(''), 361 | infoData, 362 | fontSize: fontSize, 363 | timestamp: new Date().getTime(), 364 | prefix, 365 | fontFamily: fontFamilyString, 366 | nameAsUnicode: options.useNameAsUnicode, 367 | _opts: cssOptions 368 | }); 369 | } 370 | 371 | if (options.typescript) { 372 | await createTypescript({ ...options, typescript: options.typescript }) 373 | } 374 | 375 | if (options.website) { 376 | const pageNames = ['font-class', 'unicode', 'symbol']; 377 | const htmlPaths: Record = {}; 378 | // setting default home page. 379 | const indexName = pageNames.includes(options.website.index) ? options.website.index : 'font-class'; 380 | pageNames.forEach(name => { 381 | const fileName = name === indexName ? 'index.html' : `${name}.html`; 382 | htmlPaths[name] = path.join(options.dist, fileName); 383 | }); 384 | const fontClassPath = htmlPaths['font-class']; 385 | const unicodePath = htmlPaths['unicode']; 386 | const symbolPath = htmlPaths['symbol']; 387 | 388 | // default template 389 | options.website.template = options.website.template || path.join(__dirname, 'website', 'index.njk'); 390 | // template data 391 | const tempData: SvgToFontOptions['website'] & { 392 | fontname: string; 393 | classNamePrefix: string; 394 | _type: string; 395 | _link: string; 396 | _IconHtml: string; 397 | _title: string; 398 | } = { 399 | meta: null, 400 | links: null, 401 | corners: null, 402 | description: null, 403 | footerInfo: null, 404 | ...options.website, 405 | fontname: options.fontName, 406 | classNamePrefix: options.classNamePrefix, 407 | _type: 'font-class', 408 | _link: `${(options.css && typeof options.css !== 'boolean' && options.css.fileName) || options.fontName}.css`, 409 | _IconHtml: cssIconHtml.join(''), 410 | _title: options.website.title || options.fontName 411 | }; 412 | // website logo 413 | if (options.website.logo && fs.pathExistsSync(options.website.logo) && path.extname(options.website.logo) === '.svg') { 414 | tempData.logo = fs.readFileSync(options.website.logo).toString(); 415 | } 416 | // website favicon 417 | if (options.website.favicon && fs.pathExistsSync(options.website.favicon)) { 418 | tempData.favicon = await image2uri(options.website.favicon); 419 | } else { 420 | tempData.favicon = ''; 421 | } 422 | const classHtmlStr = await createHTML(options.website.template, tempData); 423 | fs.outputFileSync(fontClassPath, classHtmlStr); 424 | log.log(`${color.green('SUCCESS')} Created ${fontClassPath} `); 425 | 426 | tempData._IconHtml = unicodeHtml.join(''); 427 | tempData._type = 'unicode'; 428 | const unicodeHtmlStr = await createHTML(options.website.template, tempData); 429 | fs.outputFileSync(unicodePath, unicodeHtmlStr); 430 | log.log(`${color.green('SUCCESS')} Created ${unicodePath} `); 431 | 432 | tempData._IconHtml = symbolHtml.join(''); 433 | tempData._type = 'symbol'; 434 | const symbolHtmlStr = await createHTML(options.website.template, tempData); 435 | fs.outputFileSync(symbolPath, symbolHtmlStr); 436 | log.log(`${color.green('SUCCESS')} Created ${symbolPath} `); 437 | } 438 | 439 | if (options.outSVGPath) { 440 | const outPath = await generateIconsSource(options); 441 | log.log(`${color.green('SUCCESS')} Created ${outPath} `); 442 | } 443 | if (options.outSVGReact) { 444 | const outPath = await generateReactIcons(options); 445 | log.log(`${color.green('SUCCESS')} Created React Components. `); 446 | } 447 | if (options.outSVGReactNative) { 448 | generateReactNativeIcons(options, unicodeObject); 449 | log.log(`${color.green('SUCCESS')} Created React Native Components. `); 450 | } 451 | if (options.outSVGVue) { 452 | const outPath = await generateVueIcons(options); 453 | log.log(`${color.green('SUCCESS')} Created Vue Components. `); 454 | } 455 | 456 | return infoData; 457 | } catch (error) { 458 | log.log('SvgToFont:CLI:ERR:', error); 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
    3 | Using my app is also a way to support me: 4 |
    5 | Deskmark 6 | Keyzer 7 | Vidwall Hub 8 | VidCrop 9 | Vidwall 10 | Mousio Hint 11 | Mousio 12 | Musicer 13 | Audioer 14 | FileSentinel 15 | FocusCursor 16 | Videoer 17 | KeyClicker 18 | DayBar 19 | Iconed 20 | Mousio 21 | Quick RSS 22 | Quick RSS 23 | Web Serve 24 | Copybook Generator 25 | DevTutor for SwiftUI 26 | RegexMate 27 | Time Passage 28 | Iconize Folder 29 | Textsound Saver 30 | Create Custom Symbols 31 | DevHub 32 | Resume Revise 33 | Palette Genius 34 | Symbol Scribe 35 |
    36 |
    37 | 38 | [Free Font](https://github.com/jaywcjlove/free-font) 39 | 40 |

    41 | 42 | SVG To Font 43 | 44 |

    45 | 46 |

    47 | 48 | Buy me a coffee 49 | 50 | 51 | Build & Deploy 52 | 53 | 54 | Gitee Repo 55 | 56 | 57 | Open in unpkg 58 | 59 | 60 | NPM Download 61 | 62 | 63 | npm version 64 | 65 |

    66 | 67 | Read a set of SVG icons and ouput a TTF/EOT/WOFF/WOFF2/SVG font, Generator of fonts from SVG icons. 68 | 69 | [Install](#install) · [Usage](#using-with-nodejs) · [Command](#using-with-command) · [Font Usage](#font-usage) · [API](#api) · [options](#options) · [npm](http://npm.im/svgtofont) · [License](#license) 70 | 71 | **Features:** 72 | 73 | - Supported font formats: `WOFF2`, `WOFF`, `EOT`, `TTF` and `SVG`. 74 | - Support SVG Symbol file. 75 | - Support [`React`](https://github.com/facebook/react), [`ReactNative`](https://github.com/facebook/react-native), [`Vue`](https://github.com/vuejs/core) & [`TypeScript`](https://github.com/microsoft/TypeScript). 76 | - Support [`Less`](https://github.com/less/less.js)/[`Sass`](https://github.com/sass/sass)/[`Stylus`](https://github.com/stylus/stylus). 77 | - Allows to use custom templates (example `css`, `less` and etc). 78 | - Automatically generate a preview site. 79 | 80 | ```bash 81 | ╭┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈╮ 82 | ┆ Project ┆ 83 | ┆ ╭┈┈┈┈┈┈┈┈┈┈┈╮ ┆ 84 | ╭┈┈┈┈┈┈┈┈╮ ┆ ┆ svg ┆┈┈╮ ┆ 85 | ┆iconfont┆┈┈╮ ┆ ╰┈┈┈┈┈┈┈┈┈┈┈╯ ┆ ┆ 86 | ╰┈┈┈┈┈┈┈┈╯ ┆ ╭┈┈┈┈┈┈┈┈┈┈┈┈╮ ┆ ╭┈┈┈┈┈┈┈┈┈┈┈╮ ┆ ┆ 87 | ├┈▶┆download svg┆┈┈▶┆ ┆┈svgtofont┈┆ ┆ ┆ 88 | ╭┈┈┈┈┈┈┈┈╮ ┆ ╰┈┈┈┈┈┈┈┈┈┈┈┈╯ ┆╭┈┈┆create font┆◀┈╯ ┆ 89 | ┆icomoon ┆┈┈╯ ┆┆ ╰┈┈┈┈┈┈┈┈┈┈┈╯ ┆ 90 | ╰┈┈┈┈┈┈┈┈╯ ┆┆ ╭┈┈┈┈┈┈┈┈┈┈┈╮ ┆ 91 | ┆╰┈▶┆ use font ┆ ┆ 92 | ┆ ╰┈┈┈┈┈┈┈┈┈┈┈╯ ┆ 93 | ╰┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈╯ 94 | ``` 95 | 96 | ```mermaid 97 | graph LR; 98 | A[iconfont]-->C[Download SVG]; 99 | B[icomoon]-->C; 100 | D[icongo]-->C; 101 | E[yesicon]-->C; 102 | click A "https://www.iconfont.cn" "阿里巴巴矢量图标库" _blank 103 | click B "https://icomoon.io" "Pixel Perfect Icon Solutions" _blank 104 | click D "https://icongo.github.io" "Include popular icons in your React projects easily icons." _blank 105 | click E "https://yesicon.app/" "216,162 High-Quality Vector Icons from Top Design Teams." _blank 106 | C .-> ide1 107 | subgraph ide1 [Project] 108 | svg -->a2[svgtofont create font] 109 | a2 .-> b3[use font] 110 | end 111 | ``` 112 | 113 | **Icon Font Created By svgtofont** 114 | 115 | - [file-icons](https://uiwjs.github.io/file-icons/) File icons in the file tree. 116 | - [uiw-iconfont](https://github.com/uiwjs/icons) The premium icon font for [@uiwjs](https://github.com/uiwjs) Component Library. Support [`React`](https://github.com/facebook/react) & [`TypeScript`](https://github.com/microsoft/TypeScript). 117 | - [Bootstrap Icons Font](https://github.com/uiwjs/bootstrap-icons) Official open source SVG icon library for Bootstrap. 118 | - [test example](./test) For a simple test example, run `npm run test` in the root directory to see the results. 119 | 120 | ## Install 121 | 122 | ```bash 123 | npm i svgtofont 124 | ``` 125 | 126 | > [!NOTE] 127 | > This package `v5+` is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c): Node 18+ is needed to use it and it must be `import` instead of `require`. 128 | > ```js 129 | > import svgtofont from 'svgtofont'; 130 | > ``` 131 | 132 | #### Using With Command 133 | 134 | ```json 135 | { 136 | "scripts": { 137 | "font": "svgtofont --sources ./svg --output ./font --fontName uiw-font" 138 | }, 139 | "svgtofont": { 140 | "css": { 141 | "fontSize": "12px" 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | You can add configuration to package.json. [#48](https://github.com/jaywcjlove/svgtofont/issues/48) 148 | 149 | Support for `.svgtofontrc` and [more](https://github.com/jaywcjlove/auto-config-loader/blob/add7ae012f5c3903296fbf0ef06e3631e379c2cc/core/README.md?plain=1#L106-L135) configuration files. 150 | 151 | ```js 152 | { 153 | "fontName": "svgtofont", 154 | "css": true 155 | } 156 | ``` 157 | 158 | ```js 159 | /** 160 | * @type {import('svgtofont').SvgToFontOptions} 161 | */ 162 | export default { 163 | fontName: "iconfont", 164 | } 165 | ``` 166 | 167 | #### Using With Nodejs 168 | 169 | > [!NOTE] 170 | > This package `v5+` is now pure ESM. Please [read this](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). 171 | 172 | ```js 173 | import svgtofont from 'svgtofont'; 174 | import path from 'node:path'; 175 | 176 | svgtofont({ 177 | src: path.resolve(process.cwd(), 'icon'), // svg path, only searches one level, not recursive 178 | dist: path.resolve(process.cwd(), 'fonts'), // output path 179 | fontName: 'svgtofont', // font name 180 | css: true, // Create CSS files. 181 | }).then(() => { 182 | console.log('done!'); 183 | }); 184 | ``` 185 | 186 | Or 187 | 188 | ```js 189 | import svgtofont from 'svgtofont'; 190 | import path from 'node:path'; 191 | 192 | svgtofont({ 193 | src: path.resolve(process.cwd(), "icon"), // svg path, only searches one level, not recursive 194 | dist: path.resolve(process.cwd(), "fonts"), // output path 195 | styleTemplates: path.resolve(rootPath, "styles"), // file templates path (optional) 196 | fontName: "svgtofont", // font name 197 | css: true, // Create CSS files. 198 | startUnicode: 0xea01, // unicode start number 199 | svgicons2svgfont: { 200 | fontHeight: 1000, 201 | normalize: true 202 | }, 203 | // website = null, no demo html files 204 | website: { 205 | title: "svgtofont", 206 | // Must be a .svg format image. 207 | logo: path.resolve(process.cwd(), "svg", "git.svg"), 208 | version: pkg.version, 209 | meta: { 210 | description: "Converts SVG fonts to TTF/EOT/WOFF/WOFF2/SVG format.", 211 | keywords: "svgtofont,TTF,EOT,WOFF,WOFF2,SVG" 212 | }, 213 | description: ``, 214 | // Add a Github corner to your website 215 | // Like: https://github.com/uiwjs/react-github-corners 216 | corners: { 217 | url: 'https://github.com/jaywcjlove/svgtofont', 218 | width: 62, // default: 60 219 | height: 62, // default: 60 220 | bgColor: '#dc3545' // default: '#151513' 221 | }, 222 | links: [ 223 | { 224 | title: "GitHub", 225 | url: "https://github.com/jaywcjlove/svgtofont" 226 | }, 227 | { 228 | title: "Feedback", 229 | url: "https://github.com/jaywcjlove/svgtofont/issues" 230 | }, 231 | { 232 | title: "Font Class", 233 | url: "index.html" 234 | }, 235 | { 236 | title: "Unicode", 237 | url: "unicode.html" 238 | } 239 | ], 240 | footerInfo: `Licensed under MIT. (Yes it's free and open-sourced` 241 | } 242 | }).then(() => { 243 | console.log('done!'); 244 | });; 245 | ``` 246 | 247 | ## API 248 | 249 | ```js 250 | import { createSVG, createTTF, createEOT, createWOFF, createWOFF2, createSvgSymbol, copyTemplate, createHTML } from 'svgtofont/lib/utils'; 251 | 252 | const options = { ... }; 253 | 254 | async function createFont() { 255 | const unicodeObject = await createSVG(options); 256 | const ttf = await createTTF(options); // SVG Font => TTF 257 | await createEOT(options, ttf); // TTF => EOT 258 | await createWOFF(options, ttf); // TTF => WOFF 259 | await createWOFF2(options, ttf); // TTF => WOFF2 260 | await createSvgSymbol(options); // SVG Files => SVG Symbol 261 | } 262 | ``` 263 | 264 | ## options 265 | 266 | > svgtofont(options) 267 | 268 | ### config 269 | 270 | > Type: `config?: AutoConfOption` 271 | 272 | By default, settings are automatically loaded from `.svgtofontrc` and `package.json`. You can add configuration to `package.json`. [#48](https://github.com/jaywcjlove/svgtofont/issues/48) 273 | 274 | Support for `.svgtofontrc` and [more](https://github.com/jaywcjlove/auto-config-loader/blob/add7ae012f5c3903296fbf0ef06e3631e379c2cc/core/README.md?plain=1#L106-L135) configuration files. 275 | 276 | ### log 277 | 278 | > Type: `Boolean` 279 | 280 | A value of `false` disables logging 281 | 282 | ### logger 283 | 284 | > Type: `(msg) => void` 285 | 286 | log callback function 287 | 288 | ### dist 289 | 290 | > Type: `String` 291 | > Default value: ~~`dist`~~ => `fonts` 292 | 293 | The output directory. 294 | 295 | ### outSVGReact 296 | 297 | > Type: `Boolean` 298 | > Default value: `false` 299 | 300 | Output `./dist/react/`, SVG generates `react` components. 301 | 302 | ```js 303 | git/git.svg 304 | 305 | // ↓↓↓↓↓↓↓↓↓↓ 306 | 307 | import React from 'react'; 308 | export const Git = props => ( 309 | 310 | ); 311 | ``` 312 | 313 | ### outSVGReactNative 314 | 315 | > Type: `Boolean` 316 | > Default value: `false` 317 | 318 | Output `./dist/reactNative/`, SVG generates `reactNative` components. 319 | 320 | ```js 321 | import { Text } from 'react-native'; 322 | 323 | const icons = { "Git": "__GitUnicodeChar__", "Adobe": "__AdobeUnicodeChar__" }; 324 | 325 | export const RangeIconFont = props => { 326 | const { name, ...rest } = props; 327 | return ( 328 | {icons[name]} 329 | ); 330 | }; 331 | 332 | ``` 333 | 334 | ### outSVGVue 335 | 336 | > Type: `Boolean` 337 | > Default value: `false` 338 | 339 | Output `./dist/vue/`, SVG generates `vue` components. 340 | 341 | ```js 342 | git/git.svg 343 | 344 | // ↓↓↓↓↓↓↓↓↓↓ 345 | 346 | import { defineComponent, h } from 'vue'; 347 | 348 | export const Git = defineComponent({ 349 | name: 'Git', 350 | props: { 351 | class: { 352 | type: String, 353 | default: '' 354 | } 355 | }, 356 | setup(props, { attrs }) { 357 | return () => h( 358 | 'svg', 359 | { 360 | viewBox: '0 0 20 20', 361 | width: undefined, 362 | height: undefined, 363 | class: `svgtofont ${props.class}`, 364 | ...attrs 365 | }, 366 | [] 367 | ); 368 | } 369 | }); 370 | 371 | ``` 372 | 373 | ### outSVGPath 374 | 375 | > Type: `Boolean` 376 | > Default value: `false` 377 | 378 | Output `./dist/svgtofont.json`, The content is as follows: 379 | 380 | ```js 381 | { 382 | "adobe": ["M14.868 3H23v19L14.868 3zM1 3h8.138L1 22V3zm.182 11.997H13.79l-1.551-3.82H8.447z...."], 383 | "git": ["M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1..."], 384 | "stylelint": ["M129.74 243.648c28-100.109 27.188-100.5.816c2.65..."] 385 | } 386 | ``` 387 | 388 | Or you can generate the file separately: 389 | 390 | ```js 391 | const { generateIconsSource } = require('svgtofont/src/generate'); 392 | const path = require('path'); 393 | 394 | async function generate () { 395 | const outPath = await generateIconsSource({ 396 | src: path.resolve(process.cwd(), 'svg'), 397 | dist: path.resolve(process.cwd(), 'dist'), 398 | fontName: 'svgtofont', 399 | }); 400 | } 401 | 402 | generate(); 403 | ``` 404 | 405 | ### generateInfoData 406 | 407 | > Type: `Boolean` 408 | > Default value: `false` 409 | 410 | Output `./dist/info.json`, The content is as follows: 411 | 412 | ```js 413 | { 414 | "adobe": { 415 | "encodedCode": "\\ea01", 416 | "prefix": "svgtofont", 417 | "className": "svgtofont-adobe", 418 | "unicode": "" 419 | }, 420 | ... 421 | } 422 | ``` 423 | 424 | ### src 425 | 426 | > Type: `String` 427 | > Default value: `svg` 428 | 429 | output path 430 | 431 | ### emptyDist 432 | 433 | > Type: `String` 434 | > Default value: `false` 435 | 436 | Clear output directory contents 437 | 438 | ### fontName 439 | 440 | > Type: `String` 441 | > Default value: `iconfont` 442 | 443 | The font family name you want. 444 | 445 | ### styleTemplates 446 | 447 | > Type: `String` 448 | > Default value: `undefined` 449 | 450 | The path of the templates, see `src/styles` or `test/templates/styles` to get reference about 451 | how to create a template, file names can have the extension .template, like a `filename.scss.template` 452 | 453 | ### startUnicode 454 | 455 | > Type: `Number` 456 | > Default value: `0xea01` 457 | 458 | unicode start number 459 | 460 | ### getIconUnicode 461 | 462 | Get Icon Unicode 463 | 464 | ```ts 465 | getIconUnicode?: (name: string, unicode: string, startUnicode: number) 466 | => [string, number]; 467 | ``` 468 | 469 | ### useNameAsUnicode 470 | 471 | > Type: `Boolean` 472 | > Default value: `false` 473 | 474 | should the name(file name) be used as unicode? this switch allows for the support of ligatures. 475 | 476 | let's say you have an svg with a file name of `add` and you want to use ligatures for it. you would set up your processing as mentioned above and turn on this switch. 477 | ```js 478 | { 479 | ... 480 | useNameAsUnicode: true 481 | } 482 | ``` 483 | while processing, instead of using a single sequential char for the unicode, it uses the file name. using the file name as the unicode allows the following code to work as expected. 484 | ```css 485 | .icons { 486 | font-family: 'your-font-icon-name' !important; 487 | font-size: 16px; 488 | font-style: normal; 489 | -webkit-font-smoothing: antialiased; 490 | -moz-osx-font-smoothing: grayscale; 491 | } 492 | ``` 493 | ```html 494 | add 495 | ``` 496 | as you add more svgs and process them into your font you would just use the same pattern. 497 | ```html 498 | add 499 | remove 500 | edit 501 | ``` 502 | 503 | ### addLigatures 504 | 505 | > Type: `Boolean` 506 | > Default value: `false` 507 | 508 | adds possibility to use name (file name) in addition to codepoints. adds support of ligatures. 509 | 510 | let's say you have some svgs and you want to use codepoints but for some of them for example with a file name of `add` you want to use ligatures for it. this option only adds ligatures and still allows for using codepoints as usual. this is in contrary to useNameAsUnicode which basically removes support for codepoints in favour of ligatures. 511 | ```js 512 | { 513 | ... 514 | addLigatures: true 515 | } 516 | ``` 517 | 518 | ### useCSSVars 519 | 520 | > Type: `Boolean` 521 | > Default value: `false` 522 | 523 | consoles whenever {{ cssString }} template outputs unicode characters or css vars 524 | 525 | ### classNamePrefix 526 | 527 | > Type: `String` 528 | > Default value: font name 529 | 530 | Create font class name prefix, default value font name. 531 | 532 | ### css 533 | 534 | > Type: `Boolean|CSSOptions` 535 | > Default value: `false` 536 | 537 | Create CSS/LESS files, default `true`. 538 | 539 | ```ts 540 | type CSSOptions = { 541 | /** 542 | * Output the css file to the specified directory 543 | */ 544 | output?: string; 545 | /** 546 | * Which files are exported. 547 | */ 548 | include?: RegExp; 549 | /** 550 | * Setting font size. 551 | */ 552 | fontSize?: string | boolean; 553 | /** 554 | * Set the path in the css file 555 | * https://github.com/jaywcjlove/svgtofont/issues/48#issuecomment-739547189 556 | */ 557 | cssPath?: string; 558 | /** 559 | * Set file name 560 | * https://github.com/jaywcjlove/svgtofont/issues/48#issuecomment-739547189 561 | */ 562 | fileName?: string; 563 | /** 564 | * Ad hoc template variables. 565 | */ 566 | templateVars?: Record; 567 | /** 568 | * When including CSS files in a CSS file, 569 | * you can add a timestamp parameter or custom text to the file path to prevent browser caching issues and ensure style updates are applied. @default true 570 | * @example `path/to/iconfont.css?t=1612345678` 571 | */ 572 | hasTimestamp?: boolean | string; 573 | } 574 | ``` 575 | 576 | ### svgicons2svgfont 577 | 578 | This is the setting for [svgicons2svgfont](https://github.com/nfroidure/svgicons2svgfont/tree/dd713bea4f97afa59f7dba6a21ff7f22db565bcf#api) 579 | 580 | 581 | #### svgicons2svgfont.fontName 582 | 583 | > Type: `String` 584 | > Default value: `'iconfont'` 585 | 586 | The font family name you want. 587 | 588 | #### svgicons2svgfont.fontId 589 | 590 | > Type: `String` 591 | > Default value: the options.fontName value 592 | 593 | The font id you want. 594 | 595 | #### svgicons2svgfont.fontStyle 596 | 597 | > Type: `String` 598 | > Default value: `''` 599 | 600 | The font style you want. 601 | 602 | #### svgicons2svgfont.fontWeight 603 | 604 | > Type: `String` 605 | > Default value: `''` 606 | 607 | The font weight you want. 608 | 609 | #### svgicons2svgfont.fixedWidth 610 | 611 | > Type: `Boolean` 612 | > Default value: `false` 613 | 614 | Creates a monospace font of the width of the largest input icon. 615 | 616 | #### svgicons2svgfont.centerHorizontally 617 | 618 | > Type: `Boolean` 619 | > Default value: `false` 620 | 621 | Calculate the bounds of a glyph and center it horizontally. 622 | 623 | #### svgicons2svgfont.normalize 624 | 625 | > Type: `Boolean` 626 | > Default value: `false` 627 | 628 | Normalize icons by scaling them to the height of the highest icon. 629 | 630 | #### svgicons2svgfont.fontHeight 631 | 632 | > Type: `Number` 633 | > Default value: `MAX(icons.height)` 634 | 635 | The outputted font height (defaults to the height of the highest input icon). 636 | 637 | #### svgicons2svgfont.round 638 | 639 | > Type: `Number` 640 | > Default value: `10e12` 641 | 642 | Setup SVG path rounding. 643 | 644 | #### svgicons2svgfont.descent 645 | 646 | > Type: `Number` 647 | > Default value: `0` 648 | 649 | The font descent. It is useful to fix the font baseline yourself. 650 | 651 | **Warning:** The descent is a positive value! 652 | 653 | #### svgicons2svgfont.ascent 654 | 655 | > Type: `Number` 656 | > Default value: `fontHeight - descent` 657 | 658 | The font ascent. Use this options only if you know what you're doing. A suitable 659 | value for this is computed for you. 660 | 661 | #### svgicons2svgfont.metadata 662 | 663 | > Type: `String` 664 | > Default value: `undefined` 665 | 666 | The font [metadata](http://www.w3.org/TR/SVG/metadata.html). You can set any 667 | character data in but it is the be suited place for a copyright mention. 668 | 669 | #### svgicons2svgfont.log 670 | 671 | > Type: `Function` 672 | > Default value: `console.log` 673 | 674 | Allows you to provide your own logging function. Set to `function(){}` to 675 | disable logging. 676 | 677 | ### svgoOptions 678 | 679 | > Type: `OptimizeOptions` 680 | > Default value: `undefined` 681 | 682 | Some options can be configured with `svgoOptions` though it. [svgo](https://github.com/svg/svgo#configuration) 683 | 684 | ### svg2ttf 685 | 686 | This is the setting for [svg2ttf](https://github.com/fontello/svg2ttf/tree/c33a126920f46b030e8ce960cc7a0e38a6946bbc#svg2ttfsvgfontstring-options---buf) 687 | 688 | #### svg2ttf.copyright 689 | 690 | > Type: `String` 691 | 692 | copyright string 693 | 694 | #### svg2ttf.ts 695 | 696 | > Type: `String` 697 | 698 | Unix timestamp (in seconds) to override creation time 699 | 700 | #### svg2ttf.version 701 | 702 | > Type: `Number` 703 | 704 | font version string, can be Version `x.y` or `x.y`. 705 | 706 | ### website 707 | 708 | Define preview web content. Example: 709 | 710 | ```js 711 | { 712 | ... 713 | // website = null, no demo html files 714 | website: { 715 | title: "svgtofont", 716 | logo: path.resolve(process.cwd(), "svg", "git.svg"), 717 | version: pkg.version, 718 | meta: { 719 | description: "Converts SVG fonts to TTF/EOT/WOFF/WOFF2/SVG format.", 720 | keywords: "svgtofont,TTF,EOT,WOFF,WOFF2,SVG", 721 | favicon: "./favicon.png" 722 | }, 723 | // Add a Github corner to your website 724 | // Like: https://github.com/uiwjs/react-github-corners 725 | corners: { 726 | url: 'https://github.com/jaywcjlove/svgtofont', 727 | width: 62, // default: 60 728 | height: 62, // default: 60 729 | bgColor: '#dc3545' // default: '#151513' 730 | }, 731 | links: [ 732 | { 733 | title: "GitHub", 734 | url: "https://github.com/jaywcjlove/svgtofont" 735 | }, 736 | { 737 | title: "Feedback", 738 | url: "https://github.com/jaywcjlove/svgtofont/issues" 739 | }, 740 | { 741 | title: "Font Class", 742 | url: "index.html" 743 | }, 744 | { 745 | title: "Unicode", 746 | url: "unicode.html" 747 | } 748 | ] 749 | } 750 | } 751 | ``` 752 | 753 | #### website.template 754 | 755 | > Type: `String` 756 | > Default value: [index.njk](src/website/index.njk) 757 | 758 | Custom template can customize parameters. You can define your own template based on the [default template](src/website/index.njk). 759 | 760 | ```js 761 | { 762 | website: { 763 | template: path.join(process.cwd(), "my-template.njk") 764 | } 765 | } 766 | ``` 767 | #### website.index 768 | 769 | > Type: `String` 770 | > Default value: `font-class`, Enum{`font-class`, `unicode`, `symbol`} 771 | 772 | Set default home page. 773 | 774 | ## Font Usage 775 | 776 | Suppose the font name is defined as `svgtofont`, The default home page is `unicode`, Will generate: 777 | 778 | ```bash 779 | font-class.html 780 | index.html 781 | svgtofont.css 782 | svgtofont.eot 783 | svgtofont.json 784 | svgtofont.less 785 | svgtofont.module.less 786 | svgtofont.scss 787 | svgtofont.styl 788 | svgtofont.svg 789 | svgtofont.symbol.svg 790 | svgtofont.ttf 791 | svgtofont.woff 792 | svgtofont.woff2 793 | symbol.html 794 | ``` 795 | 796 | Preview demo `font-class.html`, `symbol.html` and `index.html`. Automatically generated style `svgtofont.css` and `svgtofont.less`. 797 | 798 | ### symbol svg 799 | 800 | ```xml 801 | 804 | ``` 805 | 806 | ### Unicode 807 | 808 | ```html 809 | 819 | 820 | ``` 821 | 822 | ### Class Name 823 | 824 | Support for `.less` and `.css` styles references. 825 | 826 | ```html 827 | 828 | 829 | ``` 830 | 831 | ### Using With React 832 | 833 | Icons are used as components. `v3.16.7+` support. 834 | 835 | ```jsx 836 | import { Adobe, Alipay } from '@uiw/icons'; 837 | 838 | 839 | 840 | ``` 841 | 842 | #### In the project created by [create-react-app](https://github.com/facebook/create-react-app) 843 | 844 | ```jsx 845 | import logo from './logo.svg'; 846 | 847 | 848 | ``` 849 | 850 | ```jsx 851 | import { ReactComponent as ComLogo } from './logo.svg'; 852 | 853 | 854 | ``` 855 | 856 | #### In the project created by [webpack](https://github.com/webpack/webpack) 857 | 858 | ```bash 859 | yarn add babel-plugin-named-asset-import 860 | yarn add @svgr/webpack 861 | ``` 862 | 863 | ```js 864 | // webpack.config.js 865 | [ 866 | require.resolve('babel-plugin-named-asset-import'), 867 | { 868 | loaderMap: { 869 | svg: { 870 | ReactComponent: '@svgr/webpack?-svgo,+ref![path]', 871 | }, 872 | }, 873 | }, 874 | ], 875 | ``` 876 | 877 | ```jsx 878 | import { ReactComponent as ComLogo } from './logo.svg'; 879 | 880 | 881 | ``` 882 | 883 | ### Using With ReactNative 884 | 885 | A unique component named after the font name is generated. 886 | 887 | Props are TextProps and are used as inline style. 888 | 889 | In addition, the iconName prop is mandatory and refers to svg names written in camelCase 890 | 891 | ```jsx 892 | SvgToFont.jsx 893 | // ↓↓↓↓↓↓↓↓↓↓ 894 | 895 | import { SvgToFont } from './SvgToFont'; 896 | 897 | 898 | ``` 899 | ```ts 900 | SvgToFont.d.ts 901 | // ↓↓↓↓↓↓↓↓↓↓ 902 | 903 | import { TextStyle } from 'react-native'; 904 | 905 | export type SvgToFontIconNames = 'git'| 'adobe'| 'demo' | 'left' | 'styleInline' 906 | 907 | export interface SvgToFontProps extends Omit { 908 | iconName: SvgToFontIconNames 909 | } 910 | 911 | export declare const SvgToFont: (props: SvgToFontProps) => JSX.Element; 912 | ``` 913 | 914 | ### Using with Vue 915 | 916 | Icons are used as components. `v3+` support. 917 | 918 | ```vue 919 | 922 | 923 | 927 | ``` 928 | 929 | ## Contributors 930 | 931 | As always, thanks to our amazing contributors! 932 | 933 | 934 | 935 | 936 | 937 | Made with [contributors](https://github.com/jaywcjlove/github-action-contributors). 938 | 939 | ## License 940 | 941 | Licensed under the [MIT License](https://opensource.org/licenses/MIT). 942 | --------------------------------------------------------------------------------