From d953ab8e07bfb69403fba6ef202b7967475b6aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:44:55 +0100 Subject: [PATCH] Enhance webfont build process to support additional stroke weights and corresponding styles (#1313) --- .../icons-webfont/.build/build-outline.mjs | 196 +++++++++--------- .../icons-webfont/.build/build-webfont.mjs | 92 ++++---- packages/icons-webfont/package.json | 6 +- 3 files changed, 155 insertions(+), 139 deletions(-) diff --git a/packages/icons-webfont/.build/build-outline.mjs b/packages/icons-webfont/.build/build-outline.mjs index 972a1f136..b0b01ca59 100644 --- a/packages/icons-webfont/.build/build-outline.mjs +++ b/packages/icons-webfont/.build/build-outline.mjs @@ -8,123 +8,131 @@ import { execSync } from 'child_process' const DIR = getPackageDir('icons-webfont') +const strokes = { + 200: 1, + 300: 1.5, + 400: 2, +} + const buildOutline = async () => { let filesList = {} const icons = getAllIcons(true) const compileOptions = getCompileOptions() - await asyncForEach(Object.entries(icons), async ([type, icons]) => { - fs.mkdirSync(resolve(DIR, `icons-outlined/${type}`), { recursive: true }) - filesList[type] = [] + for (const strokeName in strokes) { + const stroke = strokes[strokeName] - await asyncForEach(icons, async function ({ name, content, unicode }) { - console.log(type, name); + await asyncForEach(Object.entries(icons), async ([type, icons]) => { + fs.mkdirSync(resolve(DIR, `icons-outlined/${strokeName}/${type}`), { recursive: true }) + filesList[type] = [] - if (compileOptions.includeIcons.length === 0 || compileOptions.includeIcons.indexOf(name) >= 0) { + await asyncForEach(icons, async function ({ name, content, unicode }) { + console.log(type, name); - if (unicode) { - console.log('Stroke for:', name, unicode) + if (compileOptions.includeIcons.length === 0 || compileOptions.includeIcons.indexOf(name) >= 0) { - let filename = `${name}.svg` if (unicode) { - filename = `u${unicode.toUpperCase()}-${name}.svg` - } + console.log(`Stroke ${strokeName} for:`, name, unicode) - filesList[type].push(filename) - - content = content - .replace('width="24"', 'width="1000"') - .replace('height="24"', 'height="1000"') - - if (compileOptions.strokeWidth) { - content = content - .replace('stroke-width="2"', `stroke-width="${compileOptions.strokeWidth}"`) - } - - const cachedFilename = `u${unicode.toUpperCase()}-${name}.svg`; - - if (unicode && fs.existsSync(resolve(DIR, `icons-outlined/${type}/${cachedFilename}`))) { - // Get content - let cachedContent = fs.readFileSync(resolve(DIR, `icons-outlined/${type}/${cachedFilename}`), 'utf-8') - - // Get hash - let cachedHash = ''; - cachedContent = cachedContent.replace(//, function (m, hash) { - cachedHash = hash; - return ''; - }) - - // Check hash - if (crypto.createHash('sha1').update(cachedContent).digest("hex") === cachedHash) { - console.log('Cached stroke for:', name, unicode) - return true; + let filename = `${name}.svg` + if (unicode) { + filename = `u${unicode.toUpperCase()}-${name}.svg` } + + filesList[type].push(filename) + + content = content + .replace('width="24"', 'width="1000"') + .replace('height="24"', 'height="1000"') + + content = content + .replace('stroke-width="2"', `stroke-width="${stroke}"`) + + const cachedFilename = `u${unicode.toUpperCase()}-${name}.svg`; + + if (unicode && fs.existsSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${cachedFilename}`))) { + // Get content + let cachedContent = fs.readFileSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${cachedFilename}`), 'utf-8') + + // Get hash + let cachedHash = ''; + cachedContent = cachedContent.replace(//, function (m, hash) { + cachedHash = hash; + return ''; + }) + + // Check hash + if (crypto.createHash('sha1').update(cachedContent).digest("hex") === cachedHash) { + console.log('Cached stroke for:', name, unicode) + return true; + } + } + + await outlineStroke(content, { + optCurve: true, + steps: 4, + round: 0, + centerHorizontally: true, + fixedWidth: false, + color: 'black' + }).then(outlined => { + // Save file + fs.writeFileSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`), outlined, 'utf-8') + + // Fix outline + execSync(`fontforge -lang=py -script .build/fix-outline.py icons-outlined/${strokeName}/${type}/${filename}`).toString() + execSync(`svgo icons-outlined/${strokeName}/${type}/${filename}`).toString() + + // Add hash + const fixedFileContent = fs + .readFileSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`), 'utf-8') + .replace(/\n/g, ' ') + .trim(), + hashString = `` + + // Save file + fs.writeFileSync( + resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`), + fixedFileContent + hashString, + 'utf-8' + ) + }).catch(error => console.log(error)) } - - await outlineStroke(content, { - optCurve: true, - steps: 4, - round: 0, - centerHorizontally: true, - fixedWidth: false, - color: 'black' - }).then(outlined => { - // Save file - fs.writeFileSync(resolve(DIR, `icons-outlined/${type}/${filename}`), outlined, 'utf-8') - - // Fix outline - execSync(`fontforge -lang=py -script .build/fix-outline.py icons-outlined/${type}/${filename}`).toString() - execSync(`svgo icons-outlined/${type}/${filename}`).toString() - - // Add hash - const fixedFileContent = fs - .readFileSync(resolve(DIR, `icons-outlined/${type}/${filename}`), 'utf-8') - .replace(/\n/g, ' ') - .trim(), - hashString = `` - - // Save file - fs.writeFileSync( - resolve(DIR, `icons-outlined/${type}/${filename}`), - fixedFileContent + hashString, - 'utf-8' - ) - }).catch(error => console.log(error)) } - } + }) }) - }) - // Remove old files - await asyncForEach(Object.entries(icons), async ([type, icons]) => { - const existedFiles = (await glob(resolve(DIR, `icons-outlined/${type}/*.svg`))).map(file => basename(file)) - existedFiles.forEach(file => { - if (filesList[type].indexOf(file) === -1) { - console.log('Remove:', file) - fs.unlinkSync(resolve(DIR, `icons-outlined/${type}/${file}`)) - } + // Remove old files + await asyncForEach(Object.entries(icons), async ([type, icons]) => { + const existedFiles = (await glob(resolve(DIR, `icons-outlined/${strokeName}/${type}/*.svg`))).map(file => basename(file)) + existedFiles.forEach(file => { + if (filesList[type].indexOf(file) === -1) { + console.log('Remove:', file) + fs.unlinkSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${file}`)) + } + }) }) - }) - // Copy icons from firs to all directory - await asyncForEach(Object.entries(icons), async ([type, icons]) => { - fs.mkdirSync(resolve(DIR, `icons-outlined/all`), { recursive: true }) + // Copy icons from firs to all directory + await asyncForEach(Object.entries(icons), async ([type, icons]) => { + fs.mkdirSync(resolve(DIR, `icons-outlined/${strokeName}/all`), { recursive: true }) - await asyncForEach(icons, async function ({ name, unicode }) { - const iconName = `u${unicode.toUpperCase()}-${name}` + await asyncForEach(icons, async function ({ name, unicode }) { + const iconName = `u${unicode.toUpperCase()}-${name}` - if (fs.existsSync(resolve(DIR, `icons-outlined/${type}/${iconName}.svg`))) { - // Copy file - console.log(`Copy ${iconName} to all directory`) + if (fs.existsSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${iconName}.svg`))) { + // Copy file + console.log(`Copy ${iconName} to all directory`) - fs.copyFileSync( - resolve(DIR, `icons-outlined/${type}/${iconName}.svg`), - resolve(DIR, `icons-outlined/all/${iconName}${type !== 'outline' ? `-${type}` : ''}.svg`) - ) - } + fs.copyFileSync( + resolve(DIR, `icons-outlined/${strokeName}/${type}/${iconName}.svg`), + resolve(DIR, `icons-outlined/${strokeName}/all/${iconName}${type !== 'outline' ? `-${type}` : ''}.svg`) + ) + } + }) }) - }) + } console.log('Done') } diff --git a/packages/icons-webfont/.build/build-webfont.mjs b/packages/icons-webfont/.build/build-webfont.mjs index f225bdf45..6d3e0e1a5 100644 --- a/packages/icons-webfont/.build/build-webfont.mjs +++ b/packages/icons-webfont/.build/build-webfont.mjs @@ -8,6 +8,12 @@ const p = getPackageJson() const DIR = getPackageDir('icons-webfont') const fontHeight = 1000 +const strokes = { + 200: 1, + 300: 1.5, + 400: 2, +} + const aliases = getAliases(true) fs.mkdirSync(`${DIR}/dist/fonts`, { recursive: true }) @@ -26,50 +32,52 @@ const getAlliasesFlat = () => { return allAliases } -asyncForEach(types, async type => { - console.log(`Building webfont for ${type} icons`) +for (const strokeName in strokes) { + asyncForEach(types, async type => { + console.log(`Building ${strokeName} webfont for ${type} icons`) - await webfont({ - files: `icons-outlined/${type}/*.svg`, - fontName: 'tabler-icons', - prependUnicode: true, - formats, - normalize: true, - fontHeight, - descent: 100, - ascent: 900, - fixedWidth: false - }) - .then((result) => { - formats.forEach(format => { - fs.writeFileSync(`${DIR}/dist/fonts/tabler-icons${type !== 'all' ? `-${type}` : ''}.${format}`, result[format]) - }) - - const glyphs = result.glyphsData - .map(icon => icon.metadata) - .sort(function (a, b) { - return ('' + a.name).localeCompare(b.name) + await webfont({ + files: `icons-outlined/${strokeName}/${type}/*.svg`, + fontName: 'tabler-icons', + prependUnicode: true, + formats, + normalize: true, + fontHeight, + descent: 100, + ascent: 900, + fixedWidth: false + }) + .then((result) => { + formats.forEach(format => { + fs.writeFileSync(`${DIR}/dist/fonts/tabler-icons${strokeName !== "400" ? `-${strokeName}` : ''}${type !== 'all' ? `-${type}` : ''}.${format}`, result[format]) }) - const options = { - name: `Tabler Icons${type !== 'all' ? ` ${toPascalCase(type)}` : ''}`, - fileName: `tabler-icons${type !== 'all' ? `-${type}` : ''}`, - glyphs, - v: p.version, - aliases: (type === 'all' ? getAlliasesFlat() : aliases[type]) || {} - } + const glyphs = result.glyphsData + .map(icon => icon.metadata) + .sort(function (a, b) { + return ('' + a.name).localeCompare(b.name) + }) - //scss - const compiled = template(fs.readFileSync(`${DIR}/.build/iconfont.scss`).toString()) - const resultSCSS = compiled(options) - fs.writeFileSync(`${DIR}/dist/tabler-icons${type !== 'all' ? `-${type}` : ''}.scss`, resultSCSS) + const options = { + name: `Tabler Icons ${strokeName}${type !== 'all' ? ` ${toPascalCase(type)}` : ''}`, + fileName: `tabler-icons${strokeName !== "400" ? `-${strokeName}` : ''}${type !== 'all' ? `-${type}` : ''}`, + glyphs, + v: p.version, + aliases: (type === 'all' ? getAlliasesFlat() : aliases[type]) || {} + } - //html - const compiledHtml = template(fs.readFileSync(`${DIR}/.build/iconfont.html`).toString()) - const resultHtml = compiledHtml(options) - fs.writeFileSync(`${DIR}/dist/tabler-icons${type !== 'all' ? `-${type}` : ''}.html`, resultHtml) - }) - .catch((error) => { - throw error; - }); -}) + //scss + const compiled = template(fs.readFileSync(`${DIR}/.build/iconfont.scss`).toString()) + const resultSCSS = compiled(options) + fs.writeFileSync(`${DIR}/dist/tabler-icons${strokeName !== "400" ? `-${strokeName}` : ''}${type !== 'all' ? `-${type}` : ''}.scss`, resultSCSS) + + //html + const compiledHtml = template(fs.readFileSync(`${DIR}/.build/iconfont.html`).toString()) + const resultHtml = compiledHtml(options) + fs.writeFileSync(`${DIR}/dist/tabler-icons${strokeName !== "400" ? `-${strokeName}` : ''}${type !== 'all' ? `-${type}` : ''}.html`, resultHtml) + }) + .catch((error) => { + throw error; + }); + }) +} diff --git a/packages/icons-webfont/package.json b/packages/icons-webfont/package.json index e50f581be..62b3d395e 100644 --- a/packages/icons-webfont/package.json +++ b/packages/icons-webfont/package.json @@ -21,9 +21,9 @@ "build:outline": "node .build/build-outline.mjs", "build:webfont": "rm -fd dist/fonts/* && node .build/build-webfont.mjs", "build:css": "pnpm run build:css:outline && pnpm run build:css:filled && pnpm run build:css:all", - "build:css:filled": "sass dist/tabler-icons-filled.scss dist/tabler-icons-filled.css --style expanded && sass dist/tabler-icons-filled.scss dist/tabler-icons-filled.min.css --style compressed", - "build:css:outline": "sass dist/tabler-icons-outline.scss dist/tabler-icons-outline.css --style expanded && sass dist/tabler-icons-outline.scss dist/tabler-icons-outline.min.css --style compressed", - "build:css:all": "sass dist/tabler-icons.scss dist/tabler-icons.css --style expanded && sass dist/tabler-icons.scss dist/tabler-icons.min.css --style compressed", + "build:css:filled": "sass dist/tabler-icons-filled.scss dist/tabler-icons-filled.css --style expanded && sass dist/tabler-icons-filled.scss dist/tabler-icons-filled.min.css --style compressed && sass dist/tabler-icons-200-filled.scss dist/tabler-icons-200-filled.css --style expanded && sass dist/tabler-icons-200-filled.scss dist/tabler-icons-200-filled.min.css --style compressed && sass dist/tabler-icons-300-filled.scss dist/tabler-icons-300-filled.css --style expanded && sass dist/tabler-icons-300-filled.scss dist/tabler-icons-300-filled.min.css --style compressed", + "build:css:outline": "sass dist/tabler-icons-outline.scss dist/tabler-icons-outline.css --style expanded && sass dist/tabler-icons-outline.scss dist/tabler-icons-outline.min.css --style compressed && sass dist/tabler-icons-200-outline.scss dist/tabler-icons-200-outline.css --style expanded && sass dist/tabler-icons-200-outline.scss dist/tabler-icons-200-outline.min.css --style compressed && sass dist/tabler-icons-300-outline.scss dist/tabler-icons-300-outline.css --style expanded && sass dist/tabler-icons-300-outline.scss dist/tabler-icons-300-outline.min.css --style compressed", + "build:css:all": "sass dist/tabler-icons.scss dist/tabler-icons.css --style expanded && sass dist/tabler-icons.scss dist/tabler-icons.min.css --style compressed && sass dist/tabler-icons-200.scss dist/tabler-icons-200.css --style expanded && sass dist/tabler-icons-200.scss dist/tabler-icons-200.min.css --style compressed && sass dist/tabler-icons-300.scss dist/tabler-icons-300.css --style expanded && sass dist/tabler-icons-300.scss dist/tabler-icons-300.min.css --style compressed", "clean": "rm -rf dist && rm -rf ./icons-outlined", "copy": "pnpm run copy:license", "copy:license": "cp ../../LICENSE ./LICENSE"