XMLSerializer mess up links - svg

I have a svg object with defs:
defs.append('svg:pattern')
.attr('id', 'blue')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', '40')
.attr('height', '39')
.append('svg:image')
.attr('xlink:href', 'images/blue.png')
.attr('width', '40')
.attr('height', '39');
In my code I use XMLSerializer to POST data:
var svg1 = tmp.getElementsByTagName("svg");
var svg_xml = (new XMLSerializer).serializeToString(svg1[0]);
In Safari I get the desired result:
<svg width="850" height="200">
<pattern id="blue" patternUnits="userSpaceOnUse" width="40" height="39">
<image xlink:href="images/blue.png" width="40" height="39"></image>
</pattern>
In Firefox or IE I get strange results:
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="200">
<defs>
<pattern id="blue" patternUnits="userSpaceOnUse" width="40" height="39">
<image a0:href="images/plateColor/blue.png" xmlns:a0="http://www.w3.org/1999/xlink" width="40" height="39"/>
</pattern>
What am I doing wrong?
Thanks
Rolf

This is not a bug in D3.js. Here's how you can solve the problem for any SVG:
First, grab your SVG element:
svg = document.querySelector("svg");
Now, an SVG element can have more than one of these attributes in different namespaces, so we have to remove these first to prevent duplicate attributes. Otherwise the SVG is no longer valid after being serialized to a string by XMLSerializer:
svg.removeAttribute("xmlns");
svg.removeAttribute("xmlns:xlink");
Finally, add the same attributes, but with the proper namespace:
prefix = "http://www.w3.org/2000/xmlns/";
if (!svg.hasAttributeNS(prefix, "xmlns")) {
svg.setAttributeNS(prefix, "xmlns", "http://www.w3.org/2000/svg");
}
if (!svg.hasAttributeNS(prefix, "xmlns:xlink")) {
svg.setAttributeNS(prefix, "xmlns:xlink", "http://www.w3.org/1999/xlink");
}
Now the XMLSerializer should no longer produce a weird output.

Related

How do I <use> a named <tspan> element in an SVG document?

I'm trying to define a tspan early in an SVG document so it can be transcluded later inside a text. At least in Firefox, the following code does not produce this result.
<svg version="1.1" width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<defs>
<tspan id="transcluded-text">This is a text</tspan>
</defs>
<text>
<use href="#transcluded-text"/>
</text>
</svg>
Using Firefox's Inspect tool, the use element contains a shadow DOM (#shadow-root) as expected, but the shadow DOM itself is empty.
Without using Javascript, is it possible to transclude a tspan inside a text like this?
The text element can only contain text content child elements (so, not <use>) and the only text related element the defs element can contain is <text>. So, it all points to that the <tspan> cannot be used in this way.
<svg version="1.1" width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<defs>
<text x="10" y="20" font-size="20" id="transcluded-text">This is a text</text>
</defs>
<use href="#transcluded-text"/>
</svg>
Since you can not use <use> elements within a <text> element;
or a bare <tspan> within <defs>
A modern Native Web Component can do the replace job:
(this will cause a FOUC you might want to deal with)
<script>
customElements.define("svg-use-replacer", class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // make sure SVG is parsed
this.querySelectorAll('use').forEach(use => {
let href = use.getAttribute("href");
let tspan = this.querySelector(href);
use.replaceWith(tspan); // .replaceWith was not available in IE
});
// if the bare <svg> is required in the DOM:
// this.replaceWith(this.querySelector("svg"));
});
}
});
</script>
<svg-use-replacer>
<svg version="1.1" width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<defs>
<tspan id="foo" stroke="green">Wonderful</tspan>
</defs>
<text x="10" y="20" font-size="20">
Hello
<use href="#foo" /> Web Component
</text>
</svg>
</svg-use-replacer>
Notes
The oldskool way is to attach a class and after that Element is parsed run JavaScript on it.
With Web Components it totally does not matter WHEN the Web Component is defined.
You can execute the above script any time you want.

Adding title element to paperjs SVG (using node.js)

