0

I have data from a database that draws a graph using NVD3. I then want to work toward PDFing the report page with dompdf. It seems that the "basic SVG support" of dompdf does not work well with my bar chart. So I figure I will convert the SVG to a PNG fisrt.

This answer works well for displaying the SVG as a PNG without styling: https://stackoverflow.com/a/19269812

Code:

var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="'  + el.attr('class') +'"'
+ ' width="'  + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
    ctx.drawImage(img, 0, 0);
    alert('ok');
    DOMURL.revokeObjectURL(url);
};
img.src = url;

There is another answer on the same question that addresses the styling, but I can't get the image to open at the correct size on the same screen i.e. without opening a new tab (even then, the image is cropped). https://stackoverflow.com/a/38085847

Code:

var style = "\n";
var requiredSheets = ['phylogram_d3.css', 'open_sans.css']; // list of required CSS
for (var i=0; i<document.styleSheets.length; i++) {
    var sheet = document.styleSheets[i];
    if (sheet.href) {
        var sheetName = sheet.href.split('/').pop();
        if (requiredSheets.indexOf(sheetName) != -1) {
            var rules = sheet.rules;
            if (rules) {
                for (var j=0; j<rules.length; j++) {
                    style += (rules[j].cssText + '\n');
                }
            }
        }
    }
}

var svg = d3.select("svg"),
    img = new Image(),
    serializer = new XMLSerializer(),

// prepend style to svg
svg.insert('defs',":first-child")
d3.select("svg defs")
    .append('style')
    .attr('type','text/css')
    .html(style);


// generate IMG in new tab
var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
window.open().document.write('<img src="' + img.src + '"/>');

So, using Javascript, how can I convert an SVG to a PNG? I'm trying combinations of the two, but I think my problem is that the d3 selector used in the second snippet is too different from the SVG markup method used in the first:

   var style = "\n";
    var requiredSheets = ['default-blue-white.css']; // list of required CSS
    for (var i=0; i<document.styleSheets.length; i++) {
        var sheet = document.styleSheets[i];
        if (sheet.href) {
            var sheetName = sheet.href.split('/').pop();
            if (requiredSheets.indexOf(sheetName) != -1) {
                var rules = sheet.rules;
                if (rules) {
                    for (var j=0; j<rules.length; j++) {
                        style += (rules[j].cssText + '\n');
                    }
                }
            }
        }
    }


    var svgX = d3.select("svg");

    // prepend style to svg
    svgX.insert('defs',":first-child");
    d3.select("svg defs")
        .append('style')
        .attr('type','text/css')
        .html(style);



    var el = $($('svg')[0]);
    var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
        + ' class="'  + el.attr('class') +'"'
        + ' width="'  + el.attr('width') +'"'
        + ' height="' + el.attr('height') +'"'
        + '>'
        + $('svg')[0].innerHTML.toString()+'</svg>';
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    var DOMURL = this.URL || this.webkitURL || this;
    var img = new Image();
    var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
    var url = DOMURL.createObjectURL(svg);
    img.onload = function() {
        ctx.drawImage(img, 0, 0);
        alert('ok');
        DOMURL.revokeObjectURL(url);
    };
    img.src = url;
Warren
  • 1,984
  • 3
  • 29
  • 60

1 Answers1

0

The production of a <svg> by cobbling up its attributes is much too naive, as you could see from the croppings and false sizes.

Going via a data URL is a viable way, but instead of opening that in a new window (where it would be a SVG image, not a PNG), write it to canvas.

Do not insert the stylesheets into the DOM with d3.js selection.html(), but use plain Javascript document.createCDATASection() and element.appendChild() to avoid ending up with invalid XML.

The following will replace the <svg> with a <canvas> in its place, and with the same size as it is rendered in the browser.

var styleRules = [];
var requiredSheets = [...]; // list of required CSS files
for (var sheet of document.styleSheet)) {
    if (sheet.href) {
        var sheetName = sheet.href.split('/').pop();
        if (requiredSheets.indexOf(sheetName) != -1) {
            var rules = Array.from(sheet.cssRules).map(rule => rule.cssText);
            styleRules = styleRules.concat(rules);
        }
    }
}
var styleText = styleRules.join(' ');
var styleNode = document.createCDATASection(styleRules);

var svg = d3.select("svg"),
    img = new Image(),
    serializer = new XMLSerializer(),

// prepend style to svg
svg.insert('defs',":first-child")
var styleEl = d3.select("svg defs")
    .append('style')
    .attr('type','text/css');

styleEl.node().appendChild(styleNode);

var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));

var bbox = svg.getBoundingClientRect();

var canvas = document.createElement("canvas");

canvas.width = bbox.width;
canvas.height = bbox.height;
canvas.getContext("2d").drawImage(img,0,0,bbox.width,bbox.width);

canvas.parentNode.replaceChild(canvas, svg);
ccprog
  • 20,308
  • 4
  • 27
  • 44