How to Include CSS Style When Converting Svg to Png

How to Include CSS style when converting svg to png

Question 1 (first half): what's the real reason (is the GPU acceleration related in anyway?)

No, GPU acceleration has nothing to do with it.

The broadest reason is privacy.

To draw your svg with drawImage you have to load your svg as an external document inside an <img> tag. SVG can be quite a complex image format for resources loading (it can literally require any kind of resource that could be required by any HTML document). Thus, it has been stated in specs that the same security as the one for <iframe> elements or <object> or similar ones should apply to <img> content and even stricter :

<img> content can not require any external resources, nor access to the main document.

Question 1 (second half): and the solution for this issue

You pointed to some SO questions already answering it, you could also just include all the stylesheets from the main document inside a <style> tag inside your parsed svg Node, before you do create the Blob from it. dumb implementation here

Question 2 : "how i still overcome this issue when using a custom font with Fontface"

For external resources, you have to encode it as dataURI, and include it in your svg node before you create the Blob. For font in particular, you'd set a font-face property in a <style> element.

So at the end, your svg would have something like

<defs>
<style>
/* all your parsed styles in here */
@font-face {
font-family: foo;
src: url('data:application/font-woff;charset=utf-8;base64,...')
}
</style>
</defs>

in itself before you do extract its markup.

Adding css style when converting svg to png

Essentially, you need to move the styles "inline" on the elements. Your g axis path for example goes from:

<g class="x axis" transform="translate(0,450)">
<path class="domain" d="M0,6V0H440V6"></path>
</g>

To:

<g class="x axis" transform="translate(0,450)">
<path class="domain" d="M0,6V0H440V6"
style="fill: none; stroke: #000; stroke-width: 1px; shape-rendering: crispEdges;">
</path>
</g>

There's some recursion tricks out there to do this but full recursion across all the elements is probably a bit of overkill (and gonna be slow).

I would either manually move the styles in line or do something to target the elements you care about. For instance, here's how your could fix the axis lines:

d3.selectAll('.axis path, .axis line, .axis').each(function() {
var element = this;
var computedStyle = getComputedStyle(element, null);
for (var i = 0; i < computedStyle.length; i++) {
var property = computedStyle.item(i);
var value = computedStyle.getPropertyValue(property);
element.style[property] = value;
}
});

Full working example:

<!DOCTYPE html><meta charset="utf-8"><style>
.axis { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .x.axis path { display: none; }</style>
<body>
<button id="save">Save as Image</button>
<div id="svgdataurl"></div>
<span id="h3"> </span>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> <script> var margin = { top: 20, right: 20, bottom: 30, left: 40 }, width = 500 - margin.left - margin.right, height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear() .range([0, width]);
var y = d3.scale.linear() .range([height, 0]);
var xAxis = d3.svg.axis() .scale(x) .orient("bottom");
var yAxis = d3.svg.axis() .scale(y) .orient("left");
var svg = d3.select("#h3").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([0, 100]); y.domain([0, 100]);
svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis);
svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end");
d3.select("#save").on("click", function() {
d3.selectAll('.axis path, .axis line, .axis').each(function() { var element = this; var computedStyle = getComputedStyle(element, null); for (var i = 0; i < computedStyle.length; i++) { var property = computedStyle.item(i); var value = computedStyle.getPropertyValue(property); element.style[property] = value; } });
var html = d3.select('#h3 svg') .attr("version", 1.1) .attr("xmlns", "http://www.w3.org/2000/svg") //HERE I WAS TRYING WITH .style but I want to link all the css classes from .css file below// .node().parentNode.innerHTML;
var imgsrc = 'data:image/svg+xml;base64,' + btoa(html); var img = '<img src="' + imgsrc + '">'; d3.select("#svgdataurl").html(img); }); </script>

Exporting svg to png or other image with styling, using JavaScript

This is because c3 sets some rules from the parent div container, which has a class="c3" attribute.

In particular, you will find a

.c3 path, .c3 line {
fill: none;
stroke: rgb(0, 0, 0);
}

rule.

This rule won't match anymore when you will convert your svg to a standalone.

To workaround this, you might simply set this class on the parent svg node instead.

svg.classList.add('c3');

But you will loose the

.c3 svg {
font: 10px sans-serif;
-webkit-tap-highlight-color: transparent;
}

rule.
So you might have to set it yourself, and convert it to e.g .c3 svg, svg.c3 {...

Alternatively, you could getComputedStyle all the nodes of your svg, and filter out the defaults ones, but that would still be a lot of work to do for the browser...

Styling errors when converting inline SVG to png

I've been searching myself for a solution to export PNG with CSS created through Rickshaw (based on D3). The sole solution I found was to:

  • treat the DIVs different from the SVGs, and treat them all individually
  • convert the DIVs (and other non-SVG content) with html2canvas to canvas
  • make the CSS inline to the SVG; @thirdcreed has posted the JavaScript code and D3 selectors for that at: Rickshaw CSS/Axes in JSDOM - adapt that to your custom CSS as needed.
  • convert the SVGs into canvas with code such as

    var imgsrc = 'data:image/svg+xml;base64,'+ btoa(html2);
    var img = '<img src="'+imgsrc+'">';
    var canvas = document.querySelector("canvas"),
    context = canvas.getContext("2d");
    var image = new Image;
    image.src = imgsrc;
    image.onload = function() {
    context.drawImage(image, 0, 0);
    }
  • merge the different canvases you have into one
  • convert into image with code such as:

    var canvasdata = canvas.toDataURL("image/png");
    var pngimg = '<img src="'+canvasdata+'">';
    d3.select("#pngdataurl").html(pngimg); // contains selector from D3, adjust if you don't use D3
    var a = document.getElementById("some_anchor"); // Fix for Firefox: supply an anchor-tag here that is 'display:none' in your document, otherwise download won't work
    a.download = "sample.png";
    a.href = canvasdata;
    a.click();

Note that every browser expect for Internet Explorer requires the SVGs to have the xmlns attribute.

Including fonts when converting SVG to PNG

I'm able to include the font in the png itself with the following code, give it a try

var svg = document.getElementById('generated-svg');
var svgData = new XMLSerializer().serializeToString( svg );

var canvas = document.createElement("canvas");
canvas.width = 300;
canvas.height = 500;
var ctx = canvas.getContext("2d");

//display image
var img = document.createElement( "img" );
img.setAttribute( "src", "data:image/svg+xml;base64," + btoa( svgData ) );


img.onload = function() {
ctx.drawImage( img, 0, 0 );

//image link
console.log( canvas.toDataURL( "image/png" ) );


//open image
window.location.href=canvas.toDataURL( "image/png" );
};

https://jsfiddle.net/user3839189/hutvL4ks/1/

SVG to PNG retaining CSS using javascript

Update:

I understand that you are using an external stylesheet for your SVG markup. So I think you need a three-step solution:

  1. Make the stylesheet declarations that apply to the SVG markup inline. This is done best client-side. I do not have a solution in source code right now, but it should be possible to use W3C DOM Level 2 Style interface implementations to find out the selectors that apply to an element, and the declarations that have been used in the corresponding blocks (document.defaultView.getComputedStyle() alone will probably result in an SVG fragment having too many inline declarations).
  2. Convert SVG markup with inline stylesheets to PNG. This is best done server-side (e. g., with ImageMagick), so you would need to send the SVG markup to the server.
  3. Serve the PNG resource to the user.

Those two steps could be performed on form submission where in the onsubmit attribute you do step #1 and then call the form's submit() method.



Related Topics



Leave a reply



Submit