-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
The problem
I discovered that depending on environment and OS the SVG export of the canvas (.toBuffer() method) exports the SVG file with the units "pt" set in markup. This is invalid behavior. SVG units should stay "none", so the example below:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800pt" height="400pt" viewBox="0 0 800 400" version="1.1">
...
</svg>
is invalid and we should get the format like below (in pixels, no units):
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="400" viewBox="0 0 800 400" version="1.1">
...
</svg>
The usage code in TypeScript
import {imageSize} from 'image-size';
import {Canvas, createCanvas} from 'canvas';
const canvas: Canvas = createCanvas(400, 400, 'svg');
const buffer: Buffer = canvas.toBuffer();
const dimensions = imageSize(buffer);
// jest test
expect(dimensions.width).toEqual(400); // here it fails
expect(dimensions.height).toEqual(400); // here it fails
Environments
The code is working locally on Mac OS, Windows (not checked on any linux locally)
It fails on Docker image with Debian (probably 12) / Node 20.11.0 (other linux OS not checked).
Why this is a problem?
Invalid units causes invalid scaling of the image when it is used. The "pt" units is not 1:1 a pixel, each pixel is represented by the following unit:
96/72 pt => 1.3333 px
, this results in an image scaled to ~133% of initially set dimensions.
Solution
Always use RAW pixels, do not set any unit while exporting to buffer. The cavnas itself seems to have correct size, width and height in pixels, but I was not testing it intensively. The problem seems to be on the export only - in the .toBuffer()
method (parameterless).
Temporary fix made as workaround
let svgFileXML = this.canvas.toBuffer().toString('utf8'); // convert buffer to XML string
const regex = /(<svg.*width="[0-9]+)pt(".*height="[0-9]+)pt(".*>)/; // match SVG header tag
svgFileXML = svgFileXML.replace(regex, '$1$2$3'); // replace with tag without units
return Buffer.from(svgFileXML, 'utf8'); // I use buffer in my code so I convert back to buffer