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:
- 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). - 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.
- 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
How to Overwrite Angular 2 Material Styles
How to Change the Text Color of First Select Option
When Does CSS's !Important Declaration Not Work
Sass Indented Syntax on Multiple Lines
Are Alternate Nested Styles Possible in CSS
First Letter Capitalize and Other Letters in Lower Case in CSS
CSS Display: Table Min-Height Not Working
How to Stop Jquery Mobile to Apply Styles to My Specific Form Elements
Less Mixin a Variable Class Name
Repeat CSS Background Image a Set Number of Times
How to Prevent CSS Gradient Banding
CSS "Outline" Different Behavior Behavior on Webkit & Gecko
Vertical Align Table-Cell Don't Work with Position Absolute
Bug in CSS3 Rotatey Transition on Safari
Order of Prioritization When Using Multiple Contradictory CSS Files