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?