yWorks / svg2pdf.js

A javascript-only SVG to PDF conversion utility that runs in the browser. Brought to you by yWorks - the diagramming experts
MIT License
654 stars 100 forks source link

Problems regarding scalefactors #104

Closed ThomasJunk closed 5 years ago

ThomasJunk commented 5 years ago

Describe the bug Given dimensions of diagram do not work as expected when using mm as unit in jsPDF

To Reproduce I prepared two minimal exaples as a gist

index1.html shows a version with unit pt the example was taken from the site here: 0.352778 is the conversion factor of 1pt to mm. Using this, the resulting rectangle is nearly 10mm (a bit off, perhaps due to rounding errors)

index2html shows a version with mm. If I take the desired output in mm and retrofit that on the screen, I use millimeter2pixel as a conversion function to calculate a given amount of pixels. The scale factor should be the inverse pixel2millimeter(1, DPI). The result isn't 210x100 mm wide.

Expected behavior Using mm as a unit, the result should be 210x100 mm wide.

Additional Info From my understanding it shouldn't matter whether I calculate to pixels and scale accordingly.

HackbrettXXX commented 5 years ago

svg2pdf does not support units, which means it treats all dimensions in the svg as if given in the unit that was passed to jsPDF. So in your example this means:

  1. you convert the dimensions to pixels
  2. you set the unit of jsPDF to "mm" (resulting in a scale of ´72/25.4`)
  3. you set the scale of svg2pdf to the inverse of (1.)

So for any dimension n, the resulting dimension n' is:

n' = n * (dpi/25.4) * (25.4/dpi) * (72/25.4)
n' = n * (72/25.4)

So, in order to receive the same dimensions in the output as in the input you must scale the input additionally by 25.4/72, which, if you reduce the formula, basically means you convert the millimeters on your device (and its DPI) to PDF's 72 units per inch.

ThomasJunk commented 5 years ago

Sorry, I am having a bit of a hard time with this.

Setting the scale to 1 and the unit to mm and leaving the rest as is - like I did in index3.html (see gist- the result is in an expected range a box of 9x mm. Even the stroke-width is 1mm.

From what you have written, I would have expected

you set the unit of jsPDF to "mm" (resulting in a scale of ´72/25.4`)

which seems not to be the case. From what you have written, I would have expected to do a rescale. Or am I misunderstanding you?

Edit: Further: if I scale papersize and increase the scalefactor proportionally I get the invariant result.

HackbrettXXX commented 5 years ago

I'm having a hard time understanding what you mean, too. Maybe I try to clarify what I mean by

svg2pdf does not support units, which means it treats all dimensions in the svg as if given in the unit that was passed to jsPDF.

svg2pdf treats

<rect width="100px" height="100px"/>

exactly like

<rect width="100mm" height="100mm"/>

and the output is exactly the same. This means in your index2.html, the width/height you converted to px are interpreted as mm by svg2pdf.

Setting the unit of jsPDF to mm produces the same results as if you would set svg2pdf's scale to 72/24.5 (apart from the page size, of course, which is also interpreted in the given unit).

You can prove the correct behavior of svg2pdf by setting the page width/height to the width/height of the rect (plus some margins so can actually see the rect):

    const svgElement = document.querySelector("svg");
    const width = 100 + 20;
    const height = 100 + 20;
    // create a new jsPDF instance
    const pdf = new jsPDF("l", "mm", [width, height]);
    // render the svg element
    svg2pdf(svgElement, pdf, {
      xOffset: 0,
      yOffset: 0,
      scale: 1
    });

The resulting rect fits exactly into the page, which is 120x120mm large.

ThomasJunk commented 5 years ago

Thanks, for taking the time! :+1:

I'm having a hard time understanding what you mean, too. The niceties of textual conversation. :D

What I observed is, what you have written. If I have a rect like:

<rect width="100" height="100" />

set the scale to 1, the unit to mm and the dimensions 297x210: the result is a rectangle from the expected dimensions (actually it is smaller by a factor of x where I am trying to understand where it comes from). That is fine.

If I leave the width and height at 100 but scale the papersize up and set the scale accordingly up, e.g. 297*3, 210*3 and scale 3 and print that out again, it works as expected.

If I change the width and height of the rect by a factor of 10 to 1000 (leaving the papersize at 297x210) and scale down accordingly to 0.1 the outputsize is as expected.

Aside: my example SVG was a quick Inkscape scribble and had a given stroke width of 1. If I set the papersize 297x210 , the units to mm and the scale to 1, I get the expected result, including the line width of 1mm. Additionally, if I set the width and height of the rectangle to 1000 and increase the stroke's width to 10 and set the scale to 0.1, the result is as expected: a 10 by 10 square with a stroke width of 1mm .

Does that make any sense so far?

ThomasJunk commented 5 years ago

That said leads me to the assumption:

When increasing the pixels on screen, I have to use the inverse factor as scale.

I calculate millimeter2pixels and set the scale with converting 1 pixel to n millimeters. For debugging purposes I draw a rect and checked the results.

That seems to work so far: It scales well. But my initial problem is that the result on the printer doesn't quite match the dimensions given. As said above: The rect is in fact 9 x not 100mm. And until now, I assumed that this is due to scaling factor issues, which could be ruled out by now, I think.

OTOH: I am having the issue accross printers. So I would rule out that accordingly.

yGuy commented 5 years ago

Just a thought: This might be an issue with the printing process and an automatic "fit page into printable area" feature of your PDF printer or printer driver. Did you measure the size of the resulting rectangle in the PDF?

HackbrettXXX commented 5 years ago

So if I understand you correctly, the rect has the correct size when you view the PDF in a PDF viewer but has an incorrect size when you print it out? Then I must agree with @yGuy: this is probably because of your printing process scaling the PDF to fit the page size.

ThomasJunk commented 5 years ago

"fit page into printable area" feature of your PDF printer or printer driver.

Printer I rule out at this point.

But printer driver / PDF Viewer seems to be to blame. Indeed. I actually made a rectangle 297x210 and it looked fine on sceen.

What I have learned:

  1. svg2pdf is agnostic to units (what was to be expected) 1.1 If you scale paper size, you have to set the scale accordingly, when leaving SVG units out. 1.2 If you scale SVG-units up or down, you have to do the inverse to the scale factor (e.g. millimeter2pixel for a given DPI, scale: pixel2millimeter for a given DPI) 1.3 you set the unit of jsPDF to "mm" (resulting in a scale of 72/25.4) seems not to hold (see 1) Aside: To put my given d3 chart with a fixed DPI on paper there are further things to be scaled accordingly
  2. NEVER rely on printed output alone, there are drivers and PDF viewers who eat your charts -.-

I am closing here. Thank you very much for your patience!