简体   繁体   中英

Convert SVG to PNG with styles

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 ie 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;

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);

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