Combination of set_text_shaping and offset_rendering causing error
jsid72 opened this issue ยท 5 comments
Describe the bug
Use of set_text_shaping(True)
[needed for Arabic support] and offset_rendering()
[used to handle page break] in combination are causing an error. If we remove one of these two from the code then things work fine.
Error details
Traceback (most recent call last)
Cell In[54], line 21
19 col_width = pdf.epw / 4 # distribute content evenly
20 for i in range(5): # repeat table 4 times
---> 21 with pdf.offset_rendering() as dummy:
22 for row in data: # data comes from snippets on the Tables documentation page
23 for datum in row:
File C:\anaconda3\lib\contextlib.py:119, in _GeneratorContextManager.__enter__(self)
117 del self.args, self.kwds, self.func
118 try:
--> 119 return next(self.gen)
120 except StopIteration:
121 raise RuntimeError("generator didn't yield") from None
File C:\PythonPDFMaking\lib\site-packages\fpdf\fpdf.py:4678, in FPDF.offset_rendering(self)
4672 """
4673 All rendering performed in this context is made on a dummy FPDF object.
4674 This allows to test the results of some operations on the global layout
4675 before performing them "for real".
4676 """
4677 prev_page, prev_y = self.page, self.y
-> 4678 recorder = FPDFRecorder(self, accept_page_break=False)
4679 recorder.page_break_triggered = False
4680 yield recorder
File C:\PythonPDFMaking\lib\site-packages\fpdf\recorder.py:37, in FPDFRecorder.__init__(self, pdf, accept_page_break)
35 def __init__(self, pdf, accept_page_break=True):
36 self.pdf = pdf
---> 37 self._initial = deepcopy(self.pdf.__dict__)
38 self._calls = []
39 if not accept_page_break:
File C:\anaconda3\lib\copy.py:146, in deepcopy(x, memo, _nil)
144 copier = _deepcopy_dispatch.get(cls)
145 if copier is not None:
--> 146 y = copier(x, memo)
147 else:
148 if issubclass(cls, type):
File C:\anaconda3\lib\copy.py:230, in _deepcopy_dict(x, memo, deepcopy)
228 memo[id(x)] = y
229 for key, value in x.items():
--> 230 y[deepcopy(key, memo)] = deepcopy(value, memo)
231 return y
File C:\anaconda3\lib\copy.py:146, in deepcopy(x, memo, _nil)
144 copier = _deepcopy_dispatch.get(cls)
145 if copier is not None:
--> 146 y = copier(x, memo)
147 else:
148 if issubclass(cls, type):
File C:\anaconda3\lib\copy.py:205, in _deepcopy_list(x, memo, deepcopy)
203 append = y.append
204 for a in x:
--> 205 append(deepcopy(a, memo))
206 return y
File C:\anaconda3\lib\copy.py:146, in deepcopy(x, memo, _nil)
144 copier = _deepcopy_dispatch.get(cls)
145 if copier is not None:
--> 146 y = copier(x, memo)
147 else:
148 if issubclass(cls, type):
File C:\anaconda3\lib\copy.py:230, in _deepcopy_dict(x, memo, deepcopy)
228 memo[id(x)] = y
229 for key, value in x.items():
--> 230 y[deepcopy(key, memo)] = deepcopy(value, memo)
231 return y
File C:\anaconda3\lib\copy.py:172, in deepcopy(x, memo, _nil)
170 y = x
171 else:
--> 172 y = _reconstruct(x, memo, *rv)
174 # If is its own copy, don't memoize.
175 if y is not x:
File C:\anaconda3\lib\copy.py:270, in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
268 if state is not None:
269 if deep:
--> 270 state = deepcopy(state, memo)
271 if hasattr(y, '__setstate__'):
272 y.__setstate__(state)
File C:\anaconda3\lib\copy.py:146, in deepcopy(x, memo, _nil)
144 copier = _deepcopy_dispatch.get(cls)
145 if copier is not None:
--> 146 y = copier(x, memo)
147 else:
148 if issubclass(cls, type):
File C:\anaconda3\lib\copy.py:210, in _deepcopy_tuple(x, memo, deepcopy)
209 def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
--> 210 y = [deepcopy(a, memo) for a in x]
211 # We're not going to put the tuple in the memo, but it's still important we
212 # check for it, in case the tuple contains recursive mutable structures.
213 try:
File C:\anaconda3\lib\copy.py:210, in <listcomp>(.0)
209 def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
--> 210 y = [deepcopy(a, memo) for a in x]
211 # We're not going to put the tuple in the memo, but it's still important we
212 # check for it, in case the tuple contains recursive mutable structures.
213 try:
File C:\anaconda3\lib\copy.py:146, in deepcopy(x, memo, _nil)
144 copier = _deepcopy_dispatch.get(cls)
145 if copier is not None:
--> 146 y = copier(x, memo)
147 else:
148 if issubclass(cls, type):
File C:\anaconda3\lib\copy.py:230, in _deepcopy_dict(x, memo, deepcopy)
228 memo[id(x)] = y
229 for key, value in x.items():
--> 230 y[deepcopy(key, memo)] = deepcopy(value, memo)
231 return y
File C:\anaconda3\lib\copy.py:161, in deepcopy(x, memo, _nil)
159 reductor = getattr(x, "__reduce_ex__", None)
160 if reductor is not None:
--> 161 rv = reductor(4)
162 else:
163 reductor = getattr(x, "_ _reduce_ _", None)
File <stringsource>:2, in uharfbuzz._harfbuzz.Font.__reduce_cython__()
TypeError: no default __reduce__ due to non-trivial __cinit__
Minimal code
data = (
("01", "A", "6"),
("02", "B", "6"),
("03", "C", "1"),
("04", "D", "1"),
("05", "E", "4"),
)
from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
font_folder = r'C:\WINDOWS\FONTS\\'
pdf.add_font('Calibri','',font_folder + 'calibri.ttf')
pdf.set_text_shaping(True)
pdf.set_font("Calibri", size=16)
line_height = pdf.font_size * 2
col_width = pdf.epw / 4 # distribute content evenly
for i in range(5): # repeat table 4 times
with pdf.offset_rendering() as dummy:
for row in data: # data comes from snippets on the Tables documentation page
for datum in row:
dummy.cell(col_width, line_height, f"{datum} ({i})", border=1)
dummy.ln(line_height)
dummy.ln(line_height * 2)
if dummy.page_break_triggered:
pdf.add_page()
pdf.cell(text="Appendix C")
pdf.ln(line_height)
for row in data: # data comes from snippets on the Tables documentation page
for datum in row:
pdf.cell(col_width, line_height, f"{datum} ({i})", border=1)
pdf.ln(line_height)
pdf.ln(line_height * 2)
pdf.output("unbreakable_tables.pdf")
Environment
- Operating System: Windows 10
- Python version: 3.9.13
fpdf2
version used: 2.7.6
Thank you for the clear bug report @jsid72!
Seems like uharfbuzz.Font
does not like being deepcopied:
https://github.com/py-pdf/fpdf2/blob/2.7.7/fpdf/fonts.py#L275
There is some really minimal code reproducing this error:
from copy import deepcopy
import uharfbuzz as hb
font = hb.Font(hb.Face(hb.Blob.from_file_path('calibri.ttf')))
deepcopy(font)
I opened #1084 to fix this
Could you please review it @andersonhc?
@allcontributors please add @jsid72 for bug
I've put up a pull request to add @jsid72! ๐
The fix has been merged into the master
branch of this repo, but not released yet.
You can install this unreleased latest version this way, if you want to test that this fix solves your initial problem:
pip install git+https://github.com/py-pdf/fpdf2.git@master