-
Notifications
You must be signed in to change notification settings - Fork 265
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
While using Unicode fonts if markdown in multi_cell is on, then the rendering of muliti_cell in a subsequent line is broken #359
Comments
That only seems to happen when the txt argument to multi_cell ends with a markdown item, like Apparently the character mapping algorithm "forgets" to switch back to the regular font after it is done, so the next invocation will look up the characters in the wrong set. |
Thanks @gmischler for taking a look at this. It seems to me that this problem actually has two parts:
This is the first part which occurs also with non Unicode fonts. The second part of the problem is displaying wrong characters. pdf.add_font('Arial', '', 'arial.ttf', True)
pdf.set_font('Arial', '', 10) I get: I hope these details help a bit. |
I think I found a solution, although I'm not sure if it's the correct one. If we give the contents of the text handling part of ... # ca. line 2185
s_width, underlines = 0, []
if text_line.fragments:
s += " q " # issue #359 <<<<<<
if align == "R":
dx = w - self.c_margin - styled_txt_width
...
...
...
link,
)
s += " Q " # issue #359 <<<<<<
if s:
self._out(s)
self.lasth = h
... # ca. line 2320 The " q ... Q " nearby issued depending on |
Thank you two for your investigations! The change you suggested @gmischler would increase by 4 bytes the size of any text line rendered by Is the next step here to submit a PR? |
We'd have to check if we can do it more selectively, and/or if we can remove the other q/Q pair from #348 in exchange.
I can include it in my currently open one... 😉 |
Alright, but the "worst case scenario" is also the most common one... Also, practically speaking, all PDF reference tests files containing text will have to be updated. |
On closer inspection, there are several aspects of
|
Except for 6., I implemented/fixed all the points from the above list (based on #350), and found that the result works quite nicely, without any leaked formatting or random characters: With just 5. and 8., there are 14 PDF files that this would change. Some of them get slightly larger by the added The code ended up a bit shorter and much more straightforward than the current version, because several special case kludges became unnecessary. Some parts were a bit tricky because fpdf carries around an unhealthy amount of global state. I circumvented that where necessary, which makes the solution slightly less elegant than it otherwise could have been. Still, adding this directly to #350 would be a lot of feature creep, so I'll put it to the side for the moment. I already have two open PRs after all... |
Thank you @gmischler for those excellents suggestions!
I'm really not sure about that... I think calling You're right, we just get your already-open PRs merged first. |
Thank you both @gmischler and @Lucas-C for working on this issue! Although we have a workaround, I'm still interested in getting this issue fixed since after this bug is being provoked also the unbreakable() does not work in our code. (There is no exception thrown but output() generates an empty document. We tried to create a trivial code example with this outcome, but had no success. - so no bug report on this.) Can you please give me a rough estimation when we will have the fix in the master branch? |
Damn. If ever you can get some minimal code reproducing this, I'd be curious to see it
I'd say within a month or so. |
I've finally succeeded to create a minimal example - which uses built-in font and has markdown set to False: pdf = FPDF('P', 'mm', 'A4')
pdf.set_auto_page_break(True, 20)
pdf.add_page()
pdf.set_font('Helvetica', '', 10)
data = (
("First name", "Last name", "Age", "City"),
("Jules", "Smith", "34", "San Juan"),
("Mary", "Ramos", "45", "Orlando"),
("Carlson", "Banks", "19", "Los Angeles"),
("Lucas", "Cimon", "31", "Angers"),
("Jules", "Smith", "34", "San Juan"),
("Mary", "Ramos", "45", "Orlando"),
("Carlson", "Banks", "19", "Los Angeles"),
("Lucas", "Cimon", "31", "Angers"),
("Jules", "Smith", "34", "San Juan"),
("Mary", "Ramos", "45", "Orlando"),
("Carlson", "Banks", "19", "Los Angeles"),
("Lucas", "Cimon", "31", "Angers"),
)
l_height = pdf.font_size * 1.2
cell_width = pdf.epw / 4
no_of_lines_list = []
for row in data:
max_no_of_lines_in_cell = 1
for cell in row:
result = pdf.multi_cell(cell_width, l_height, cell, border=1, align='L', ln=3, max_line_height=l_height, split_only=True)
no_of_lines_in_cell = len(result)
if no_of_lines_in_cell > max_no_of_lines_in_cell:
max_no_of_lines_in_cell = no_of_lines_in_cell
no_of_lines_list.append(max_no_of_lines_in_cell)
for j, row in enumerate(data):
cell_height = no_of_lines_list[j] * l_height
for cell in row:
if j == 0:
pdf.multi_cell(cell_width, cell_height, '**' + cell + '**', border=1, fill=False, align='L', ln=3, max_line_height=l_height, markdown=False)
else:
pdf.multi_cell(cell_width, cell_height, cell, border=1, align='L', ln=3, max_line_height=l_height)
pdf.ln(cell_height)
pdf.ln()
with pdf.unbreakable() as pdf:
for i in range(4):
for row in data:
max_no_of_lines_in_cell = 1
for cell in row:
result = pdf.multi_cell(cell_width, l_height, cell, border=1, align='L', ln=3, max_line_height=l_height, split_only=True)
no_of_lines_in_cell = len(result)
if no_of_lines_in_cell > max_no_of_lines_in_cell:
max_no_of_lines_in_cell = no_of_lines_in_cell
no_of_lines_list.append(max_no_of_lines_in_cell)
for j, row in enumerate(data):
cell_height = no_of_lines_list[j] * l_height
for cell in row:
if j == 0:
pdf.multi_cell(cell_width, cell_height, '**' + cell + '**', border=1, fill=False, align='L', ln=3, max_line_height=l_height, markdown=False)
else:
pdf.multi_cell(cell_width, cell_height, cell, border=1, align='L', ln=3, max_line_height=l_height)
pdf.ln(cell_height)
return pdf.output() The code above works fine if the table in the unbreakable context has been rendered only three or less times.
Great, thank you. |
You are welcome.
Great!
Agree. Actually I also wanted to open a separate issue for this, but was not sure what is your preference and if it is closely tied to markdwon problem since this is where we discovered it. :) |
The PR fixing this has just been merged |
I can confirm, that the fix in the PR works great. Thank you both @Lucas-C and @gmischler! But the PDF version is now set back to 1.3. |
This is the default version of PDFs producedby |
Yes, that's what I've thought, but when I implement a header which contains a SVG, then the version is automatically set to 1.4. If a header contains a PNG, then the version is left to 1.3. Here the simple implementation of a header in a subclass: class PDF(FPDF, HTMLMixin):
def header(self):
self.set_xy(100, 10)
#self.image('fa_app/static/logo.png', 165, 5, 40, 0, '', 'https://test.com')
self.image('fa_app/static/logo.svg', 165, 5, 40, 0, '', 'https://test.com')
self.set_y(20) |
Oh, OK, I understand. Whenever a The is because the drawing API makes use of features (notably transparency and blending modes) that were introduced in PDF 1.4. You can still reset the document PDF version to 1.3 if you wish, before calling |
Thanks for making this clear. |
This code renders a "broken" table if Unicode fonts are used:
If you set "markdown=False" in the code above, the rendering is not broken any more.
This bug emerged after some latest changes in the master branch.
I am using Python 3.9.2 with the latest version of the master fpdf2 branch (2.5.1)
The text was updated successfully, but these errors were encountered: