Width calculated by get_string_width is slightly too small for multi_cell
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
fpdf2version used: 2.8.2
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? 🙂
@allcontributors please add @andreaswimmer for bug
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.
I've ran into this problem as well and worked around it by rounding to a distant decimal place using math.ceil(value * 10000) / 10000, or by adding a fudge factor of 1e-10. This isn't a great solution though, I'm not sure what the best way to fix this is. Maybe something related to doing "close-enough" comparisons rather than strict comparisons with floating point values inside fpdf
Hey @andersonhc , I would love to work on this issue. Please assign the issue to me.
@Vedant817 you have asked to be assigned to 6 issues in the past 24 hours.
This is a lot, and it makes me doubt how serious you are about contributing to fpdf2.
Please choose only a single issue to work on at a time, and start by working on a first PR, or you behaviour will be considered to be spam.