IOweb/node_modules/@iconify/tools/lib/optimise/flags.mjs
2026-07-03 15:07:38 -05:00

254 lines
6.4 KiB
JavaScript

import { parseSVG } from '../svg/parse.mjs';
const CLOSE_PATH = 1;
const MOVE_TO = 2;
const HORIZ_LINE_TO = 4;
const VERT_LINE_TO = 8;
const LINE_TO = 16;
const CURVE_TO = 32;
const SMOOTH_CURVE_TO = 64;
const QUAD_TO = 128;
const SMOOTH_QUAD_TO = 256;
const ARC = 512;
const argCount = {
[MOVE_TO]: 2,
[LINE_TO]: 2,
[HORIZ_LINE_TO]: 1,
[VERT_LINE_TO]: 1,
[CLOSE_PATH]: 0,
[QUAD_TO]: 4,
[SMOOTH_QUAD_TO]: 2,
[CURVE_TO]: 6,
[SMOOTH_CURVE_TO]: 4,
[ARC]: 7
};
const isDigit = (num) => num >= 48 && num <= 57;
const isWhiteSpace = (num) => num === 32 || num === 9 || num === 13 || num === 10;
function cleanPath(path) {
const commands = [];
const length = path.length;
let currentNumber = "";
let currentNumberHasExp = false;
let currentNumberHasExpDigits = false;
let currentNumberHasDecimal = false;
let canParseCommandOrComma = true;
let currentCommand = null;
let currentCommandType = null;
let currentArgs = [];
let i = 0;
const finishCommand = () => {
if (currentCommand !== null) {
commands.push({
command: currentCommand,
params: currentArgs.slice(0)
});
currentArgs = [];
canParseCommandOrComma = true;
switch (currentCommand) {
case "M":
currentCommand = "L";
break;
case "m":
currentCommand = "l";
break;
}
}
};
const parseNumber = () => {
if (currentNumber !== "" && currentCommandType) {
let value = Number(currentNumber);
if (isNaN(value)) {
throw new Error(`Invalid number "${currentNumber}" at ${i}`);
}
if (currentCommandType === ARC) {
if (currentArgs.length < 2 && value <= 0) {
throw new Error(
`Expected positive number, got "${value}" at ${i}`
);
}
while (true) {
if (currentArgs.length < 3 || currentArgs.length > 4) {
break;
}
if (currentNumber === "0" || currentNumber === "1") {
break;
}
const slice = currentNumber.slice(0, 1);
const newNumber = currentNumber.slice(1);
const newValue = Number(newNumber);
if (slice === "0" || slice === "1") {
if (isNaN(newValue)) {
throw new Error(
`Invalid number "${currentNumber}" at ${i}`
);
}
currentArgs.push(slice);
currentNumber = newNumber;
value = newValue;
continue;
}
throw new Error(
`Expected a flag, got "${currentNumber}" at ${i}`
);
}
}
currentArgs.push(currentNumber);
if (currentArgs.length === argCount[currentCommandType]) {
finishCommand();
}
currentNumber = "";
currentNumberHasExpDigits = false;
currentNumberHasExp = false;
currentNumberHasDecimal = false;
canParseCommandOrComma = true;
}
};
for (i = 0; i < length; i++) {
const char = path[i];
const num = char.charCodeAt(0);
if (isDigit(num)) {
currentNumber += char;
currentNumberHasExpDigits = currentNumberHasExp;
continue;
}
if (char === "e" || char === "E") {
currentNumber += char;
currentNumberHasExp = true;
continue;
}
if ((char === "-" || char === "+") && currentNumberHasExp && !currentNumberHasExpDigits) {
currentNumber += char;
continue;
}
if (char === "." && !currentNumberHasExp && !currentNumberHasDecimal) {
currentNumber += char;
currentNumberHasDecimal = true;
continue;
}
parseNumber();
if (isWhiteSpace(num)) {
continue;
}
if (canParseCommandOrComma && char === ",") {
canParseCommandOrComma = false;
continue;
}
if (char === "+" || char === "-" || char === ".") {
currentNumber = char;
currentNumberHasDecimal = char === ".";
continue;
}
if (currentArgs.length > 0) {
throw new Error(`Unexpected command at ${i}`);
}
if (!canParseCommandOrComma) {
throw new Error(`Command cannot follow comma at ${i}`);
}
canParseCommandOrComma = false;
currentCommand = char;
switch (char) {
case "z":
case "Z":
commands.push({
command: char,
params: []
});
canParseCommandOrComma = true;
currentCommandType = null;
currentCommand = null;
break;
case "h":
case "H":
currentCommandType = HORIZ_LINE_TO;
break;
case "v":
case "V":
currentCommandType = VERT_LINE_TO;
break;
case "m":
case "M":
currentCommandType = MOVE_TO;
break;
case "l":
case "L":
currentCommandType = LINE_TO;
break;
case "c":
case "C":
currentCommandType = CURVE_TO;
break;
case "s":
case "S":
currentCommandType = SMOOTH_CURVE_TO;
break;
case "q":
case "Q":
currentCommandType = QUAD_TO;
break;
case "t":
case "T":
currentCommandType = SMOOTH_QUAD_TO;
break;
case "a":
case "A":
currentCommandType = ARC;
break;
default:
throw new Error(`Unexpected character "${char}" at ${i}`);
}
}
parseNumber();
if (currentArgs.length) {
if (!currentCommandType) {
throw new Error("Empty path");
}
if (currentArgs.length !== argCount[currentCommandType]) {
throw new Error(`Unexpected end of path at ${i}`);
}
finishCommand();
}
let output = "";
commands.forEach((item) => {
output += item.command;
item.params.forEach((value, index) => {
if (index > 0) {
const char = value[0];
switch (char) {
case "-":
case "+":
break;
case ".":
if (index < 1) {
break;
}
if (item.params[index - 1].includes(".")) {
break;
}
default:
output += " ";
}
}
output += value;
});
});
return output;
}
function deOptimisePaths(svg) {
parseSVG(svg, (item) => {
if (item.tagName !== "path") {
return;
}
const d = item.element.attribs.d;
if (typeof d === "string") {
try {
const optimised = cleanPath(d);
if (optimised !== d) {
item.$element.attr("d", optimised);
}
} catch {
}
}
});
}
export { deOptimisePaths };