Convert SVG with image pattern and clipPath to PNG - javascript

I need to convert a svg into a png. I tried using this code
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: #000;
fill-opacity: .8;
}
</style>
<body>
<div id="svg">
<?php include 'small.svg'; ?>
</div>
<button id="save">Save as Image</button>
<h2>SVG dataurl:</h2>
<div id="svgdataurl"></div>
<h2>SVG converted to PNG dataurl via HTML5 CANVAS:</h2>
<div id="pngdataurl"></div>
<canvas width="960" height="500" style="display:none"></canvas>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var svg = document.querySelector( "svg" );
var svgData = new XMLSerializer().serializeToString( svg );
var canvas = document.createElement( "canvas" );
var ctx = canvas.getContext( "2d" );
var img = document.createElement( "img" );
img.setAttribute( "src", "data:image/svg+xml;base64," + btoa( svgData ) );
img.onload = function() {
ctx.drawImage( img, 0, 0 );
// Now is done
console.log( canvas.toDataURL( "image/png" ) );
};
document.getElementById("pngdataurl").appendChild(img);
</script>
but it doenst work. im pretty sure it's because im using an image pattern, rect and a clip path to product my SVG. I say that because i tried this code with just the path object and it worked fine. I also tried using image magic with this code
<?php
error_reporting(E_ALL);
$svg ="small.svg";
$mask = new Imagick('mask.png');
$im = new Imagick();
//$im->setBackgroundColor(new ImagickPixel('transparent'));
$im->readImageBlob($svg);
$im->setImageFormat("png32");
$im->compositeImage( $mask, imagick::COMPOSITE_DEFAULT, 0, 0 );
header('Content-type: image/png');
echo $im;
?>
Fatal error: Uncaught exception 'ImagickException' with message 'no decode delegate for this image format `' # error/blob.c/BlobToImage/346' in /home/[path to file]:10 Stack trace: #0 /home/path to file: Imagick->readimageblob('small.svg') #1 {main} thrown in /home/[path to file] on line 10
i would rather do this with js if possible. please help...my boss really hates me right now. Here is my svg
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3384.2 2608.6" enable-background="new 0 0 3384.2 2608.6" xml:space="preserve">
<defs>
<pattern id="img1" patternUnits="userSpaceOnUse" width="100%" height="100%">
<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/7/71/Weaved_truncated_square_tiling.png" x="0" y="0" width="100%" height="100%" />
</pattern>
</defs>
<rect width="3384.2" height="2608.6" clip-path="url(#shirt)" fill="url(#img1)" />
<clipPath id="shirt">
<path fill-rule="evenodd" clip-rule="evenodd" fill="url(#img1)" d="[coordinates that are too long for this post ]"/>
</clipPath>
</svg>

I had this issue recently too, at least with the pattern fills - it seems to be an issue with remote images, so if you base64 encode them into a data URL, it works fine:
var img = new Image();
img.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext('2d').drawImage(img, 0, 0);
var patternImage = document.createElementNS('http://www.w3.org/2000/svg', 'image');
patternImage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', canvas.toDataURL());
};
img.src = patternImageUrl;
You can then add/update this image in a pattern def (or do this once and hardcode the result of canvas.toDataURL() into the pattern image href).

Related

How to convert svg string to svg file using Python?

Using AJAX I send a svg image to Django using the following function:
function uploadSVG(){
var svgImage = document.getElementById("SVG");
var serializer = new XMLSerializer();
var svgStr = serializer.serializeToString(svgImage);
$(document).ready(function(){
$.post("ajax_upload_svg/",
{
csrfmiddlewaretoken: csrftoken,
svgImage: svgStr
},
function(){
console.log('Done')
});
});
}
In Django I end up with the svg image as a string using the following function:
def uploadSVG(request):
svgImg = request.POST.get('svgImage')
return HttpResponse('')
The string I get looks like this:
<svg xmlns="http://www.w3.org/2000/svg" id="SVG" width="460" height="300" style="border:2px solid #000000"><rect x="150" y="70" width="160" height="130" fill="#292b2c"/></svg>
How can I convert this svg string into a svg file?
The solution is:
with open("svgTest.svg", "w") as svg_file:
svg_file.write(svgImg)

Using XPath to get an element in SVG

