svgdotjs / svg.js

The lightweight library for manipulating and animating SVG
https://svgjs.dev
Other
11.07k stars 1.08k forks source link

getBbox is incorrect in node.js #1267

Open testerez opened 2 years ago

testerez commented 2 years ago

I'm trying to calculate the size of an SVG in node but I'm getting incorrect results.

Here is the SVG:

<svg>
    <path d="M126.32 126.32C155.22 97.43 155.22 50.57 126.32 21.67C97.42 -7.22 50.57 -7.22 21.67 21.67C-7.22 50.57 -7.22 97.43 21.67 126.32C50.57 155.22 97.42 155.22 126.32 126.32Z " fill="black"></path>
</svg>

And the node.js code:

import { registerWindow, SVG } from '@svgdotjs/svg.js';
import { createSVGWindow } from 'svgdom';
import { svgString } from '../../svgString';

const window = createSVGWindow();
const { document } = window;
registerWindow(window, document);

const rootSvg = SVG(svgString);
console.log(rootSvg.bbox());

Result:

{
    "x": 21.67,
    "y": 21.67,
    "w": 126.32499999999997,
    "width": 126.32499999999997,
    "h": 126.32499999999997,
    "height": 126.32499999999997,
    "x2": 147.99499999999998,
    "y2": 147.99499999999998,
    "cx": 84.83249999999998,
    "cy": 84.83249999999998
  }

When calculating the bbox the same way in the browser, I'm getting the expected result:

{
    "x": 0.0025005340576171875,
    "y": 0.0025005340576171875,
    "w": 147.99249267578125,
    "width": 147.99249267578125,
    "h": 147.99249267578125,
    "height": 147.99249267578125,
    "x2": 147.99499320983887,
    "y2": 147.99499320983887,
    "cx": 73.99874687194824,
    "cy": 73.99874687194824
  }

(width and height are correct here)

I've put together an example to compare node and browser results here: https://stackblitz.com/edit/nextjs-ct2xan?file=svgString.js

jameshowe commented 2 years ago

Similar problem, mines related to measuring text elements, the widths seem to be short:

Screenshot 2022-07-23 at 21 02 03

The red rectangle is using the coordinates I get back from getBBox() / getClientBoundingBox(). The width gets shorter the longer the text gets e.g.

Screenshot 2022-07-23 at 21 05 04
twilson90 commented 5 months ago

Similar problem, mines related to measuring text elements, the widths seem to be short:

Screenshot 2022-07-23 at 21 02 03

The red rectangle is using the coordinates I get back from getBBox() / getClientBoundingBox(). The width gets shorter the longer the text gets e.g.

Screenshot 2022-07-23 at 21 05 04

I'm also having the same issue with measuring text elements. My guess is it's using dimensions from another font, default serif maybe. Any solution?

Fuzzyma commented 5 months ago

Usually if you register your own font properly it should use that one to measure the dimensions. I would need the font file to test that issue

Fuzzyma commented 3 months ago

@testerez @jameshowe @twilson90 In order to investigate this I need the font files.

jameshowe commented 3 months ago

@Fuzzyma the custom font in the example is https://fonts.google.com/specimen/Mansalva (regular), but you can see it happens even with Arial fonts too.

Fuzzyma commented 3 months ago

@jameshowe do you have an example for me with Arial? I guess thats easiest to replicate

jameshowe commented 1 month ago

This was a while ago, I'm not sure I have the code anymore (presume that's what you are after). I'll have a look around.

jameshowe commented 1 month ago

Ok so thankfully manage to find it, I've tried to strip it back to the smallest possible demonstrable PoC - I am able to replicate the same issue using Arial font with the following code:

import { createSVGWindow } from 'svgdom';
import { SVG, registerWindow } from '@svgdotjs/svg.js';
import { writeFileSync } from 'fs';

const window = createSVGWindow();
const doc = window.document;
registerWindow(window, doc);
// create svg.js instance
const canvas = SVG(doc.documentElement).size(530, 670);
let curOffset = 30
for (let i = 12; i < 30; i++) {
  // render text
  const position = canvas.text(`ARIAL FONT @ ${i}px`);
  position
    .font({
      fill: 'black',
      family: 'Arial',
      size: i
    })
    .move(30, curOffset)
  // render bounding box
  const bounds = position.node.getBoundingClientRect()
  canvas
    .rect(bounds.width, bounds.height)
    .move(bounds.x, bounds.y)
    .fill('#00000000')
    .stroke('red');
  // add some padding between each render
  curOffset += bounds.height + 5
}

await writeFileSync('output.svg', canvas.svg());

For the packages:

npm i @svgdotjs/svg.js@3.1.2 svgdom@0.1.10
Fuzzyma commented 1 month ago

Thanks will look into it once I find some time