fix svg loading to remove garbage icons

This commit is contained in:
2026-04-06 05:46:32 +02:00
parent 8f6a2ef674
commit 1cd0f1ed1d
+85 -1
View File
@@ -1112,6 +1112,90 @@
} }
} }
function parseViewBox(svgEl) {
const raw = String(svgEl.getAttribute("viewBox") || "").trim();
const parts = raw.split(/\s+/).map((item) => Number(item));
if (parts.length !== 4 || parts.some((v) => Number.isNaN(v))) {
return { minX: 0, minY: 0, width: 100, height: 100 };
}
return { minX: parts[0], minY: parts[1], width: parts[2], height: parts[3] };
}
function groupLooksOffCanvas(groupEl, viewBox) {
const attrNames = new Set(["x", "y", "cx", "cy", "x1", "y1", "x2", "y2", "d", "points"]);
const allElements = [groupEl, ...groupEl.querySelectorAll("*")];
let farOutsideCount = 0;
let numericCount = 0;
const minAllowedX = viewBox.minX - Math.max(40, viewBox.width * 0.8);
const minAllowedY = viewBox.minY - Math.max(40, viewBox.height * 0.8);
const maxAllowedX = viewBox.minX + viewBox.width + Math.max(40, viewBox.width * 0.8);
const maxAllowedY = viewBox.minY + viewBox.height + Math.max(40, viewBox.height * 0.8);
for (const node of allElements) {
for (const attr of node.getAttributeNames()) {
if (!attrNames.has(attr)) continue;
const value = node.getAttribute(attr);
if (!value) continue;
const matches = value.match(/-?\d*\.?\d+/g);
if (!matches) continue;
for (let idx = 0; idx < matches.length; idx += 1) {
const num = Number(matches[idx]);
if (Number.isNaN(num)) continue;
numericCount += 1;
const isXCoord = idx % 2 === 0;
if (isXCoord) {
if (num < minAllowedX || num > maxAllowedX) farOutsideCount += 1;
} else {
if (num < minAllowedY || num > maxAllowedY) farOutsideCount += 1;
}
}
}
}
if (numericCount < 10) return false;
return farOutsideCount / numericCount > 0.55;
}
function maxNestedGroupDepth(groupEl) {
let maxDepth = 1;
const stack = [{ node: groupEl, depth: 1 }];
while (stack.length > 0) {
const entry = stack.pop();
if (!entry) continue;
maxDepth = Math.max(maxDepth, entry.depth);
for (const child of Array.from(entry.node.children)) {
if (!child.tagName || child.tagName.toLowerCase() !== "g") continue;
stack.push({ node: child, depth: entry.depth + 1 });
}
}
return maxDepth;
}
function normalizeHeadSvgMarkup(svgMarkup) {
if (!svgMarkup) return null;
try {
const parser = new DOMParser();
const parsed = parser.parseFromString(svgMarkup, "image/svg+xml");
const svgEl = parsed.querySelector("svg");
if (!svgEl) return svgMarkup;
const topLevelGroups = Array.from(svgEl.children).filter((el) => el.tagName && el.tagName.toLowerCase() === "g");
if (topLevelGroups.length > 1) {
const viewBox = parseViewBox(svgEl);
const firstGroup = topLevelGroups[0];
const deeplyNestedGroup = maxNestedGroupDepth(firstGroup) >= 3;
if (groupLooksOffCanvas(firstGroup, viewBox) || deeplyNestedGroup) {
firstGroup.remove();
}
}
return new XMLSerializer().serializeToString(svgEl);
} catch {
return svgMarkup;
}
}
async function preloadReplaySvgs() { async function preloadReplaySvgs() {
if (!replay || !Array.isArray(replay.turns)) return; if (!replay || !Array.isArray(replay.turns)) return;
const urls = new Set(); const urls = new Set();
@@ -1135,7 +1219,7 @@
if (type === "head") { if (type === "head") {
const svgMarkup = svgCache.get(iconUrl); const svgMarkup = svgCache.get(iconUrl);
if (svgMarkup) { if (svgMarkup) {
layer.innerHTML = svgMarkup; layer.innerHTML = normalizeHeadSvgMarkup(svgMarkup);
const svgEl = layer.querySelector("svg"); const svgEl = layer.querySelector("svg");
if (svgEl) { if (svgEl) {
svgEl.style.width = "100%"; svgEl.style.width = "100%";