title: Generating, viewing, and saving SVG client-side in browser
body: <p>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 <em>aren't</em> working.</p>
  
  <p>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.</p>
  
  <h1>Goals</h1>
  
  <ol>
  <li>All processing should be done client-side; in fact, at this stage everything is a local <code>file://</code>, no web server involved.</li>
  <li>Add text and elements to an SVG image (inline in the HTML) using scripted form elements.</li>
  <li>Click on the SVG (which is shrunk down to a "preview" size) to open it, as modified, in a new window/tab.</li>
  <li>Use some easy-to-access method (i.e. not "DOM inspector, copy to text file, save") to save the SVG to disk.</li>
  </ol>
  
  <h1>Test case</h1>
  
  <p><a href="https://gist.github.com/perey/1d352a790f749aa05a8b" rel="noreferrer">https://gist.github.com/perey/1d352a790f749aa05a8b</a> (<a href="http://htmlpreview.github.io/?https://gist.github.com/perey/1d352a790f749aa05a8b/raw/212a1b3bcf03bc7937837768089865da94075934/svg-test.html" rel="noreferrer">see it in action</a>)
  </p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"&gt;
    &lt;head&gt;
      &lt;meta charset="utf-8"/&gt;
      &lt;title&gt;SVG Generator&lt;/title&gt;
      &lt;style type="text/css"&gt;
        figure {
          width: 45%;
          float: right;
        }
        #output-pic {
          border: thin solid green;
          cursor: pointer;
        }
        form {
          width: 45%;
          float: left;
        }
      &lt;/style&gt;
      &lt;script&gt;
        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);
        }
      &lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;figure role="img" aria-labelledby="preview-caption"&gt;
        &lt;figcaption id="preview-caption"&gt;Preview &lt;small&gt;(click for full
          size)&lt;/small&gt;&lt;/figcaption&gt;
        &lt;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"&gt;
          &lt;title&gt;A test SVG file&lt;/title&gt;
          &lt;defs&gt;
            &lt;style type="text/css"&gt;
              text {
                font-family: serif;
                stroke: none;
                fill: red;
              }
              .underline {
                stroke: blue;
                stroke-width: 1;
                fill: none;
                marker-mid: url(#arrow);
              }
            &lt;/style&gt;
            &lt;marker id="arrow"
                    viewBox="-3 -3 6 6" orient="auto"
                    markerUnits="strokeWidth"
                    refX="0" refY="0"
                    markerWidth="6" markerHeight="6"&gt;
              &lt;path d="M0,0 -3,-3 3,0 -3,3 Z"/&gt;
            &lt;/marker&gt;
          &lt;/defs&gt;
          &lt;text id="changeable-text" text-anchor="middle" font-size="40"
                x="320" y="240"&gt;&lt;/text&gt;
          &lt;path class="underline" d="M10,250 h310 310"/&gt;
        &lt;/svg&gt;
      &lt;/figure&gt;
      &lt;form&gt;
        &lt;label&gt;Text: &lt;input id="input-box"/&gt;&lt;/label&gt;
      &lt;/form&gt;
    &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <h1>Issues</h1>
  
  <h2>Opening the SVG</h2>
  
  <p>Opening <code>about:blank</code>, deleting its document element, and adding the SVG element, feels really hacky. However, <strike>nothing else has worked</strike> I have only found a slightly better way of constructing a document in a new window (see below).</p>
  
  <p>In particular, I've tried loading a barebones SVG file and adding all the child nodes of the preview SVG, like so:
  </p>
  
  <pre><code>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!");
  }
  </code></pre>
  
  <p>However, after this function does its thing, the loaded file then "wipes" all changes made to it and reverts to its pristine state. (The <code>alert</code> 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.)</p>
  
  <p><strike>It's not a matter of tying the node reparenting to the new window's <code>onload</code> handler.</strike> <strong>Yes it is.</strong> I made a mistake when I first tried that. Here's what I did:
  </p>
  
  <pre><code>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!");
      }
  }
  </code></pre>
  
  <p>What I <strong>should've</strong> done is put the definition of <code>blank_root</code> <em>inside</em> the <code>onload</code> handler. That works.</p>
  
  <p>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 <code>about:blank</code> HTML", but is that really the best way?</p>
  
  <h2>Missing markers</h2>
  
  <p>(This only seems to be an issue with Firefox, not with Chromium.)</p>
  
  <p>The <code>marker-mid</code> styling works fine in the preview image, but not in the opened SVG. I have no idea why. <strong>Edit:</strong> Modifying an SVG file instead of <code>about:blank</code> 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".</p>
  
  <h2>Saving the generated SVG</h2>
  
  <p>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.</p>
  
  <p>Any help, suggestions, advice, or corrections?</p>