├── .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 |
--------------------------------------------------------------------------------
/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 |
6 |
--------------------------------------------------------------------------------
/examples/templates/svg/adobe.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/examples/example#211/svg/adobe.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/examples/example#233/svg/adobe.svg:
--------------------------------------------------------------------------------
1 |
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 |
4 |
--------------------------------------------------------------------------------
/examples/example/svg/git.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/examples/example#211/svg/git.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/examples/example#233/svg/git.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/examples/templates/svg/git.svg:
--------------------------------------------------------------------------------
1 |
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 |
4 |
--------------------------------------------------------------------------------
/examples/templates/svg/stylelint.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/examples/example#211/svg/stylelint.svg:
--------------------------------------------------------------------------------
1 |
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 | [](https://jaywcjlove.github.io/#/sponsor) [](https://uiwjs.github.io/npm-unpkg/#/pkg/svgtofont@${{steps.create_tag.outputs.versionNumber}}/file/README.md) [](https://bundlephobia.com/result?p=svgtofont@${{steps.create_tag.outputs.versionNumber}}) [](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 |
143 |
144 |
145 | {{ _IconHtml|safe }}
146 |
147 |
148 |
149 | {% for linkItem in links|default([]) %}
150 | {{ linkItem.title }}{% if not loop.last %} · {% endif %}
151 | {% endfor %}
152 |
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 |
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 |

6 |

7 |

8 |

9 |

10 |

11 |

12 |

13 |

14 |

15 |

16 |

17 |

18 |

19 |

20 |

21 |

22 |

23 |

24 |

25 |

26 |

27 |

28 |

29 |

30 |

31 |

32 |

33 |

34 |

35 |
36 |
37 |
38 | [Free Font](https://github.com/jaywcjlove/free-font)
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
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 |
924 |
925 |
926 |
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 |
--------------------------------------------------------------------------------