Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing the SVG data through JavaScript

I am working on a web application using SVG & JS. Things are going well but I am a bit confused by data handling.

The data for objects that users create is dynamically appended to the SVG root. Where & how is this SVG data stored, & how can I access it? The reason I want to read it is that I want to convert this SVG data into HTML5 canvas (using Google's canvg) and finally turn that into a png image to be stored in our db for reference.

Any guidance would be much appreciated.

like image 609
Zain Avatar asked Mar 05 '11 04:03

Zain


1 Answers

When a web browser loads an SVG file, it (roughly) parses the stream of bytes and creates a DOM structure in memory for the SVG file. It is this in-memory representation that is modified when script dynamically creates and appends new elements to the document.

You can see a very simple example of this here:
http://phrogz.net/svg/svg_in_xhtml5.xhtml
The SVG file loads with a circle in the background for the face, and then JavaScript dynamically creates the eyes and nose and appends them as children of the SVG element.

There are three ways I know of that you can access this information and get it into a canvas:

  1. You can walk the DOM of elements yourself, and issue canvas context drawing commands:

    var svg = document.getElementsByTagName('svg')[0];
    var kids = svg.childNodes;
    for (var i=0,len=kids.length;i<len;++i){
      var kid = kids[i];
      if (kid.nodeType!=1) continue; // skip anything that isn't an element
      switch(kid.nodeName){
        case 'circle':
          // ...
        break;
        case 'path':
          // ...
        break;
        // ...
      }
    }
    

    See the SVG DOM reference for more information on the properties and methods available to you, or just use the DOM 2 Core methods to fetch properties. For simplicity, you might want to use the latter.

    For example, you can either access the current (non-SMIL animated) cx of the circle using the SVG DOM via var cx = kid.cx.baseVal.value; or you can more simply do var cx = kid.getAttribute('cx');.

    This is going to be simple where SVG and Canvas have similar commands (e.g. rect or line), a little bit of work where they don't match exactly (e.g. circle versus arcTo, polyline as a series of lineTo commands), and a lot of work for the path element and its many drawing commands.

  2. Per this answer and this paper, use the XMLSerializer interface to get the SVG back as markup, use that directly as the source of an img, and then drawImage that to the canvas. Using my above example:

    var svg_xml = (new XMLSerializer).serializeToString(svg);
    console.log(svg_xml);
    // "<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" viewBox="-350 -250 700 500">
    // <circle r="200" class="face" fill="red"/>
    // <path fill="none" class="face" transform="translate(-396,-230)" d="M487.41,282.411c-15.07,36.137-50.735,61.537-92.333,61.537         c-41.421,0-76.961-25.185-92.142-61.076"/>
    // <circle cx="-60" cy="-50" r="20" fill="#000"/>
    // <circle cx="60" cy="-50" r="20" fill="#000"/>
    // </svg>"
    var ctx = myCanvas.getContext('2d');
    var img = new Image;
    img.onload = function(){ ctx.drawImage(img,0,0); };
    img.src = "data:image/svg+xml;base64,"+btoa(svg_xml);
    

    If you have SVG in XHTML (as in my example), the serialization will not capture styles defined outside of the SVG block (as in my example) and so they will not be applied in the image.

    Note that per this question/answer (mine), you must set the onload attribute before you set the src to the data URL if you want IE9 compatibility.

    Unfortunately, due to what may or may not be a bug, drawing a data-url image to a canvas causes the origin-clean flag to be set to false, which means that you will not be able to call toDataURL() on the canvas and get your PNG back. So...

  3. I suppose for your specific needs of Client-modified SVG->Canvas->PNG-on-server you will need to use the first step above to serialize the svg_xml and then pass that raw source to canvg.

Alternatively, you might consider serializing the SVG per the above and submitting that directly to your server and doing server-side SVG-to-PNG conversion.

like image 92
Phrogz Avatar answered Sep 28 '22 09:09

Phrogz