I would like to add a <title> element to an SVG file generate with paper.js and its exportSVG function, within a node.js application (using paperjs-jsdom package)
So far, my files look like this :
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="642" height="377" viewBox="0,0,642,377">
<g>
...
</g></svg>
And I would like it to look like this :
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="642" height="377" viewBox="0,0,642,377">
<title>toto</title>
<g>
...
</g></svg>
I have looked into jsdom (which should be included in the paperjs-jsdom package), trying to load <svg> content in a DOM, but I have not been able to make it work.
If your concern is just adding a <title> tag after the <svg> tag, a workaround without even using JSDOM could be to use a RegExp.
Here is a sketch demonstrating the solution.
// Draw a circle.
new Path.Circle({
center: view.center,
radius: 50,
fillColor: 'orange'
})
// Export Paper.js project as SVG string.
var svg = project.exportSVG({asString:true});
// Add title after <svg> tag.
svg = svg.replace(/(^<svg[^>]+>)/, '$1<title>My Title</title>');
// Show result in console.
console.log(svg);
Result is:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1151" height="937" viewBox="0,0,1151,937">
<title>My Title</title>
<g fill="#ffa500" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal">
<path d="M525.5,468.5c0,-27.61424 22.38576,-50 50,-50c27.61424,0 50,22.38576 50,50c0,27.61424 -22.38576,50 -50,50c-27.61424,0 -50,-22.38576 -50,-50z"/>
</g>
</svg>

How to create an svg element programmatically in an svg document?

I know that we can use the document.createElementNS("http://www.w3.org/2000/svg","line"); to create a svg element embeded in a html page.
But, this method doesn't seems to work in a standalone svg document.
Actually, I am trying to draw the national flag of India in svg, but, drawing the 24 spokes in the wheel of the flag would be very time consuming. So, I thought of drawing them programmatically through JavaScript.
Any help on how to create elements programmatically in a standalone svg document will be appreciated.
You can use javascript inside an svg element. I've done only the center of the flag.
Please observe the viewBox="-50 -50 100 100". The point {x:0,y:0} is in the center of the SVG canvas.
svg{border:1px solid; width:90vh;}
<svg viewBox="-50 -50 100 100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" >
<g id="center" fill="blue" stroke="blue">
<circle r="5" />
<circle id="outline" stroke-width="4" fill="none" r="48" />
<polygon id="spoke" points="0,0 20,1.5 48,0 20,-1.5 0,0" fill="blue" stroke="none" />
<circle id="dot" r="2.5" cx="46" transform="rotate(7.5)" />
</g>
<script>
<![CDATA[
const SVG_XLINK = "http://www.w3.org/1999/xlink";
const SVG_NS = 'http://www.w3.org/2000/svg';
// a function that creates a new `<use>` element and rotates it around the origin of the SVG canvas
function reuse(used,parent,i) {
let use = document.createElementNS(SVG_NS, 'use');
use.setAttributeNS(SVG_XLINK, 'xlink:href', used); use.setAttributeNS(null,"transform" ,`rotate(${360*i/24})`);
parent.appendChild(use);
}
// a loop that creates 24 use elements
for(let i = 0;i < 24; i++ ){
reuse("#spoke",document.querySelector("#center"),i);
reuse("#dot",document.querySelector("#center"),i);
}
]]>
</script>
</svg>
You can't do what you're trying to do since Javascript shouldn't be included in, and isn't understood or processed inside a SVG file.

Using predefined SVG file for creating a custom JointJS shape with ports

I have a series of pre-created SVG symbols I want to use in JointJS.
I've searched about using precreated SVGs and I found to be possible to create a complete custom elements using SVG by putting the SVG in the 'markup' property - (https://groups.google.com/forum/#!topic/jointjs/pQvN_0lXPVk).
Below is the example of an SVG. Your help about how can I embed this definition in the markup property and add ports to it will be highly appreciated.
Thanks
<?xml version="1.0" standalone="no"?>
<svg viewBox="0 0 1024 768" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke-linecap="round" stroke-linejoin="round" fill-rule="evenodd" xml:space="preserve" >
<defs >
<clipPath id="clipId0" >
<path d="M0,768 1024,768 1024,0 0,0 z" />
</clipPath>
</defs>
<g stroke-width="0.1" clip-path="url(#clipId0)" fill="none" stroke="rgb(0,0,0)" />
<g stroke-width="0.25" clip-path="url(#clipId0)" fill="rgb(0,0,0)" stroke="none" >
<path d="M1013.96,634.98 10.0392,634.98 1013.96,133.02 z" />
</g>
<g stroke-width="0.25" clip-path="url(#clipId0)" fill="none" stroke="rgb(0,0,0)" >
<polyline points="10.0392,133.02 1013.96,133.02 1013.96,634.98 10.0392,634.98 10.0392,133.02 " />
<polyline points="10.0392,634.98 1013.96,133.02 " />
</g>
</svg>
You can add the SVGImageElement to your markup to display arbitrary SVG in your shapes. Just convert the SVG file content to dataURL and set the xlink:href attribute.
var shape = new joint.shapes.basic.Image({
// markup: '<g class="rotatable"><g class="scalable"><image/></g><text/></g>',
attrs: {
image: {
'xlink:href': 'data:image/svg+xml;utf8,' + encodeURLComponent(svgFileContent)
}
}
});
http://jsfiddle.net/kumilingus/eqen3pdf/
In order to create a shape showing an SVG image and yet having ports you can e.g. use devs.Model shape and replace the only SVGRectElement in its markup with an SVGImageElement.
new joint.shapes.devs.Model({
markup: '<g class="rotatable"><g class="scalable"><image class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
attrs: {
'.body': {
'xlink:href': 'data:image/svg+xml;utf8,' + encodeURLComponent(svgFileContent)
},
inPorts: ['in'],
outPorts: ['out']
});
http://jsfiddle.net/kumilingus/tm2ctvxq/
Note, that it's possible to insert the SVG file content directly into your markup (without <?xml version="1.0" standalone="no"?>). I would not recommended it for more complex SVG though.
For instance when SVG contains an SVGClipPathElement with an id. Creating 2 instances of your shape breaks condition that all the IDs in the SVG must be unique.

Background image fills Raphael SVG to Canvas via canvg are black

I have found the info on converting a raphael SVG into a canvas element here very helpful. But I'm a little stuck - I have a Raphael SVG drawn (it's a little ugly, sorry, but it works), and I can almost recreate it on canvas via canvg, except my fill patterns are all black. Is there something wrong in the SVG code rendered?
Here is an example of the svg code (innerHTML) of my raphael drawing:
<svg height="530" version="1.1" width="530" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="overflow-x: hidden; overflow-y: hidden; position: relative; "><desc>Created with Raphaƫl 2.0.0</desc><defs><pattern id="BDC4C7DB-1FF9-4BE5-B868-C7B68C6A7B8B" x="0" y="0" patternUnits="userSpaceOnUse" height="1" width="1" patternTransform="matrix(1,0,0,1,0,0) translate(535.9612108001184,76)"><image x="0" y="0" href="assets/img/crush_gloss/coral_bell_gloss.jpg"></image></pattern><pattern id="75CC740F-C521-4E46-BE6F-2CE15E4CD6D4" x="0" y="0" patternUnits="userSpaceOnUse" height="1" width="1" patternTransform="matrix(1,0,0,1,0,0) translate(-31.03878919988169,426)"><image x="0" y="0" href="assets/img/crush_gloss/coral_bell_gloss.jpg"></image></pattern><pattern id="70CBF480-046A-4F7D-8B41-FD642EC8109B" x="0" y="0" patternUnits="userSpaceOnUse" height="1" width="1" patternTransform="matrix(1,0,0,1,0,0) translate(265.9612108001183,76)"><image x="0" y="0" href="assets/img/crush_gloss/habenero_gloss.jpg"></image></pattern><pattern id="146B2DC3-E4B5-4959-BFCD-6A6CF69E3300" x="0" y="0" patternUnits="userSpaceOnUse" height="1" width="1" patternTransform="matrix(1,0,0,1,0,0) translate(238.96121080011827,26)"><image x="0" y="0" href="assets/img/crush_gloss/coral_bell_gloss.jpg"></image></pattern> ... AND ON ... </defs></svg>
I am using the following to try to recreate my SVG in canvas:
innerSVG=document.getElementById('app_display').innerHTML;
//alert (innerSVG);
// load svg snippet in the canvas
canvg('canv', innerSVG);
My patterns draw fine, my background fill color is fine, it's just the image fill on the patterns I've drawn that's not okay; they're always black. I have tried referencing them by full url, relative, it makes no difference. There is no base meta tag on this page. Thoughts on where I'm wrong?
Try replacing your href with xlink:href
<image x="0" y="0" xlink:href="assets/img/crush_gloss/coral_bell_gloss.jpg"></image>
Or...
canvg('canv', innerSVG.replace("href","xlink:href"));

Resources