I've worked out a system that works. My main problem is that I need to replace many images in the SVG, and this happens asynchronously so I needed a way to continue when all were complete. I ended up with a working solution and wanted to post what I have, though I'm confident that others could improve on it. I basically created a recursive function that takes one image URL, creates an element to load it, then add a function to replace the URL with the data URL once it's loaded, then trigger a repeat call of the function. Once no more image URLs are found, we trigger the download. This is sequential so likely could be improved upon, but most images are already loaded or in the process of being loaded on my page so it's pretty much instant for me.
I used various other questions to put this together. In particular:
How to Download and SVG element as an SVG file
Convert image from url to Base64
How do I save/export an SVG file after creating an SVG with D3.js (IE, safari and chrome)?
Here's my code:
// Download SVG file - call this function
function downloadSVG() {
const svg = document.getElementsByTagName('svg')[0].cloneNode(true);
// Replace image URLs with embedded data for SVG also triggers download
replaceImageURLs(svgData, "svg", null);
}
// Find image URLs and replace with embedded versions
function replaceImageURLs(svg, type, img) {
let protocol = "http";
let start = '<image xlink:href="'+protocol;
let startPos, len, url;
if (svg.indexOf(start) !== -1) {
startPos = svg.indexOf(start)+start.length-protocol.length;
len = svg.substring(startPos).indexOf("\"");
url = svg.substring(startPos,startPos+len);
const img2 = document.createElement("img");
img2.onload = function() {
let base64 = getBase64Image(img2);
svg = svg.replace(url,base64);
replaceImageURLs(svg, type, img);
img2.remove();
}
img2.src = url;
} else {
const svgBlob = new Blob([svg], {type: "image/svg+xml;charset=utf-8"});
const svgUrl = URL.createObjectURL(svgBlob);
downloadLink(svgUrl, "gvexport."+type);
}
}
// Convert image URL to base64 data - we use for embedding images in SVG
// From https://stackoverflow.com/questions/22172604/convert-image-from-url-to-base64
function getBase64Image(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
return canvas.toDataURL("image/png");
}
// Trigger a download via javascript
function downloadLink(URL, filename) {
const downloadLink = document.createElement("a");
downloadLink.href = URL;
downloadLink.download = filename;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}