Search code examples
node.jsimagesvgsharp

SVG as SVG's image tag not working while using with sharp


I'm using sharp to convert the file but sharp gives me an empty png file.

I want to use a base64 formatted SVG image as <image> tag's source and want to convert it to a png file.

Here's an example to show what I'm trying to do;

const sharp = require('sharp');
sharp(Buffer.from(<look below js file to see the SVG>))
    .toFormat('png')
    .toFile('output.png')

And here's a JS to show what I tried to do. Basically, I want to convert this SVG to a png file using sharp but sharp gives me an empty png.


Solution

  • It looks like sharp does not handle external images well.

    You can try to "flatten" SVG before passing it to sharp.

    Quick and dirty implementation

    This is very error prone. Ideally you would use something like Cheerio for parsing and modifying SVG input.

    It also preserves only x, y, width, height and opacity attributes. Others attributes would require further changes.

    function flattenSVG (svg) {
      const images = svg.match(/<image [^>]+>/g);
      if (!images || images.length < 1) {
        return svg;
      }
    
      var result = svg;
      images.forEach(image => {
        const [,data] = image.match(/ xlink:href="data:image\/svg\+xml;base64,([^"]+)"/) || [];
        if (!data) {
          return;
        }
    
        const innerSVG = Buffer.from(data, 'base64').toString();
        const [,width] = image.match(/ width="([^"]+)"/) || [];
        const [,height] = image.match(/ height="([^"]+)"/) || [];
        const [,opacity] = image.match(/ opacity="([^"]+)"/) || [];
        const [,x] = image.match(/ x="([^"]+)"/) || [];
        const [,y] = image.match(/ y="([^"]+)"/) || [];
        const [header] = innerSVG && innerSVG.match(/<svg[^>]+>/) || [];
        const fixedHeader = header
          .replace(/ (x|y|width|height)="([^"]+)"/g, '')
          .replace('<svg', `<svg x="${x}" y="${y}" width="${width}" height="${height}" opacity="${opacity || 1.0}"`);
        const replacement = innerSVG && innerSVG.replace(header, fixedHeader);
        result = result.replace(image, replacement);
      });
    
      return result;
    }
    

    So, in your code, you would use something like:

    sharp(Buffer.from(flattenSVG(testSVG)))
          .toFormat('png')
          .toFile('output.png')
    

    You can check working code (a bit different for test purposes, as it uses buffer instead of file output) at Glitch.