Search code examples
javascriptphpcanvassvgcanvg

Convert Inline svg to canvas using canvg script


I have this website: http://materialground.com/icon-maker

I have this code above my body

<symbol id="fa-flaticon-3" viewBox="0 0 512 512">
      <path d="m512 247c0 118-87 216-200 233 1-5 2-11 1-16 0 0-1-6-2-14 41-6 77-25 105-51-1-2-3-3-5-6-7-11 3-22 3-32 0-9-20-9-20-15-3-22 13-22 22-30 8-9-9-28-20-27-11 1-41-5-39-28 4-29-36-27-42-40-8-19 6-43 24-49 25-7 51-28 49-52-3-26-14-50-34-64-21-8-43-13-66-14-20 0-38 5-37 14 2 23 67 29 56 51-6 11-49 27-41 44 6 11 19 1 14 22-2 10-11 28-24 29-13 1-24-34-60-35-17 0-41 27-21 46 12 12 36-14 42 6 5 14 0 44 22 56 9 5 21 9 34 15-19 4-42 11-68 21l-23-6c10-8 25-13 21-30-6-26-50-16-80-46-9-10-29-40-29-77-15 28-23 61-23 95 0 11 0 21 2 32 0 0-1 0-1 0-10 0-19 2-28 5-1-12-2-24-2-37 0-129 105-235 235-235 129 0 235 106 235 235z m-176 69l-58 26 1-12 37-17c-5 0-11-1-16-1-21 0-55 11-94 26l-123-32c-12-3-24-2-34 5l-6 4c-6 3-6 12 0 16l74 48c-18 9-35 18-49 27l-38-20c-7-4-14-4-20-1l-4 1c-5 2-8 9-5 14l21 37 0 0c-8 7-12 12-12 16 0 12 14 15 27 15 22 0 321-69 321-130 0-13-9-19-22-22z m-126 121l65-23c3-1 6 1 7 4l6 49c1 12-6 24-17 29l-6 3c-4 2-8 1-11-2l-46-53c-2-2-1-6 2-7z"></path>
    </symbol>

And below i have this code :

<svg viewBox="0 0 256 512" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" class="fa-youtube red draggable" vbox="0 0 256 512" id="img" width="341.3333333333333" fill="#fff" height="512" style="width:512 !important; height:512 !important;  padding:0; border-radius:10%">
<use xlink:href="#fa-flaticon"></use>
</svg>

I want the root svg tag to be modified by users like fill color. background color, stroke, viewbox etc.

Now the question is how to save the svg to png using canvg or any other script. I can also use php script.

I have used this code but its not working

function renderCanvas()
{
    var oSerializer = new XMLSerializer();
    var sXML = oSerializer.serializeToString(document.getElementById("svg"));

    canvg(document.getElementById('canvas'), sXML,{ ignoreMouse: true, ignoreAnimation: true })
}

I also added blank canvas.

<canvas id="canvas"></canvas>

Solution

  • Most of the svg to canvas libraries will fail on external resources (images, uses, symbols and any other attribute in the xlink name-space or with a <funciri>(url()) into their attribute.

    I am writing a script that does handle those cases.

    It does search for such external resources, and append it to a clone of the svg element to be converted, and then uses the canvas drawImage() ability to render svg.

    Its usage is quite simple, and may be even simpler in a near future.

    SVG2bitmap(SVGElement, [function([canvasElement],[dataURL]) || IMGElement || CanvasElement] [, Object{optional parameters}])

    Here is a dump of the function that do parse elements with such external attributes :

    function parseXlinks() {
      // some browsers don't support the asterisk namespace selector (guess which ones ...)
      // create a test element
      var test = document.createElementNS('http://www.w3.org/2000/svg', 'use');
      // set its href attribute to something that should be found
      test.setAttributeNS(xlinkNS, 'href', '__#__');
      // append it to our document
      clone.appendChild(test);
      // if querySelector returns null then the selector is not supported
      var supported = !!clone.querySelector('[*|href*="#"]'),
        xl,
        i;
      // the test is done, remove the element
      clone.removeChild(test);
      // if the selector is not supported
      if (!supported) {
        // xl is an array
        xl = [];
        // iterate through all our elements
        var children = clone.querySelectorAll('*');
        for (i = 0; i < children.length; i++) {
          // search the xlink:href attribute
          var xl_attr = children[i].getAttributeNS(xlinkNS, 'href');
          // we only want the ones that refer to elements
          if (xl_attr && xl_attr.indexOf('#') > -1) {
            xl.push(children[i]);
          }
        }
      } else {
        // get all our elements using an xlink:href attribute
        xl = clone.querySelectorAll('[*|href*="#"]');
      }
      // the list of all attributes that can have a <funciri> (url()) as value
      var url_attrs = ["clip-path", "src", "cursor", "fill", "filter", "marker", "marker-start", "marker-mid", "marker-end", "mask", "stroke"];
      // build our selector string
      var urlSelector = '[*|' + url_attrs.join('*="url"], *[*|') + '*="url"]';
      // query is magic
      var url = clone.querySelectorAll(urlSelector);
    
      // we found something
      if (xl.length || url.length) {
        // create a <defs> or get the svg's one
        getDef();
      }
      // there is no such elements ?
      else {
        // continue directly with images
        parseImages();
        return;
      }
    
      // create an array for external docs
      var externals = [],
        inDoc = [];
      var getElements = function(arr) {
        for (var i = 0; i < inDoc.length; i++) {
          var el = inDoc[i];
          // not in our actual svg ?
          if (!svg.getElementById(el)) {
            // get it somewhere else in the page
            var ref = document.querySelector('#' + el);
            // failed
            if (!ref) {
              console.warn('could not find this element : ', '#' + el);
              continue;
            }
            // we got it, add a clone to our svg
            defs.appendChild(ref.cloneNode(true));
          }
        }
      };
    
      // fetch the external documents
      var addFile = function(url) {
        var pushed = false;
        for (var i = 0; i < externals.length; i++) {
          // if we already have this document, just push the element
          if (url[0] === externals[i].file_url) {
            pushed = true;
            externals[i].elements.push(url[1]);
            checkParse();
          }
        }
        // that was a new doc
        if (!pushed) {
          // create the object we'll use for this doc
          var that = {
            file_url: url[0],
            elements: [url[1]],
            loading: null
          };
          // add it to our array
          externals.push(that);
          // create a new request
          var xhr = new XMLHttpRequest();
    
          xhr.onload = function() {
            // we're not loading anymore
            that.loading = false;
            // everything went fine
            if (this.status === 200) {
              that.response = this.responseText;
            } else {
              console.warn('could not load this document :', url[0], '\n' +
                'Those elements are lost : ', that.elements.join(' , '));
            }
            // In case we were the last one
            checkParse();
          };
          xhr.onerror = function(e) {
            that.loading = false;
            console.warn('could not load this document', url[0]);
            console.warn('Those elements are lost : ', that.elements.join(' , '));
            checkParse();
          };
          xhr.open('GET', that.file_url);
          that.loading = true;
          xhr.send();
        }
      };
    
      var checkParse = function() {
        // there are still pending loadings
        if (externals.some(function(o) {
          return o.loading;
        })) {
          return;
        } else {
          // loop through all our documents
          for (var i = 0; i < externals.length; i++) {
            // if we failed to load it
            if (!externals[i].response) {
              continue;
            }
            // create a new doc from the response
            var doc = new DOMParser().parseFromString(externals[i].response, 'image/svg+xml');
            // loop through the elements we use in this document
            var els = externals[i].elements;
            for (var j = 0; j < els.length; j++) {
              // get the new id we'll use in our svg file
              var newId = externals[i].file_url.replace('.svg', '_') + els[j];
              // this one was already appended
              if (defs.querySelector('#' + newId)) {
                continue;
              }
              // find it in the response doc
              var elem = doc.documentElement.querySelector('#' + els[j]);
              if (elem) {
                // we found it
                var clone = elem.cloneNode(true);
                clone.setAttribute('id', newId);
                defs.appendChild(clone);
              } else {
                console.warn('could not find this element : ', externals[i].file_url + '#' + els[j]);
              }
            }
          }
          // all responses have been parsed
          // we can continue with images
          parseImages();
        }
      };
    
      // get the attributes containing the <funciri>
      for (i = 0; i < url.length; i++) {
        // get all our node's attributes
        var att = url[i].attributes;
        // store a new array to our node
        url[i].external_attr = [];
        for (var j = 0; j < att.length; j++) {
          // does it have a <funciri> ?
          if (att[j].value.indexOf('url(') > -1) {
            // add it to the array
            url[i].external_attr.push(att[j]);
          }
        }
      }
      var split_attr = function(list, type) {
        // loop through our list to get the external elements
        for (var i = 0; i < list.length; i++) {
          var hrefs = [],
            j;
          if (type === 'xlink') {
            // get the href attribute of our element
            hrefs.push(list[i].getAttributeNS(xlinkNS, 'href').split('#'));
          } else {
            // loop through all attributes containing a <funciri>
            var attr = list[i].external_attr;
            for (j = 0; j < attr.length; j++)
              hrefs.push(attr[j].value.substring(4).slice(0, -1).split('#'));
          }
          for (j = 0; j < hrefs.length; j++) {
            var href = hrefs[j];
            // it does point to an external doc
            if (href[0].indexOf('.svg') > 0) {
              addFile(href);
              // a new id if different external docs uses the same ids
              var newId = '#' + href[0].replace('.svg', '_') + href[1];
              // 'xlink' case
              if (type === 'xlink')
                list[i].setAttributeNS(xlinkNS, 'href', newId);
              // <funciri> case
              else
                list[i].setAttribute(list[i].external_attr[j].name, 'url(' + newId + ')');
            }
            // it should be inside the page
            else if (!href[0]) {
              // push it to our array if it's not there already
              if (inDoc.indexOf(href[1] < 0)) {
                inDoc.push(href[1]);
              }
            }
          }
        }
        if (inDoc.length) {
          getElements(inDoc);
        }
      };
    
      split_attr(xl, 'xlink');
      split_attr(url, 'funciri');
    
      // all was done synchronously or before we finished parsing (not sure this can happen)
      if (externals.length === 0 || !externals.some(function(o) {
        return o.loading;
      })) {
        exportDoc();
      }
    }
    

    Live example :

    var svg = toPixel;
    var clone = svg.cloneNode(true);
    var doSomethingWith = function(canvas) {
      document.body.appendChild(canvas)
    };
    var xlinkNS = 'http://www.w3.org/1999/xlink';
    
    function parseXlinks() {
      // some browsers don't support the asterisk namespace selector (guess which ones ...)
      // create a test element
      var test = document.createElementNS('http://www.w3.org/2000/svg', 'use');
      // set its href attribute to something that should be found
      test.setAttributeNS(xlinkNS, 'href', '__#__');
      // append it to our document
      clone.appendChild(test);
      // if querySelector returns null then the selector is not supported
      var supported = !!clone.querySelector('[*|href*="#"]'),
        xl,
        i;
      // the test is done, remove the element
      clone.removeChild(test);
      // if the selector is not supported
      if (!supported) {
        // xl is an array
        xl = [];
        // iterate through all our elements
        var children = clone.querySelectorAll('*');
        for (i = 0; i < children.length; i++) {
          // search the xlink:href attribute
          var xl_attr = children[i].getAttributeNS(xlinkNS, 'href');
          // we only want the ones that refer to elements
          if (xl_attr && xl_attr.indexOf('#') > -1) {
            xl.push(children[i]);
          }
        }
      } else {
        // get all our elements using an xlink:href attribute
        xl = clone.querySelectorAll('[*|href*="#"]');
      }
      // the list of all attributes that can have a <funciri> (url()) as value
      var url_attrs = ["clip-path", "src", "cursor", "fill", "filter", "marker", "marker-start", "marker-mid", "marker-end", "mask", "stroke"];
      // build our selector string
      var urlSelector = '[*|' + url_attrs.join('*="url"], *[*|') + '*="url"]';
      // query is magic
      var url = clone.querySelectorAll(urlSelector);
    
      // we found something
      if (xl.length || url.length) {
        // create a <defs> or get the svg's one
        getDef();
      }
      // there is no such elements ?
      else {
        // continue directly with images
        parseImages();
        return;
      }
    
      // create an array for external docs
      var externals = [],
        inDoc = [];
      var getElements = function(arr) {
        for (var i = 0; i < inDoc.length; i++) {
          var el = inDoc[i];
          // not in our actual svg ?
          if (!svg.getElementById(el)) {
            // get it somewhere else in the page
            var ref = document.querySelector('#' + el);
            // failed
            if (!ref) {
              console.warn('could not find this element : ', '#' + el);
              continue;
            }
            // we got it, add a clone to our svg
            defs.appendChild(ref.cloneNode(true));
          }
        }
      };
    
      // fetch the external documents
      var addFile = function(url) {
        var pushed = false;
        for (var i = 0; i < externals.length; i++) {
          // if we already have this document, just push the element
          if (url[0] === externals[i].file_url) {
            pushed = true;
            externals[i].elements.push(url[1]);
            checkParse();
          }
        }
        // that was a new doc
        if (!pushed) {
          // create the object we'll use for this doc
          var that = {
            file_url: url[0],
            elements: [url[1]],
            loading: null
          };
          // add it to our array
          externals.push(that);
          // create a new request
          var xhr = new XMLHttpRequest();
    
          xhr.onload = function() {
            // we're not loading anymore
            that.loading = false;
            // everything went fine
            if (this.status === 200) {
              that.response = this.responseText;
            } else {
              console.warn('could not load this document :', url[0], '\n' +
                'Those elements are lost : ', that.elements.join(' , '));
            }
            // In case we were the last one
            checkParse();
          };
          xhr.onerror = function(e) {
            that.loading = false;
            console.warn('could not load this document', url[0]);
            console.warn('Those elements are lost : ', that.elements.join(' , '));
            checkParse();
          };
          xhr.open('GET', that.file_url);
          that.loading = true;
          xhr.send();
        }
      };
    
      var checkParse = function() {
        // there are still pending loadings
        if (externals.some(function(o) {
          return o.loading;
        })) {
          return;
        } else {
          // loop through all our documents
          for (var i = 0; i < externals.length; i++) {
            // if we failed to load it
            if (!externals[i].response) {
              continue;
            }
            // create a new doc from the response
            var doc = new DOMParser().parseFromString(externals[i].response, 'image/svg+xml');
            // loop through the elements we use in this document
            var els = externals[i].elements;
            for (var j = 0; j < els.length; j++) {
              // get the new id we'll use in our svg file
              var newId = externals[i].file_url.replace('.svg', '_') + els[j];
              // this one was already appended
              if (defs.querySelector('#' + newId)) {
                continue;
              }
              // find it in the response doc
              var elem = doc.documentElement.querySelector('#' + els[j]);
              if (elem) {
                // we found it
                var clone = elem.cloneNode(true);
                clone.setAttribute('id', newId);
                defs.appendChild(clone);
              } else {
                console.warn('could not find this element : ', externals[i].file_url + '#' + els[j]);
              }
            }
          }
          // all responses have been parsed
          // we can continue with images
          parseImages();
        }
      };
    
      // get the attributes containing the <funciri>
      for (i = 0; i < url.length; i++) {
        // get all our node's attributes
        var att = url[i].attributes;
        // store a new array to our node
        url[i].external_attr = [];
        for (var j = 0; j < att.length; j++) {
          // does it have a <funciri> ?
          if (att[j].value.indexOf('url(') > -1) {
            // add it to the array
            url[i].external_attr.push(att[j]);
          }
        }
      }
      var split_attr = function(list, type) {
        // loop through our list to get the external elements
        for (var i = 0; i < list.length; i++) {
          var hrefs = [],
            j;
          if (type === 'xlink') {
            // get the href attribute of our element
            hrefs.push(list[i].getAttributeNS(xlinkNS, 'href').split('#'));
          } else {
            // loop through all attributes containing a <funciri>
            var attr = list[i].external_attr;
            for (j = 0; j < attr.length; j++)
              hrefs.push(attr[j].value.substring(4).slice(0, -1).split('#'));
          }
          for (j = 0; j < hrefs.length; j++) {
            var href = hrefs[j];
            // it does point to an external doc
            if (href[0].indexOf('.svg') > 0) {
              addFile(href);
              // a new id if different external docs uses the same ids
              var newId = '#' + href[0].replace('.svg', '_') + href[1];
              // 'xlink' case
              if (type === 'xlink')
                list[i].setAttributeNS(xlinkNS, 'href', newId);
              // <funciri> case
              else
                list[i].setAttribute(list[i].external_attr[j].name, 'url(' + newId + ')');
            }
            // it should be inside the page
            else if (!href[0]) {
              // push it to our array if it's not there already
              if (inDoc.indexOf(href[1] < 0)) {
                inDoc.push(href[1]);
              }
            }
          }
        }
        if (inDoc.length) {
          getElements(inDoc);
        }
      };
    
      split_attr(xl, 'xlink');
      split_attr(url, 'funciri');
    
      // all was done synchronously or before we finished parsing (not sure this can happen)
      if (externals.length === 0 || !externals.some(function(o) {
        return o.loading;
      })) {
        exportDoc();
      }
    }
    
    var defs;
    var getDef = function() {
      // Do we have a `<defs>` element already ?
      defs = svg.querySelector('defs') || document.createElementNS('http://www.w3.org/2000/svg', 'defs');
      if (!defs.parentNode) {
        svg.insertBefore(defs, svg.firstElementChild);
      }
    };
    
    var exportDoc = function() {
      // check if our svgNode has width and height properties set to absolute values
      // otherwise, canvas won't be able to draw it
      var bbox = svg.getBoundingClientRect();
    
      if (svg.width.baseVal.unitType !== 1) svg.setAttribute('width', bbox.width);
      if (svg.height.baseVal.unitType !== 1) svg.setAttribute('height', bbox.height);
    
      // serialize our node
      var svgData = (new XMLSerializer()).serializeToString(svg);
      // remember to encode special chars
      var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);
    
      var svgImg = new Image();
    
      svgImg.onload = function() {
        var canvas = document.createElement('canvas');
        // IE11 doesn't set a width on svg images...
        canvas.width = this.width || bbox.width;
        canvas.height = this.height || bbox.height;
    
        canvas.getContext('2d').drawImage(svgImg, 0, 0, canvas.width, canvas.height);
        doSomethingWith(canvas)
      };
    
      svgImg.src = svgURL;
    };
    
    parseXlinks();
    canvas {
      border: 1px solid green !important;
    }
    <script src="https://rawgit.com/Kaiido/SVG2Bitmap/master/SVG2Bitmap.js"></script>
    
    <svg style="display: none">
      <symbol id="sym01" viewBox="0 0 150 110">
        <circle cx="50" cy="50" r="40" stroke-width="8" stroke="red" fill="red" />
        <circle cx="90" cy="60" r="40" stroke-width="8" stroke="green" fill="white" />
      </symbol>
    </svg>
    
    <svg id="toPixel">
      <use xlink:href="#sym01" />
    </svg>
    <br>canvas version:
    <br>

    or using the entire script :

    SVG2bitmap(toPixel, function(canvas){
      document.body.appendChild(canvas);
      });
    canvas { border: 1px solid green !important;}
    <script src="https://rawgit.com/Kaiido/SVG2Bitmap/master/SVG2Bitmap.js"></script>
    
    <svg style="display: none">
      <symbol id="sym01" viewBox="0 0 150 110">
        <circle cx="50" cy="50" r="40" stroke-width="8" stroke="red" fill="red" />
        <circle cx="90" cy="60" r="40" stroke-width="8" stroke="green" fill="white" />
      </symbol>
    </svg>
    
    <svg id="toPixel">
      <use xlink:href="#sym01" />
    </svg>
    <br>canvas version:
    <br>