const ICONIFY_SELECTOR = '[class*="iconify"]'; /** * Replaces iconify CSS-mask spans with real (for html2pdf, html2canvas), * returns a restore function to swap them back. */ export function swapIconsForExport(root) { const spans = root.querySelectorAll(ICONIFY_SELECTOR); const restoreList = []; spans.forEach((span) => { const svgMarkup = extractSvgFromMask(span); if (!svgMarkup) return; // not a mask-based icon, skip const computed = getComputedStyle(span); const size = computed.fontSize || "1em"; const color = computed.color; const wrapper = document.createElement("span"); wrapper.innerHTML = svgMarkup; const svgEl = wrapper.firstElementChild; svgEl.setAttribute("width", size); svgEl.setAttribute("height", size); svgEl.style.display = "inline-block"; svgEl.style.verticalAlign = "middle"; // force fill to currentColor's resolved value svgEl.querySelectorAll("[fill]").forEach((node) => node.setAttribute("fill", color)); span.replaceWith(svgEl); restoreList.push({ svgEl, originalSpan: span }); }); return function restoreIcons() { restoreList.forEach(({ svgEl, originalSpan }) => { svgEl.replaceWith(originalSpan); }); }; } function extractSvgFromMask(el) { const maskVar = getComputedStyle(el).getPropertyValue("--svg").trim(); // maskVar looks like: url("data:image/svg+xml,...") const match = maskVar.match(/url\((['"]?)(.*?)\1\)/); if (!match) return null; const dataUri = match[2]; if (!dataUri.startsWith("data:image/svg+xml,")) return null; const encoded = dataUri.replace("data:image/svg+xml,", ""); return decodeURIComponent(encoded); }