在无服务器函数中不使用 canvas 将 SVG 转换为 PNG/JPEG

Convert a SVG to a PNG/JPEG without using canvas in a serverless function

我有一个无服务器函数,它 return 是一个基于作为查询参数发送的文本的 SVG。

module.exports = (req, res) => {
  const { yourName } = req.query;

  res.setHeader("Content-Type", "image/svg+xml");

  const svg = `
    <svg fill="none" viewBox="0 0 600 400" width="600" height="400" xmlns="http://www.w3.org/2000/svg">
      <foreignObject width="100%" height="100%">
        <div xmlns="http://www.w3.org/1999/xhtml">
          <style>
            .title {
              font-size: 10vh;
              font-weight: regular;
              background-color: #ffddff;
            }
          </style>
            
          <p class="title">Hello ${yourName}</p> 
          
        </div>
      </foreignObject>
    </svg>
  `;

  res.send(svg);

};

例如,当我调用 /api?yourName=world 时,它 return 给我以下 SVG;

(已部署 URL:https://vercel-test-ruddy.vercel.app/api?world

我需要什么?

我需要 return 将 SVG 作为图像 (PNG/JPEG)。 所以,我的函数看起来像,

module.exports = (req, res) => {
  const { yourName } = req.query;

  res.setHeader("Content-Type", "image/png");

  const svg = `
    <svg fill="none" viewBox="0 0 600 400" width="600" height="400" xmlns="http://www.w3.org/2000/svg">
      <foreignObject width="100%" height="100%">
        <div xmlns="http://www.w3.org/1999/xhtml">
          <style>
            .title {
              font-size: 10vh;
              font-weight: regular;
              background-color: #ffddff;
            }
          </style>
            
          <p class="title">Hello ${yourName}</p> 
          
        </div>
      </foreignObject>
    </svg>
  `;

  res.send( svgToPng(svg) );

};

svgToPng = (svg) => {
   // not defined
}

我是否提到了其他 SO 问题?

是的。关于将 SVG 转换为 PNG 的问题很少:

Convert SVG to image (JPEG, PNG, etc.) in the browser
How to Exporting SVG as PNG format

几乎每个答案都谈到将 SVG 转换为 canvas,然后将 canvas 转换为 PNG。而且答案也更老了。我们可以不使用 canvas 吗?

在我的项目中我实际上没有 html document,我只有一个 .js 文件(据我所知我们需要 HTML5 来使用canvas).

备注

我曾使用 Vercel 来部署我的无服务器功能,即;

部署于:https://vercel-test-ruddy.vercel.app/api?yourName=YOUR_NAME_HERE
GitRepo:https://github.com/tharindusathis/vercel-test

看起来您正在解构查询参数,在这种情况下,它会在查询中搜索特定对象 属性。

如果您明确使用您的姓名作为查询参数,它应该可以工作。

https://vercel-test-ruddy.vercel.app/api?yourName=world

或者不分离 req.query 对象 属性。

<iframe src="https://vercel-test-ruddy.vercel.app/api?yourName=world" width="450px" height="450px">

到目前为止,我发现处理此问题的最佳方法是使用无头 chrome 并获取屏幕截图。例如,通过使用 puppeteer 我可以获得如下视口的屏幕截图,

const browser = await puppeteer.launch();
const page = await browser.newPage();

let yourName = 'world'

const svg = `
    <svg fill="none" viewBox="0 0 600 400" width="600" height="400" xmlns="http://www.w3.org/2000/svg">
      <foreignObject width="100%" height="100%">
        <div xmlns="http://www.w3.org/1999/xhtml">
          <style>
            .title {
              font-size: 10vh;
              font-weight: regular;
              background-color: #ffddff;
            }
          </style>
            
          <p class="title">Hello ${yourName}</p> 
          
        </div>
      </foreignObject>
    </svg>
  `;

await page.setViewport({ width: 2048, height: 1170 });
await page.setContent(svg);

console.log(await page.content());
await page.screenshot({path: 'screenshot.png'});
await browser.close();

然后发送 png 作为响应就很容易了。