Enhance SVG processing in build-outline.mjs by implementing parallel processing with concurrency limits.
This commit is contained in:
parent
670958d52c
commit
40b0b16605
|
|
@ -6,9 +6,26 @@ import crypto from 'crypto'
|
|||
import { glob } from 'glob'
|
||||
import { optimize } from 'svgo'
|
||||
import { fixOutline } from './fix-outline.mjs'
|
||||
import os from 'os'
|
||||
|
||||
const DIR = getPackageDir('icons-webfont')
|
||||
|
||||
// Parallel processing with concurrency limit
|
||||
const parallelLimit = async (items, fn, concurrency = os.cpus().length * 2) => {
|
||||
const results = []
|
||||
let index = 0
|
||||
|
||||
const worker = async () => {
|
||||
while (index < items.length) {
|
||||
const i = index++
|
||||
results[i] = await fn(items[i], i)
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(Array(Math.min(concurrency, items.length)).fill(null).map(worker))
|
||||
return results
|
||||
}
|
||||
|
||||
const strokes = {
|
||||
200: 1,
|
||||
300: 1.5,
|
||||
|
|
@ -24,79 +41,81 @@ const buildOutline = async () => {
|
|||
for (const strokeName in strokes) {
|
||||
const stroke = strokes[strokeName]
|
||||
|
||||
await asyncForEach(Object.entries(icons), async ([type, icons]) => {
|
||||
for (const [type, typeIcons] of Object.entries(icons)) {
|
||||
fs.mkdirSync(resolve(DIR, `icons-outlined/${strokeName}/${type}`), { recursive: true })
|
||||
filesList[type] = []
|
||||
|
||||
await asyncForEach(icons, async function ({ name, content, unicode }) {
|
||||
if (compileOptions.includeIcons.length === 0 || compileOptions.includeIcons.indexOf(name) >= 0) {
|
||||
|
||||
if (unicode) {
|
||||
console.log(`Stroke ${strokeName} for:`, name, unicode)
|
||||
|
||||
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(/<!--\!cache:([a-z0-9]+)-->/, 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 => {
|
||||
// Fix outline direction (using JS instead of fontforge)
|
||||
const fixed = fixOutline(outlined)
|
||||
|
||||
// Optimize with svgo (in memory, no subprocess)
|
||||
const optimized = optimize(fixed, { multipass: true }).data
|
||||
|
||||
// Prepare final content with hash
|
||||
const finalContent = optimized.replace(/\n/g, ' ').trim()
|
||||
const hashString = `<!--!cache:${crypto.createHash('sha1').update(finalContent).digest("hex")}-->`
|
||||
|
||||
// Save file (single write instead of 3 file operations)
|
||||
fs.writeFileSync(
|
||||
resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`),
|
||||
finalContent + hashString,
|
||||
'utf-8'
|
||||
)
|
||||
}).catch(error => console.log(error))
|
||||
|
||||
// Filter icons first
|
||||
const iconsToProcess = typeIcons.filter(({ name, unicode }) => {
|
||||
if (!unicode) return false
|
||||
if (compileOptions.includeIcons.length > 0 && compileOptions.includeIcons.indexOf(name) < 0) return false
|
||||
return true
|
||||
})
|
||||
|
||||
// Collect filenames for later cleanup
|
||||
filesList[type] = iconsToProcess.map(({ name, unicode }) => `u${unicode.toUpperCase()}-${name}.svg`)
|
||||
|
||||
// Process icons in parallel with concurrency limit
|
||||
let processed = 0
|
||||
const total = iconsToProcess.length
|
||||
|
||||
await parallelLimit(iconsToProcess, async ({ name, content, unicode }) => {
|
||||
const filename = `u${unicode.toUpperCase()}-${name}.svg`
|
||||
const filePath = resolve(DIR, `icons-outlined/${strokeName}/${type}/${filename}`)
|
||||
|
||||
// Check cache
|
||||
if (fs.existsSync(filePath)) {
|
||||
let cachedContent = fs.readFileSync(filePath, 'utf-8')
|
||||
let cachedHash = ''
|
||||
cachedContent = cachedContent.replace(/<!--\!cache:([a-z0-9]+)-->/, (m, hash) => {
|
||||
cachedHash = hash
|
||||
return ''
|
||||
})
|
||||
|
||||
if (crypto.createHash('sha1').update(cachedContent).digest("hex") === cachedHash) {
|
||||
processed++
|
||||
process.stdout.write(`\rStroke ${strokeName}/${type}: ${processed}/${total} (cached: ${name})`.padEnd(80))
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Prepare content
|
||||
content = content
|
||||
.replace('width="24"', 'width="1000"')
|
||||
.replace('height="24"', 'height="1000"')
|
||||
.replace('stroke-width="2"', `stroke-width="${stroke}"`)
|
||||
|
||||
try {
|
||||
const outlined = await outlineStroke(content, {
|
||||
optCurve: true,
|
||||
steps: 4,
|
||||
round: 0,
|
||||
centerHorizontally: true,
|
||||
fixedWidth: false,
|
||||
color: 'black'
|
||||
})
|
||||
|
||||
// Fix outline direction (using JS instead of fontforge)
|
||||
const fixed = fixOutline(outlined)
|
||||
|
||||
// Optimize with svgo (in memory, no subprocess)
|
||||
const optimized = optimize(fixed, { multipass: true }).data
|
||||
|
||||
// Prepare final content with hash
|
||||
const finalContent = optimized.replace(/\n/g, ' ').trim()
|
||||
const hashString = `<!--!cache:${crypto.createHash('sha1').update(finalContent).digest("hex")}-->`
|
||||
|
||||
// Save file
|
||||
fs.writeFileSync(filePath, finalContent + hashString, 'utf-8')
|
||||
|
||||
processed++
|
||||
process.stdout.write(`\rStroke ${strokeName}/${type}: ${processed}/${total} (${name})`.padEnd(80))
|
||||
} catch (error) {
|
||||
console.error(`\nError processing ${name}:`, error.message)
|
||||
}
|
||||
}, 32) // 32 concurrent tasks
|
||||
|
||||
console.log() // New line after progress
|
||||
}
|
||||
|
||||
// Remove old files
|
||||
await asyncForEach(Object.entries(icons), async ([type, icons]) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue