|
|
|
@ -2,16 +2,7 @@ import fsPromises from 'node:fs/promises'; |
|
|
|
import { createReadStream, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'; |
|
|
|
import path from 'node:path'; |
|
|
|
import { globSync } from 'glob'; |
|
|
|
import SVGPathCommander, { parsePathString, pathToString } from 'svg-path-commander'; |
|
|
|
import { blankSquare, getAliases, getPackageJson } from '../../../.build/helpers.mjs'; |
|
|
|
import spo from 'svg-path-outline'; |
|
|
|
// Import canvas before paper-jsdom to ensure it's available for jsdom
|
|
|
|
// Use require for canvas to ensure it's loaded before jsdom initializes
|
|
|
|
import { createRequire } from 'module'; |
|
|
|
const require = createRequire(import.meta.url); |
|
|
|
require('canvas'); |
|
|
|
import paper from "paper-jsdom"; |
|
|
|
import { createCanvas } from '@napi-rs/canvas'; |
|
|
|
import crypto from 'crypto'; |
|
|
|
import { Eta } from 'eta'; |
|
|
|
import svg2ttf from "svg2ttf"; |
|
|
|
@ -35,10 +26,6 @@ function template(templateString) { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
// Setup paper.js with @napi-rs/canvas
|
|
|
|
const canvas = createCanvas(1, 1); |
|
|
|
paper.setup(canvas); |
|
|
|
|
|
|
|
/** |
|
|
|
* @typedef {stream.Readable & { metadata?: { unicode: string[], name: string } }} Svgicons2svgfontStream |
|
|
|
*/ |
|
|
|
@ -110,42 +97,6 @@ export async function buildSvgFont(svgStreams) { |
|
|
|
return await fontStreamPromise; |
|
|
|
} |
|
|
|
|
|
|
|
// Function to calculate the end point of a segment
|
|
|
|
export function getEndPoint(segment, startPoint) { |
|
|
|
const [command, ...params] = segment; |
|
|
|
const upperCommand = command.toUpperCase(); |
|
|
|
|
|
|
|
switch (upperCommand) { |
|
|
|
case 'M': |
|
|
|
return [params[0], params[1]]; |
|
|
|
case 'L': |
|
|
|
return [params[0], params[1]]; |
|
|
|
case 'H': |
|
|
|
return [params[0], startPoint[1]]; |
|
|
|
case 'V': |
|
|
|
return [startPoint[0], params[0]]; |
|
|
|
case 'C': |
|
|
|
// Cubic Bezier: C x1 y1 x2 y2 x y
|
|
|
|
return [params[4], params[5]]; |
|
|
|
case 'S': |
|
|
|
// Smooth Cubic Bezier: S x2 y2 x y
|
|
|
|
return [params[2], params[3]]; |
|
|
|
case 'Q': |
|
|
|
// Quadratic Bezier: Q x1 y1 x y
|
|
|
|
return [params[2], params[3]]; |
|
|
|
case 'T': |
|
|
|
// Smooth Quadratic Bezier: T x y
|
|
|
|
return [params[0], params[1]]; |
|
|
|
case 'A': |
|
|
|
// Arc: A rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
|
|
|
return [params[5], params[6]]; |
|
|
|
case 'Z': |
|
|
|
return startPoint; |
|
|
|
default: |
|
|
|
return startPoint; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Function to remove XML/HTML comments from SVG
|
|
|
|
export function removeComments(svgBuffer) { |
|
|
|
// Remove all XML/HTML comments (<!-- ... -->)
|
|
|
|
@ -157,114 +108,6 @@ export function removeComments(svgBuffer) { |
|
|
|
return svgBuffer; |
|
|
|
} |
|
|
|
|
|
|
|
// Function to split all paths in SVG into individual segments
|
|
|
|
export function splitPaths(svgBuffer) { |
|
|
|
svgBuffer = svgBuffer.replaceAll(/<path([^>]*)d="([^"]*)"([^>]*)>/g, (match, p1, p2, p3) => { |
|
|
|
// Convert path to absolute format
|
|
|
|
const absolutePath = new SVGPathCommander(p2).toAbsolute().toString(); |
|
|
|
// Parse path string to PathArray
|
|
|
|
const pathArray = parsePathString(absolutePath) |
|
|
|
|
|
|
|
if (!Array.isArray(pathArray) || pathArray.length === 0) { |
|
|
|
return match; |
|
|
|
} |
|
|
|
|
|
|
|
// Track current position and path start point
|
|
|
|
let currentPoint = [0, 0]; |
|
|
|
let pathStartPoint = [0, 0]; |
|
|
|
const individualPaths = []; |
|
|
|
|
|
|
|
for (let i = 0; i < pathArray.length; i++) { |
|
|
|
const segment = pathArray[i]; |
|
|
|
const [command] = segment; |
|
|
|
const upperCommand = command.toUpperCase(); |
|
|
|
|
|
|
|
if (upperCommand === 'M') { |
|
|
|
// MoveTo command - update current position and path start
|
|
|
|
currentPoint = [segment[1], segment[2]]; |
|
|
|
pathStartPoint = currentPoint; |
|
|
|
|
|
|
|
// If there's a next segment, create a path from M to that segment
|
|
|
|
if (i + 1 < pathArray.length) { |
|
|
|
const nextSegment = pathArray[i + 1]; |
|
|
|
const newPath = pathToString([segment, nextSegment]); |
|
|
|
individualPaths.push(newPath); |
|
|
|
currentPoint = getEndPoint(nextSegment, currentPoint); |
|
|
|
i++; // Skip next segment as we've already processed it
|
|
|
|
} |
|
|
|
} else if (upperCommand === 'Z') { |
|
|
|
// Close path - create a line back to start
|
|
|
|
if (individualPaths.length > 0) { |
|
|
|
// Add Z to the last path if it doesn't already have it
|
|
|
|
let lastPath = individualPaths[individualPaths.length - 1]; |
|
|
|
if (!lastPath.trim().endsWith('Z') && !lastPath.trim().endsWith('z')) { |
|
|
|
const lastPathArray = parsePathString(lastPath); |
|
|
|
lastPathArray.push(['Z']); |
|
|
|
lastPath = pathToString(lastPathArray); |
|
|
|
individualPaths[individualPaths.length - 1] = lastPath; |
|
|
|
} |
|
|
|
} |
|
|
|
currentPoint = pathStartPoint; |
|
|
|
} else { |
|
|
|
// Any other command - create a new path starting with M
|
|
|
|
const newPath = pathToString([ |
|
|
|
['M', currentPoint[0], currentPoint[1]], |
|
|
|
segment |
|
|
|
]); |
|
|
|
individualPaths.push(newPath); |
|
|
|
currentPoint = getEndPoint(segment, currentPoint); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// If we have multiple paths, create separate <path> elements
|
|
|
|
if (individualPaths.length > 1) { |
|
|
|
const newPaths = individualPaths.map(path => { |
|
|
|
return `<path${p1}d="${path}"${p3}>`; |
|
|
|
}); |
|
|
|
return newPaths.join('\n'); |
|
|
|
} else if (individualPaths.length === 1) { |
|
|
|
// Even if only one path, return it (might be different from original)
|
|
|
|
return `<path${p1}d="${individualPaths[0]}"${p3}>`; |
|
|
|
} else { |
|
|
|
// Fallback to original
|
|
|
|
return match; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
return svgBuffer; |
|
|
|
} |
|
|
|
|
|
|
|
export function reorientPath(svgBuffer) { |
|
|
|
let result = svgBuffer; |
|
|
|
|
|
|
|
const pathRegex = /<path.*?d="([^"]+)"/g; |
|
|
|
const matches = [...svgBuffer.matchAll(pathRegex)]; |
|
|
|
for (const match of matches) { |
|
|
|
const originalPath = match[1]; |
|
|
|
const compoundPath = new paper.CompoundPath(originalPath); |
|
|
|
const newPath = compoundPath.reorient(false, false).pathData; |
|
|
|
result = result.replace(originalPath, newPath); |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
export function offsetPath(svgBuffer, offset) { |
|
|
|
svgBuffer = svgBuffer.replaceAll(/<path[^>]*d="([^"]*)"/g, (match, p1) => { |
|
|
|
let newPath = spo(new SVGPathCommander(p1).toAbsolute().toString(), offset / 2 - 0.001, { |
|
|
|
inside: true, |
|
|
|
outside: true, |
|
|
|
joints: 0 |
|
|
|
}); |
|
|
|
|
|
|
|
return `<path d="${newPath}"` |
|
|
|
}) |
|
|
|
|
|
|
|
svgBuffer = svgBuffer.replaceAll(/stroke="[^"]*"/g, 'stroke="none"') |
|
|
|
svgBuffer = svgBuffer.replaceAll(/fill="[^"]*"/g, 'fill="black"') |
|
|
|
|
|
|
|
return svgBuffer; |
|
|
|
} |
|
|
|
|
|
|
|
export function calculateHash(content) { |
|
|
|
return crypto.createHash('sha1').update(content).digest("hex"); |
|
|
|
} |
|
|
|
@ -394,87 +237,3 @@ export async function processIcons(files, dirname, type, DIR, strokeName = null, |
|
|
|
|
|
|
|
return { processed, cached }; |
|
|
|
} |
|
|
|
|
|
|
|
function applyTransformToPath(pathData, transform) { |
|
|
|
try { |
|
|
|
const path = new SVGPathCommander(pathData); |
|
|
|
// Parse transform string into DOMMatrix-compatible format
|
|
|
|
// SVGPathCommander.transform() accepts transform object or string
|
|
|
|
const transformed = path.transform(transform); |
|
|
|
return transformed.toString(); |
|
|
|
} catch (e) { |
|
|
|
console.warn('Transform detected but not applied:', e.message, '- Using original path data.'); |
|
|
|
return pathData; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
export function mergePaths(svgBuffer) { |
|
|
|
const pathRegex = /<path\s+([^>]*?)\s*\/?>/gs; |
|
|
|
const matches = [...svgBuffer.matchAll(pathRegex)]; |
|
|
|
|
|
|
|
if (matches.length <= 1) { |
|
|
|
return svgBuffer; |
|
|
|
} |
|
|
|
|
|
|
|
const pathDataArray = []; |
|
|
|
const fills = new Set(); |
|
|
|
const strokes = new Set(); |
|
|
|
const fillRules = new Set(); |
|
|
|
const warnings = []; |
|
|
|
|
|
|
|
for (const match of matches) { |
|
|
|
const attrs = match[1]; |
|
|
|
const d = attrs.match(/d="([^"]*)"/)?.[1]; |
|
|
|
const transform = attrs.match(/transform="([^"]*)"/)?.[1]; |
|
|
|
const fill = attrs.match(/fill="([^"]*)"/)?.[1] || 'none'; |
|
|
|
const stroke = attrs.match(/stroke="([^"]*)"/)?.[1] || 'none'; |
|
|
|
const fillRule = attrs.match(/fill-rule="([^"]*)"/)?.[1]; |
|
|
|
|
|
|
|
if (fill !== 'none') fills.add(fill); |
|
|
|
if (stroke !== 'none') strokes.add(stroke); |
|
|
|
if (fillRule) fillRules.add(fillRule); |
|
|
|
|
|
|
|
if (d) { |
|
|
|
pathDataArray.push(transform ? applyTransformToPath(d, transform) : d); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (fills.size > 1) { |
|
|
|
warnings.push( |
|
|
|
`Multiple fill colors detected (${fills.size} different colors). All paths will use the first color found.` |
|
|
|
); |
|
|
|
} |
|
|
|
if (strokes.size > 1) { |
|
|
|
warnings.push( |
|
|
|
`Multiple stroke colors detected (${strokes.size} different colors). All paths will use the first stroke found.` |
|
|
|
); |
|
|
|
} |
|
|
|
if (warnings.length > 0) { |
|
|
|
console.warn('mergePaths:', warnings.join(' ')); |
|
|
|
} |
|
|
|
|
|
|
|
const mergedPathData = pathDataArray.join(' '); |
|
|
|
|
|
|
|
const firstAttrs = matches[0][1]; |
|
|
|
const fill = firstAttrs.match(/fill="([^"]*)"/)?.[1]; |
|
|
|
const stroke = firstAttrs.match(/stroke="([^"]*)"/)?.[1]; |
|
|
|
const strokeWidth = firstAttrs.match(/stroke-width="([^"]*)"/)?.[1]; |
|
|
|
const fillRule = fillRules.size > 0 ? [...fillRules][0] : null; |
|
|
|
|
|
|
|
const newPathAttrs = ['d="' + mergedPathData + '"']; |
|
|
|
if (fill) newPathAttrs.push(`fill="${fill}"`); |
|
|
|
if (stroke) newPathAttrs.push(`stroke="${stroke}"`); |
|
|
|
if (strokeWidth) newPathAttrs.push(`stroke-width="${strokeWidth}"`); |
|
|
|
if (fillRule) newPathAttrs.push(`fill-rule="${fillRule}"`); |
|
|
|
|
|
|
|
const newPath = '<path ' + newPathAttrs.join(' ') + ' />'; |
|
|
|
|
|
|
|
const firstMatchStart = matches[0].index; |
|
|
|
const lastMatch = matches[matches.length - 1]; |
|
|
|
const lastMatchEnd = lastMatch.index + lastMatch[0].length; |
|
|
|
|
|
|
|
const before = svgBuffer.substring(0, firstMatchStart); |
|
|
|
const after = svgBuffer.substring(lastMatchEnd); |
|
|
|
|
|
|
|
return before + newPath + after; |
|
|
|
} |
|
|
|
|