Refactor build-outline.mjs to optimize SVG processing and remove fix-outline.py.
This commit is contained in:
parent
4990aeb956
commit
670958d52c
|
|
@ -4,7 +4,8 @@ import fs from 'fs'
|
|||
import { resolve, basename } from 'path'
|
||||
import crypto from 'crypto'
|
||||
import { glob } from 'glob'
|
||||
import { execSync } from 'child_process'
|
||||
import { optimize } from 'svgo'
|
||||
import { fixOutline } from './fix-outline.mjs'
|
||||
|
||||
const DIR = getPackageDir('icons-webfont')
|
||||
|
||||
|
|
@ -75,24 +76,20 @@ const buildOutline = async () => {
|
|||
fixedWidth: false,
|
||||
color: 'black'
|
||||
}).then(outlined => {
|
||||
// Save file
|
||||
fs.writeFileSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`), outlined, 'utf-8')
|
||||
// Fix outline direction (using JS instead of fontforge)
|
||||
const fixed = fixOutline(outlined)
|
||||
|
||||
// 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()
|
||||
// Optimize with svgo (in memory, no subprocess)
|
||||
const optimized = optimize(fixed, { multipass: true }).data
|
||||
|
||||
// Add hash
|
||||
const fixedFileContent = fs
|
||||
.readFileSync(resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`), 'utf-8')
|
||||
.replace(/\n/g, ' ')
|
||||
.trim(),
|
||||
hashString = `<!--!cache:${crypto.createHash('sha1').update(fixedFileContent).digest("hex")}-->`
|
||||
// Prepare final content with hash
|
||||
const finalContent = optimized.replace(/\n/g, ' ').trim()
|
||||
const hashString = `<!--!cache:${crypto.createHash('sha1').update(finalContent).digest("hex")}-->`
|
||||
|
||||
// Save file
|
||||
// Save file (single write instead of 3 file operations)
|
||||
fs.writeFileSync(
|
||||
resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`),
|
||||
fixedFileContent + hashString,
|
||||
finalContent + hashString,
|
||||
'utf-8'
|
||||
)
|
||||
}).catch(error => console.log(error))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
import fs from 'fs'
|
||||
import SVGPathCommander from 'svg-path-commander'
|
||||
|
||||
/**
|
||||
* Fix SVG outline directions - replacement for fontforge fix-outline.py
|
||||
* Operations:
|
||||
* 1. Round coordinates
|
||||
* 2. Correct path direction (outer paths counterclockwise, inner paths clockwise)
|
||||
*/
|
||||
export function fixOutline(svgContent) {
|
||||
// Extract all path elements
|
||||
const pathRegex = /<path[^>]*\sd="([^"]+)"[^>]*>/g
|
||||
|
||||
let result = svgContent.replace(pathRegex, (match, pathData) => {
|
||||
try {
|
||||
// Round coordinates to integers (like fontforge's round()) and optimize
|
||||
const commander = new SVGPathCommander(pathData, { round: 0 })
|
||||
const optimized = commander.optimize().toString()
|
||||
|
||||
// Check and correct direction
|
||||
// For font glyphs: outer paths should be counterclockwise
|
||||
const segments = new SVGPathCommander(optimized).segments
|
||||
const isClockwise = getPathDirection(segments)
|
||||
|
||||
let finalPath = optimized
|
||||
|
||||
// If path is clockwise, reverse it to make it counterclockwise (standard for outer contours)
|
||||
if (isClockwise) {
|
||||
finalPath = new SVGPathCommander(optimized, { round: 0 }).reverse().toString()
|
||||
}
|
||||
|
||||
return match.replace(pathData, finalPath)
|
||||
} catch (e) {
|
||||
console.warn('Could not process path:', e.message)
|
||||
return match
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate path direction using shoelace formula
|
||||
* Returns true if clockwise, false if counterclockwise
|
||||
*/
|
||||
function getPathDirection(segments) {
|
||||
let sum = 0
|
||||
let points = []
|
||||
|
||||
// Extract points from segments
|
||||
for (const seg of segments) {
|
||||
if (seg[0] === 'M' || seg[0] === 'L') {
|
||||
points.push({ x: seg[1], y: seg[2] })
|
||||
} else if (seg[0] === 'C') {
|
||||
// For curves, use the endpoint
|
||||
points.push({ x: seg[5], y: seg[6] })
|
||||
} else if (seg[0] === 'Q') {
|
||||
points.push({ x: seg[3], y: seg[4] })
|
||||
} else if (seg[0] === 'Z' || seg[0] === 'z') {
|
||||
// Close path - use first point
|
||||
if (points.length > 0) {
|
||||
points.push(points[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate signed area using shoelace formula
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
sum += (points[i + 1].x - points[i].x) * (points[i + 1].y + points[i].y)
|
||||
}
|
||||
|
||||
// Positive = clockwise, negative = counterclockwise (in SVG coordinate system where Y increases downward)
|
||||
return sum > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Process SVG file
|
||||
*/
|
||||
export function fixOutlineFile(inputPath, outputPath = null) {
|
||||
const content = fs.readFileSync(inputPath, 'utf-8')
|
||||
const fixed = fixOutline(content)
|
||||
fs.writeFileSync(outputPath || inputPath, fixed, 'utf-8')
|
||||
return fixed
|
||||
}
|
||||
|
||||
// CLI support
|
||||
if (process.argv[1] && process.argv[1].endsWith('fix-outline.mjs')) {
|
||||
const file = process.argv[2]
|
||||
if (file) {
|
||||
console.log(`Correcting outline for ${file}`)
|
||||
fixOutlineFile(file)
|
||||
console.log('Finished fixing svg outline directions!')
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import fontforge
|
||||
|
||||
file = sys.argv[1]
|
||||
|
||||
font = fontforge.font()
|
||||
print (f"Correcting outline for {file}")
|
||||
glyph = font.createChar(123, file)
|
||||
glyph.importOutlines("./" + file)
|
||||
glyph.round()
|
||||
glyph.simplify()
|
||||
glyph.simplify()
|
||||
glyph.correctDirection()
|
||||
glyph.export("./" + file)
|
||||
|
||||
print ("Finished fixing svg outline directions!")
|
||||
|
|
@ -36,7 +36,8 @@
|
|||
"style": "./tabler-icons.min.css",
|
||||
"dependencies": {
|
||||
"@tabler/icons": "3.35.0",
|
||||
"sharp": "^0.33.5"
|
||||
"sharp": "^0.33.5",
|
||||
"svg-path-commander": "^2.1.11"
|
||||
},
|
||||
"keywords": [
|
||||
"icons",
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ importers:
|
|||
devDependencies:
|
||||
'@preact/preset-vite':
|
||||
specifier: ^2.8.1
|
||||
version: 2.10.2(@babel/core@7.28.4)(preact@10.27.2)(vite@5.4.20)
|
||||
version: 2.10.2(@babel/core@7.28.4)(preact@10.27.2)(vite@7.2.7)
|
||||
'@testing-library/preact':
|
||||
specifier: ^3.2.3
|
||||
version: 3.2.4(preact@10.27.2)
|
||||
|
|
@ -186,7 +186,7 @@ importers:
|
|||
version: 18.2.60
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.2.1
|
||||
version: 4.7.0(vite@5.4.20)
|
||||
version: 4.7.0(vite@7.2.7)
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
|
|
@ -211,7 +211,7 @@ importers:
|
|||
version: 18.2.60
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.2.1
|
||||
version: 4.7.0(vite@5.4.20)
|
||||
version: 4.7.0(vite@7.2.7)
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
|
|
@ -248,7 +248,7 @@ importers:
|
|||
version: 1.9.9
|
||||
vite-plugin-solid:
|
||||
specifier: ^2.10.1
|
||||
version: 2.11.8(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vite@5.4.20)
|
||||
version: 2.11.8(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vite@7.2.7)
|
||||
|
||||
packages/icons-sprite:
|
||||
dependencies:
|
||||
|
|
@ -357,6 +357,9 @@ importers:
|
|||
sharp:
|
||||
specifier: ^0.33.5
|
||||
version: 0.33.5
|
||||
svg-path-commander:
|
||||
specifier: ^2.1.11
|
||||
version: 2.1.11
|
||||
devDependencies:
|
||||
sass:
|
||||
specifier: ^1.71.1
|
||||
|
|
@ -385,7 +388,7 @@ importers:
|
|||
devDependencies:
|
||||
'@preact/preset-vite':
|
||||
specifier: ^2.8.1
|
||||
version: 2.10.2(@babel/core@7.28.4)(preact@10.27.2)(vite@5.4.20)
|
||||
version: 2.10.2(@babel/core@7.28.4)(preact@10.27.2)(vite@7.2.7)
|
||||
|
||||
test/test-react:
|
||||
dependencies:
|
||||
|
|
@ -407,7 +410,7 @@ importers:
|
|||
version: 18.3.7(@types/react@18.2.60)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.2.1
|
||||
version: 4.7.0(vite@5.4.20)
|
||||
version: 4.7.0(vite@7.2.7)
|
||||
|
||||
test/test-react-native:
|
||||
dependencies:
|
||||
|
|
@ -429,7 +432,7 @@ importers:
|
|||
version: 18.3.7(@types/react@18.2.60)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.2.1
|
||||
version: 4.7.0(vite@5.4.20)
|
||||
version: 4.7.0(vite@7.2.7)
|
||||
|
||||
test/test-svelte:
|
||||
dependencies:
|
||||
|
|
@ -3806,7 +3809,7 @@ packages:
|
|||
config-chain: 1.1.13
|
||||
dev: true
|
||||
|
||||
/@preact/preset-vite@2.10.2(@babel/core@7.28.4)(preact@10.27.2)(vite@5.4.20):
|
||||
/@preact/preset-vite@2.10.2(@babel/core@7.28.4)(preact@10.27.2)(vite@7.2.7):
|
||||
resolution: {integrity: sha512-K9wHlJOtkE+cGqlyQ5v9kL3Ge0Ql4LlIZjkUTL+1zf3nNdF88F9UZN6VTV8jdzBX9Fl7WSzeNMSDG7qECPmSmg==}
|
||||
peerDependencies:
|
||||
'@babel/core': 7.x
|
||||
|
|
@ -3815,13 +3818,13 @@ packages:
|
|||
'@babel/core': 7.28.4
|
||||
'@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.4)
|
||||
'@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.4)
|
||||
'@prefresh/vite': 2.4.10(preact@10.27.2)(vite@5.4.20)
|
||||
'@prefresh/vite': 2.4.10(preact@10.27.2)(vite@7.2.7)
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.28.4)
|
||||
debug: 4.4.3
|
||||
picocolors: 1.1.1
|
||||
vite: 5.4.20(sass@1.92.1)
|
||||
vite-prerender-plugin: 0.5.12(vite@5.4.20)
|
||||
vite: 7.2.7(sass@1.92.1)
|
||||
vite-prerender-plugin: 0.5.12(vite@7.2.7)
|
||||
transitivePeerDependencies:
|
||||
- preact
|
||||
- supports-color
|
||||
|
|
@ -3843,7 +3846,7 @@ packages:
|
|||
resolution: {integrity: sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==}
|
||||
dev: true
|
||||
|
||||
/@prefresh/vite@2.4.10(preact@10.27.2)(vite@5.4.20):
|
||||
/@prefresh/vite@2.4.10(preact@10.27.2)(vite@7.2.7):
|
||||
resolution: {integrity: sha512-lt+ODASOtXRWaPplp7/DlrgAaInnQYNvcpCglQBMx2OeJPyZ4IqPRaxsK77w96mWshjYwkqTsRSHoAM7aAn0ow==}
|
||||
peerDependencies:
|
||||
preact: ^10.4.0 || ^11.0.0-0
|
||||
|
|
@ -3855,7 +3858,7 @@ packages:
|
|||
'@prefresh/utils': 1.2.1
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
preact: 10.27.2
|
||||
vite: 5.4.20(sass@1.92.1)
|
||||
vite: 7.2.7(sass@1.92.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
|
@ -4849,6 +4852,11 @@ packages:
|
|||
- '@vue/server-renderer'
|
||||
dev: true
|
||||
|
||||
/@thednp/dommatrix@2.0.12:
|
||||
resolution: {integrity: sha512-eOshhlSShBXLfrMQqqhA450TppJXhKriaQdN43mmniOCMn9sD60QKF1Axsj7bKl339WH058LuGFS6H84njYH5w==}
|
||||
engines: {node: '>=20', pnpm: '>=8.6.0'}
|
||||
dev: false
|
||||
|
||||
/@tootallnate/once@2.0.0:
|
||||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||
engines: {node: '>= 10'}
|
||||
|
|
@ -5033,7 +5041,7 @@ packages:
|
|||
'@types/yargs-parser': 21.0.3
|
||||
dev: true
|
||||
|
||||
/@vitejs/plugin-react@4.7.0(vite@5.4.20):
|
||||
/@vitejs/plugin-react@4.7.0(vite@7.2.7):
|
||||
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
|
|
@ -5045,7 +5053,7 @@ packages:
|
|||
'@rolldown/pluginutils': 1.0.0-beta.27
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 5.4.20(sass@1.92.1)
|
||||
vite: 7.2.7(sass@1.92.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
|
@ -13259,6 +13267,13 @@ packages:
|
|||
- debug
|
||||
dev: true
|
||||
|
||||
/svg-path-commander@2.1.11:
|
||||
resolution: {integrity: sha512-wmQ6QA3Od+HOcpIzLjPlbv59+x3yd3V5W6xitUOvAHmqZpP7wVrRM2CHqEm5viHUbZu6PjzFsjbTEFtIeUxaNA==}
|
||||
engines: {node: '>=16', pnpm: '>=8.6.0'}
|
||||
dependencies:
|
||||
'@thednp/dommatrix': 2.0.12
|
||||
dev: false
|
||||
|
||||
/svg-pathdata@7.2.0:
|
||||
resolution: {integrity: sha512-qd+AxqMpfRrRQaWb2SrNFvn69cvl6piqY8TxhYl2Li1g4/LO5F9NJb5wI4vNwRryqgSgD43gYKLm/w3ag1bKvQ==}
|
||||
engines: {node: '>=20.11.1'}
|
||||
|
|
@ -13942,7 +13957,7 @@ packages:
|
|||
- yaml
|
||||
dev: true
|
||||
|
||||
/vite-plugin-solid@2.11.8(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vite@5.4.20):
|
||||
/vite-plugin-solid@2.11.8(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vite@7.2.7):
|
||||
resolution: {integrity: sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg==}
|
||||
peerDependencies:
|
||||
'@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.*
|
||||
|
|
@ -13959,13 +13974,13 @@ packages:
|
|||
merge-anything: 5.1.7
|
||||
solid-js: 1.9.9
|
||||
solid-refresh: 0.6.3(solid-js@1.9.9)
|
||||
vite: 5.4.20(sass@1.92.1)
|
||||
vitefu: 1.1.1(vite@5.4.20)
|
||||
vite: 7.2.7(sass@1.92.1)
|
||||
vitefu: 1.1.1(vite@7.2.7)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-prerender-plugin@0.5.12(vite@5.4.20):
|
||||
/vite-prerender-plugin@0.5.12(vite@7.2.7):
|
||||
resolution: {integrity: sha512-EiwhbMn+flg14EysbLTmZSzq8NGTxhytgK3bf4aGRF1evWLGwZiHiUJ1KZDvbxgKbMf2pG6fJWGEa3UZXOnR1g==}
|
||||
peerDependencies:
|
||||
vite: 5.x || 6.x || 7.x
|
||||
|
|
@ -13976,7 +13991,7 @@ packages:
|
|||
simple-code-frame: 1.3.0
|
||||
source-map: 0.7.6
|
||||
stack-trace: 1.0.0-pre2
|
||||
vite: 5.4.20(sass@1.92.1)
|
||||
vite: 7.2.7(sass@1.92.1)
|
||||
dev: true
|
||||
|
||||
/vite@5.4.20(sass@1.92.1):
|
||||
|
|
@ -14080,17 +14095,6 @@ packages:
|
|||
vite: 5.4.20(sass@1.92.1)
|
||||
dev: true
|
||||
|
||||
/vitefu@1.1.1(vite@5.4.20):
|
||||
resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 5.4.20(sass@1.92.1)
|
||||
dev: true
|
||||
|
||||
/vitefu@1.1.1(vite@7.2.7):
|
||||
resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==}
|
||||
peerDependencies:
|
||||
|
|
|
|||
Loading…
Reference in New Issue