SVG to image for display and download — and truly wishing IE R.I.P.

Background and objective

I have been working on a website that need to

My (maybe not so well) solution direction

With quite some exploration of what might and might not work, the idea would be leveraging canvas as a middle ground for rendering the SVG (as background image) as well as using the toBlob to prepare for download.

What works (for most browser)

A simplified version as follow:

// 0. The API return is expected to be base64 encoded SVG string
// 1. parse the SVG base64 text back to SVG with atob()
let svgTxt = atob(base64Res);
// 2. make a div and put the svg text as inner html
const svgContainerElem = document.createElement("div");
svgContainerElem.innerHTML = svgTxt;
// 3. Do some manipulation using DOM manipulation on the svg node
// ...
// 4. get back the modified SVG text
const svgTextUpdated = svgContainerElem.innerHTML;
// 5. convert the SVG text into base64 str and prepare to put to canvas
const svgdataUriStr = `data:image/svg+xml;utf8,${encodeURIComponent(svgTextUpdated)}`;
// 6. create an Image object and prepare to load the data
const img = new Image();
let blob2Download;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// 6.1 make the blob ready for download
canvas.toBlob((blob)=>{
blob2Download = blob;
},"image/png");
}
img.src=svgdataUriStr;
// 7. set the bsae64 dataUri as background image of the target Div
const bgStr = `url('${svgdataUriStr}')`;
const divToDisplay = document.getElementById("DivToDisplay");
divToDisplay.style.backgroundImage = bgStr;

What does not work for IE

IE not support toBlob()

Checking with can I use (caniuse.com), it say it support with prefix ms

The code for msToBlob() is called differently:

// calling toBlob()
canvas.toBlob((blob)=>{
blob2Download = blob;
},"image/png");
// calling IE msToBlob()
blob2Download = canvas.msToBlob("image/png");

The Image onload() failed

The reason is IE seems does not support loading the data Uri of SVG, it seemed to me the issue is about the format of the input I pass to the Image object as source

There have been multiple attempts I tried and they all failed:

// Approach 0: utf8 with encoded svg string like <svg>...</svg>
svgdataUriStr = `data:image/svg+xml;utf8,${encodeURIComponent(svgTextUpdated)}`;
// Approach 1: using blob => security error (anything from SVG is cross origin...)
const svgData = new XMLSerializer().serializeToString(svgElement);
const blob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"});
svgdataUriStr = URL.createObjectURL(blob);
// Approach 2: using base64 SVG => security error (anything from SVG is cross origin...)
svgdataUriStr = `data:image/svg+xml;base64,${btoa(svgTextUpdated)}`;

Reason behide: Security Error when calling toDataUrl() or msToBlob()

With further debug, it’s discovered that the security

When using the same implementation, IE would complain with a Security Error.

And most of the time, you would get suggestion on the web about setting img attribute crossOrigin to “anonymous”, and according to the MDN page, it sounds legit.

The problem is called tainted canvas, which does not allow reading of content from canvas if it’s from other origin (unless your other origin, the source that provide the image, allow your site to retrieve the image)

But my problem is, the SVG is created locally and there is nothing about CORS (cross origin), so what happened? The same article actually have a note about it:

If the source of the foreign content is an HTML <img> or SVG <svg> element, attempting to retrieve the contents of the canvas isn't allowed.

Because my source to the canvas is an SVG, so it’s blocked (only by IE, weird)

Final solution

What I use is a library called canvg, what it does, according to my limited understanding (by reading their code), it seems to parse the SVG and convert them to normal HTML element like Div and translate the positioning.

This library is super cool, it claimed to support different browsers, even there might be some tuning needed at the source SVG for some font styling and sizing, but those are much smaller problem to deal with and manageable.

Sample code:

const svgElement = svgContainerElem.querySelector("svg");
const svgData = new XMLSerializer().serializeToString(svgElement);
const canvgProcess = await Canvg.fromString(ctx, svgData);
canvgProcess.render();
const bgStr = `url('${canvas.toDataURL()}')`
const divToDisplay = document.getElementById("DivToDisplay");
divToDisplay.style.backgroundImage = bgStr;

Additional items that bugged me (but minor compare to above)

I would like to create some inner block like:

<g transform=translate(165 180)>
<text>Some text</text>
</g>

When doing SVG node manipulation, most browsers support something like:

let gElem = document.createElementNS('http://www.w3.org/2000/svg','g');      
gElem.setAttribute("transform", "translate(165 180)");
// simply assign the SVG text element as inner html text
gElem.innerHTML = `<text>Some text</text>`;

But IE require a more proper way to create elements:

let gElem = document.createElementNS('http://www.w3.org/2000/svg','g');      
gElem.setAttribute("transform", "translate(165 180)");
let textElem = document.createElementNS('http://www.w3.org/2000/svg','text');
textElem.textContent = "Some text";
gElem.appendChild(textElem)

Conclusion

IE is going to retire (as planned) in Jun 2022, I wish it rest in peace as supporting IE is always haunting a lot of developers.

I believe some of the above code might not work in future as canvas seems to be source of vulnerabilities and so I anticipate browsers would tighten the security on reading data from canvas, let’s see…