I am writing a little HTML5+JS tool to generate an SVG image. I have encountered a number of issues in doing so, and while I have workarounds for most of them, in at least one case I feel like there must be a better way. And then there are a couple of things that still just aren't working.
At present, this is for my own use, so cross-browser compatibility isn't a concern; as long as it works in Firefox (first preference) or Chromium, it's all good. I would like to stick it online once it's working right, though, so compatibility caveats would be appreciated.
Goals
- All processing should be done client-side; in fact, at this stage everything is a local
file://
, no web server involved. - Add text and elements to an SVG image (inline in the HTML) using scripted form elements.
- Click on the SVG (which is shrunk down to a preview size) to open it, as modified, in a new window/tab.
- Use some easy-to-access method (i.e. not DOM inspector, copy to text file, save) to save the SVG to disk.
Test case
https://gist.github.com/perey/1d352a790f749aa05a8b (see it in action)
<!DOCTYPE html>
<html xmlns=http://www.w3.org/1999/xhtml lang=en xml:lang=en>
<head>
<meta charset=utf-8/>
<title>SVG Generator</title>
<style type=text/css>
figure {
width: 45%;
float: right;
}
#output-pic {
border: thin solid green;
cursor: pointer;
}
form {
width: 45%;
float: left;
}
</style>
<script>
window.onload = function() {
document.getElementById(input-box).oninput = update_text;
document.getElementById(output-pic).onclick = show_svg;
update_text();
}
function update_text() {
var input_elem = document.getElementById(input-box);
var output_elem = document.getElementById(changeable-text);
output_elem.textContent = input_elem.value;
}
function show_svg() {
var svg_win = window.open(, svg_win);
var embedded_svg = document.getElementById(output-pic);
var transplanted_svg = svg_win.document.importNode(embedded_svg, true);
var blank_root = svg_win.document.documentElement;
svg_win.document.removeChild(blank_root);
svg_win.document.appendChild(transplanted_svg);
}
</script>
</head>
<body>
<figure role=img aria-labelledby=preview-caption>
<figcaption id=preview-caption>Preview <small>(click for full
size)</small></figcaption>
<svg id=output-pic
xmlns=http://www.w3.org/2000/svg
xmlns:xlink=http://www.w3.org/1999/xlink
version=1.1 width=640px height=480px
viewBox=0 0 640 480 preserveAspectRatio=xMinYMin>
<title>A test SVG file</title>
<defs>
<style type=text/css>
text {
font-family: serif;
stroke: none;
fill: red;
}
.underline {
stroke: blue;
stroke-width: 1;
fill: none;
marker-mid: url(#arrow);
}
</style>
<marker id=arrow
viewBox=-3 -3 6 6 orient=auto
markerUnits=strokeWidth
refX=0 refY=0
markerWidth=6 markerHeight=6>
<path d=M0,0 -3,-3 3,0 -3,3 Z/>
</marker>
</defs>
<text id=changeable-text text-anchor=middle font-size=40
x=320 y=240></text>
<path class=underline d=M10,250 h310 310/>
</svg>
</figure>
<form>
<label>Text: <input id=input-box/></label>
</form>
</body>
</html>
Issues
Opening the SVG
Opening about:blank
, deleting its document element, and adding the SVG element, feels really hacky. However, nothing else has worked I have only found a slightly better way of constructing a document in a new window (see below).
In particular, I've tried loading a barebones SVG file and adding all the child nodes of the preview SVG, like so:
function show_svg() {
var svg_win = window.open(blank.svg, svg_win);
var embedded_svg = document.getElementById(output-pic);
var transplanted_svg = svg_win.document.importNode(embedded_svg, true);
var blank_root = svg_win.document.documentElement;
while (transplanted_svg.hasChildNodes()) {
blank_root.appendChild(transplanted_svg.firstChild);
}
svg_win.alert(Done!);
}
However, after this function does its thing, the loaded file then wipes all changes made to it and reverts to its pristine state. (The alert
is there to highlight this fact: in Firefox, the alert box itself disappears without user action when the page is wiped. In Chromium, the alert box hangs about, but the wipe happens after it's dismissed.)
It's not a matter of tying the node reparenting to the new window's Yes it is. I made a mistake when I first tried that. Here's what I did:onload
handler.
function show_svg() {
var svg_win = window.open(blank.svg, svg_win);
var embedded_svg = document.getElementById(output-pic);
var transplanted_svg = svg_win.document.importNode(embedded_svg, true);
var blank_root = svg_win.document.documentElement;
svg_win.onload = function () {
while (transplanted_svg.hasChildNodes()) {
blank_root.appendChild(transplanted_svg.firstChild);
}
svg_win.alert(Done!);
}
}
What I should've done is put the definition of blank_root
inside the onload
handler. That works.
Still feels like there should be a way to construct a new document from scratch, though. Modifying a blank SVG is better than modifying the about:blank
HTML, but is that really the best way?
Missing markers
(This only seems to be an issue with Firefox, not with Chromium.)
The marker-mid
styling works fine in the preview image, but not in the opened SVG. I have no idea why. Edit: Modifying an SVG file instead of about:blank
doesn't have this issue. I'm off to file a bug, but I already suspect they're going to say don't try and dynamically convert a HTML file into an SVG file.
Saving the generated SVG
I have no idea how to do this. A few tantalising hints seem to say that it's something to do with Blobs, but I've found nothing that addresses saving a generated SVG file client-side, and I don't understand what they're doing well enough to make it work for me.
Any help, suggestions, advice, or corrections?