I am trying to get an element in an SVG file using XPath. I have tried the following code, but singleNodeValue is null. The doc seems to be correct, so I guess either evaluate() arguments or the XPath is wrong, but I cannot find anything wrong. Why doesn't it work?
JavaScript
fetch('test.svg')
.then(response => response.text())
.then(data=>{
const parser = new DOMParser();
const doc = parser.parseFromString(data, "text/xml");
console.log(doc);
const res = doc.evaluate("//symbol[#label='square']", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
console.log(res.singleNodeValue);
})
SVG
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<symbol label ="square">
<rect y="5" x="5" width="90" height="90" stroke-width="5" stroke="#f00" fill="#f00" fill-opacity="0.5" />
</symbol>
</svg>
After some testing, I have discovered that it works if I remove xmlns="http://www.w3.org/2000/svg". I searched the web and found an answer: Why XPath does not work with xmlns attribute
var data = '<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><symbol label ="square"><rect y="5" x="5" width="90" height="90" stroke-width="5" stroke="#f00" fill="#f00" fill-opacity="0.5" /></symbol></svg>';
const parser = new DOMParser();
const doc = parser.parseFromString(data, "text/xml");
const res = doc.querySelector("symbol[label=square]");
console.log(res);
You can make use of a namespace resolver in XPath 1 and with the evaluate function and even of an XPath default namespace in XPath 2 and later such as supported by Saxon-JS 2:
const svgString = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"
xmlns:dog = "http://dog.com/dog">
<symbol dog:label="square">
<rect y="5" x="5" width="90" height="90" stroke-width="5" stroke="#f00" fill="#f00" fill-opacity="0.5" />
</symbol>
<symbol dog:label="cat">
<rect y="5" x="5" width="90" height="90" stroke-width="5" stroke="#f00" fill="#f00" fill-opacity="0.5" />
</symbol>
</svg>`;
const doc = new DOMParser().parseFromString(svgString, 'application/xml');
const result = doc.evaluate(`//svg:symbol[#dog:label='cat']`, doc, function(prefix) { if (prefix === 'svg') return 'http://www.w3.org/2000/svg'; else if (prefix === 'dog') return 'http://dog.com/dog'; else return null; }, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
console.log(result.singleNodeValue);
const result2 = SaxonJS.XPath.evaluate(`//symbol[#dog:label = 'cat']`, doc, { xpathDefaultNamespace : 'http://www.w3.org/2000/svg', namespaceContext : { 'dog' : 'http://dog.com/dog' } });
console.log(result2);
<script src="https://www.saxonica.com/saxon-js/documentation/SaxonJS/SaxonJS2.rt.js"></script>

Error loading svg data with html2canvas and jspdf

I have a very simple svg
<div id="printId">
test
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" />
</svg>
</div>
and trying to print it with html2canvas and jspdf. But whatever I do I get an error
Error loading svg data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%22100%22%20width%3D%22100%22%20style%3D%22-webkit-writing-mode%3A%20horizontal-tb%3B%20-webkit-user-modify%3A%20read-only%3B%20-webkit-user-drag%3A%20auto%3B%20-web
Oe.error # html2canvas-1.1.2.min.js:20
and test.pdf file with just word test in it.
I googled this for about 3 hours, and in every place people recommend settings width/height atributes. Which, as you can see, doesn't help in my case.
I've simplified it as much as I could. Here is the full index.html I use:
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="printId">
test
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" />
</svg>
</div>
<script src="../../../script/jsPDF/jspdf-2.3.1.umd.min.js" defer></script>
<script src="../../../script/jsPDF/html2canvas-1.1.2.min.js" defer></script>
<script>
function saveAsPDF(divId, usersName, certNumber = '', type = 'old')
{
const { jsPDF } = window.jspdf;
const pdf = new jsPDF();
var source = document.getElementById("printId");
pdf.html(
source, {
callback: function (pdf) {
pdf.save("test.pdf");
},
});
}
</script>
<input type='submit'
onclick='saveAsPDF();'
value='save'>
</body>
</html>
I also tried all possible versions of html2canvas:
1.1.2
1.1.5
1.2.2
1.3.2
and two versions of jspdf:
2.1.1
2.3.1
And two browsers: Chrome and Opera.
What can be my issue?
Edit:
I managed to make html2canvas work on it's own with the document below. Now the question is how to make it work with jspdf together:
<html>
<head></head>
<body>
<div id="printId">
this is circle:
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" />
</svg>
</div>
<hr>
<h4>Image of above HTML content...</h4>
<div id="canvasImg"></div>
<script src="../../../script/jsPDF/html2canvas-1.3.2.min.js"></script>
<script>
html2canvas(document.getElementById('printId')).then(function(canvas) {
var canvasImg = canvas.toDataURL("image/jpg");
document.getElementById('canvasImg').innerHTML = '<img src="'+canvasImg+'" alt="">';
});
</script>
</body>
</html>
It looks like jspdf is not able to add svg as vector graphic to a page. There are three options:
Use svg2pdf instead of jspdf to convert svg to pdf on it's own.
Use addSvgAsImage function of jspdf to place an svg in the pdf page manually.
Convert svg to jpg first, then use jspdf.
I've chosen the 3rd option and wrote a function, which I run before saving canvas as pdf:
htmlToJpg: function()
{
var canvas = document.querySelector('.to-print');
screenWidth = parseFloat(window.getComputedStyle(canvas).width);
var problematic = document.querySelectorAll('.convert-to-jpg');
problematic.forEach(function(el) {
html2canvas(el,
{
scale: 2480/screenWidth // 2480px - size for A4 paper, 300 dpi
}
)
.then(function(canvas) {
var img = canvas.toDataURL("image/jpeg");
el.innerHTML = '<img src="' + img + '" class="img">';
});
});
}
import html2canvas from 'html2canvas';
import { jsPDF as JsPDF } from 'jspdf';
import { useCallback, useEffect } from 'react';
export const useDownloadPdf = (name: string, isReady: boolean) => {
useEffect(() => {
if (isReady) {
const fileName = `${name}.pdf`;
const pdf = new JsPDF({
orientation: 'p',
unit: 'mm',
format: 'a4',
putOnlyUsedFonts: true,
});
const convertElements = document.querySelectorAll('.convert-on-pdf');
const elements = Array.from(convertElements) as HTMLElement[];
if (elements.length > 0) {
Promise.all(
elements.map(async (element) => {
const canvas = await html2canvas(element);
element.replaceWith(canvas);
}),
).then(() => {
pdf.html(document.body, {
callback: (generatedPdf) => {
generatedPdf.save(fileName)
},
});
});
} else {
pdf.html(document.body, {
callback: (generatedPdf) => {
generatedPdf
.save(fileName)
},
});
}
}
}, [isReady, name, setAtribute]);}

How to show SVG with JavaScript in img

I have a php-file that sends an SVG header and draws itself partly with JavaScript. Everything works fine when I show the SVG directly in the browser. However, when I embed it in an <img>, then the JavaScript seems not to be executed. Maybe I am doing something wrong?
Here is the php-file (testsvg.php):
<?php
header('Content-type: image/svg+xml');
?>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="800"
height="600"
id="example_text">
<g id="layer1">
<text
x="100"
y="100"
id="text_holder">
<?php echo filter_input(INPUT_GET, 'text'); ?>
</text>
</g>
<script type="text/javascript">
function create_from_rect (client_rect, offset_px) {
if (! offset_px) {
offset_px=0;
}
var box = document.createElementNS(
document.rootElement.namespaceURI,
'rect'
);
if (client_rect) {
box.setAttribute('x', client_rect.left - offset_px);
box.setAttribute('y', client_rect.top - offset_px);
box.setAttribute('width', client_rect.width + offset_px * 2);
box.setAttribute('height', client_rect.height + offset_px * 2);
}
return box;
}
function add_bounding_box (text_id, padding) {
var text_elem = document.getElementById(text_id);
if (text_elem) {
var f = text_elem.getClientRects();
if (f) {
var bbox = create_from_rect(f[0], padding);
bbox.setAttribute(
'style',
'fill: none;'+
'stroke: black;'+
'stroke-width: 0.5px;'
);
text_elem.parentNode.appendChild(bbox);
}
}
}
add_bounding_box('text_holder', 10);
</script>
</svg>
(The SVG/JavaScript is mostly taken from How can I draw a box around text with SVG?.)
When I open this file directly in the browser, e.g. with http://localhost/testsvg.php?text=Test it draws a proper box around the text Test.
However, when I embed it with an <img>, e.g. with <img src="testsvg.php?text=Test" alt="">, it does not show the box.
To protect user's privacy, script is not run in images. There are other restrictions.
When thinking of an image it helps to have the mental model "could I do this if I used a raster image"? If you bear that in mind you won't go far wrong as that's basically the model the browsers implement.
Your alternatives are to use <object> or <iframe> tags instead of <img> as they do allow scripting.

Writing current time to SVG document

I am trying to add current time to an SVG file using ECMAScript.
Following is the code I have. Unfortunately it's not working. How do I fix it?
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300">
<text x="0" y="15" fill="red">It is now
<script type="text/javascript">
<![CDATA[
var currentTime = new Date();
var month = currentTime.getMonth() + 1;
var day = currentTime.getDate();
var year = currentTime.getFullYear();
document.write(month + "/" + day + "/" + year);
]]>
</script>
</text>
</svg>
Demo: http://jsfiddle.net/M8YXS/
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300">
<text x="0" y="15" fill="red">It is now <tspan id="now">(time)</tspan></text>
<!--
This script block must either be after the element in the source code
or else the code below must be wrapped in a callback that is invoked
only after the DOM is created (e.g. window.onload in a browser)
-->
<script>
// Create whatever string you want
var dateString = (new Date).toDateString();
// Set the string content of the Text Node child of the <tspan>
document.getElementById('now').firstChild.nodeValue = dateString;
</script>
</svg>

Categories

Resources