Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Width calculated by get_string_width is slightly too small for multi_cell #1393

Open
andreaswimmer opened this issue Mar 12, 2025 · 4 comments

Comments

@andreaswimmer
Copy link

Error details
get_string_width returns a (slightly) too small a value, leading to multi_cell splitting the string.

In the snippet below, I calculate the width of a string with get_string_width and then create a multi_cell with exactly that width (127.512)
However multi_cell deems that too small and inserts a line break. It is not due to c_margin, which I set to 0/include in the calculation.

This bug is very finicky and hard to hit. Small changes in the text, increasing the value by a tiny amount (length += 1e-12), changing the font or the page unit all make it disappear.

From what I can tell, this is a classic floating point error: MultiLineBreak calculates the width as 127.51200000000001 - which is above the limit of 127.512 - and inserts a line break.

Is this fixable?
My current workaround is to round up the values of get_string_width, is there a better way you'd recommend?

Minimal code
You will need the font Inter, which you can get for free at https://rsms.me/inter/download/
I tried to reproduce the issue with a default font, but couldn't.

from fpdf import FPDF
from fpdf.enums import MethodReturnValue

doc = FPDF(unit="pt")
doc.add_font("Inter", fname="./Inter.ttc")
doc.set_font("Inter")
doc.add_page()
# the bug occurs even if not setting c_margin to 0, since it is included in the length below
doc.c_margin = 0
text = "2025-03-11 17:42 UTC"
length = doc.get_string_width(text) + doc.c_margin * 2
# length = 127.512
print(f"length = {length}")
lines = doc.multi_cell(
    text=text,
    dry_run=True,
    output=MethodReturnValue.LINES,
    w=length,
)

# this fails
assert len(lines) == 1, lines

Environment

  • Operating System: Windows 11
  • Python version: 3.9.21
  • fpdf2 version used: 2.8.2
@Lucas-C
Copy link
Member

Lucas-C commented Mar 12, 2025

Thank you for the bug report @andreaswimmer 👍

I was able to reproduce the problem.

My current workaround is to round up the values of get_string_width, is there a better way you'd recommend?

Where have you been inserting those rounding operations?

Would you like to submit a PR to fix this? 🙂

@Lucas-C
Copy link
Member

Lucas-C commented Mar 12, 2025

@allcontributors please add @andreaswimmer for bug

Copy link

@Lucas-C

I've put up a pull request to add @andreaswimmer! 🎉

@andreaswimmer
Copy link
Author

My current workaround is to round up the values of get_string_width, is there a better way you'd recommend?

Where have you been inserting those rounding operations?

just immediately after the call. So in the code example above, I'd insert

length = math.floor(length + 1)

This rounds up if it's a floating point number and adds one if it happens to be an integer.
With the my unit being set to pt this is not a big change.

Would you like to submit a PR to fix this? 🙂

I'm not sure I know what the best fix would be in the first place. If the units are different from pt, adding one seems like a lot.
Like, if the unit is cm, all text measurements being off by up to 1cm seems unreasonably huge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants