简体   繁体   中英

Server Side d3 - Encoding SVG as a Base64 Image

I'm trying to encode a D3 chart as a base64 image for use in HTML emails

So far I have:

var express = require('express');
var app = express();
var jsdom = require('jsdom');

app.get('/chart', function (request, response) {
    try {
        jsdom.env(
            "<html><body><svg id=\"svg\"></svg></body></html>",
            ['http://d3js.org/d3.v3.min.js'],
            function (err, window) {
                var svg = window.d3.select("svg")
                    .attr("width", 100)
                    .attr("height", 100);

                svg.append("rect")
                    .attr("x", 10)
                    .attr("y", 10)
                    .attr("width", 80)
                    .attr("height", 80)
                    .style("fill", "orange");

                var encoded = ...; // How do I now encode this?

                response.send({
                    "html": window.d3.select("body").html(),
                    "encoded": encoded
                });
            }
        );
    } catch (err) {
        console.error(err);
    }
});

// Run Server
var port = process.env.PORT || 8305;
var server = app.listen(port, function () {
    var host = server.address().address;
    console.log('App listening at http://%s:%s', host, port);
});

This outputs the SVG html which is useful however I would also like a second response field encoded which should contain something like below that I can use in my HTML email:

"encoded": "data:image/png;base64,iVBORw0KGgoAAAANSUhEU...etc"

How do I encode this SVG? I would like to avoid any file writing if possible and go straight from SVG to encoded image. Also, I'm aware you can use SVGs in emails but I would much rather it be in an encoded image format. Thanks!

Set the "xmlns" attribute at root <svg> node to "http://www.w3.org/2000/svg" at <svg> node within document or with .attr() .

The data URI scheme consists of

data:[<media type>][;base64],<data>

Since we know that the <media type> will be image/svg+xml and that we want base64 representation of resulting data, we can define a variable to which we will concatenate base64 representation of <svg> .

let data = "data:image/svg+xml;base64,";

Then we get the .outerHTML of <svg> element

// optionally preceded by `XML` declaration `<?xml version="1.0" standalone="yes"?>`
let svgString = svg[0][0].outerHTML; 

Call btoa() with svgString as parameter

The btoa(data ) method must throw an " InvalidCharacterError " DOMException if data contains any character whose code point is greater than U+00FF. Otherwise, the user agent must convert data to a sequence of octets whose nth octet is the eight-bit representation of the code point of the n th character of data , and then must apply the base64 algorithm to that sequence of octets, and return the result. [RFC4648]

let base64 = window.btoa(svgString);
// concatenate `data` and `base64`
let dataURI = data + base64;

response.send({
                "html": window.d3.select("body").html(),
                "encoded": dataURI
             });

 let svg = window.d3.select("svg") .attr("xmlns", "http://www.w3.org/2000/svg") .attr("width", 100) .attr("height", 100); svg.append("rect") .attr("x", 10) .attr("y", 10) .attr("width", 80) .attr("height", 80) .style("fill", "orange"); let data = "data:image/svg+xml;base64,"; let svgString = svg[0][0].outerHTML; let base64 = window.btoa(svgString); let dataURI = data + base64; console.log(dataURI); document.querySelector("iframe").src = dataURI; 
 <script src="https://d3js.org/d3.v3.min.js"></script> <svg></svg> <iframe></iframe> 

Full solution which builds a d3 chart using NodeJS and converts it to an encoded PNG without using file storage (neccessary for most cloud hosting)

let express = require('express');
let app = express();

let jsdom = require('jsdom');
let Buffer = require('buffer').Buffer;
let svg2png = require('svg2png');

app.post('/chart', function (request, response) {
    try {
        jsdom.env(
            "<svg id=\"svg\"></svg>",
            ['http://d3js.org/d3.v3.min.js'],
            function (err, window) {
                let svg = window.d3.select("svg")
                    .attr("width", 100)
                    .attr("height", 100)
                    .attr("xmlns", "http://www.w3.org/2000/svg");

                svg.append("rect")
                    .attr("x", 10)
                    .attr("y", 10)
                    .attr("width", 80)
                    .attr("height", 80)
                    .style("fill", "orange");

                let data = "data:image/png;base64,";
                let svgString = svg[0][0].outerHTML;
                let buffer = new Buffer("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + svgString);
                let promise = svg2png(buffer, {width: 300, height: 400}); // This options block is optional but height and width should be specified on the SVG HTML otherwise
                promise.then(buffer => {
                    let dataURI = data + buffer.toString('base64');
                    response.set('Content-Type', 'application/json');
                    response.send({
                        "html": svgString,
                        "encoded": dataURI
                    });
                });
            }
        );
    } catch (err) {
        console.warn(err);
    }
});

// Run Server
let port = process.env.PORT || 8305;
let server = app.listen(port, function () {
    let host = server.address().address || 'localhost';
    console.log('Example app listening at http://%s:%s', host, port);
});

The encoded field in the response can be used in a HTML email using:

<img src="{encoded-value}"/>

eg

<img src="data:image/png;base64,iVBORw0KGgoAAA..."/>

In order to send it as a png image, you would first have to rasterize the svg, which is the actual problem. This has been answered in several places, but this one contains a number of good solutions. You should probably use imagemagick for the conversion. Since you want to avoid writing to an intermediate file, that answer suggests you can pipe the svg contents to the converter's stdin.

The received buffer could probably be directly encoded to base64 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM