SFNTReader: define __getstate__/__setstate__ to reopen external file

Instead of copying to BytesIO, we can return the file name in getstate and reopen the file in setstate. This keeps the TTFont truly lazy as it avoids the extra copy
This commit is contained in:
Cosimo Lupo 2020-05-18 12:41:25 +01:00
parent 942fbfe07a
commit 72f9e7794a
No known key found for this signature in database
GPG Key ID: 179A8F0895A02F4F

View File

@ -123,31 +123,29 @@ class SFNTReader(object):
def close(self): def close(self):
self.file.close() self.file.close()
def __getstate__(self): # We define custom __getstate__ and __setstate__ to make SFNTReader pickle-able
"""Makes SFNTReader pickle/deepcopy-able even with TTFont loaded as lazy=True. # and deepcopy-able. When a TTFont is loaded as lazy=True, SFNTReader holds a
# reference to an external file object which is not pickleable. So in __getstate__
# we store the file name and current position, and in __setstate__ we reopen the
# same named file after unpickling.
If SFNTReader holds a reference to an external file object which is not def __getstate__(self):
pickleable, we copy the file data to an in-memory bytes stream, which is
pickelable and behaves just like a file.
"""
if isinstance(self.file, BytesIO): if isinstance(self.file, BytesIO):
# BytesIO is already pickleable, return the state unmodified
return self.__dict__ return self.__dict__
# remove unpickleable file attribute, and only store its name and pos
state = self.__dict__.copy() state = self.__dict__.copy()
state["file"] = _copy_file_to_bytes_stream(state["file"]) del state["file"]
state["_filename"] = self.file.name
state["_filepos"] = self.file.tell()
return state return state
def __setstate__(self, state):
def _copy_file_to_bytes_stream(f) -> BytesIO: if "file" not in state:
# Copy bytes from file object to BytesIO stream. self.file = open(state.pop("_filename"), "rb")
# The returned stream also records the original file `name` and current position. self.file.seek(state.pop("_filepos"))
pos = f.tell() self.__dict__.update(state)
f.seek(0)
buf = BytesIO(f.read())
f.seek(pos)
buf.seek(pos)
if hasattr(f, "name"):
buf.name = f.name
return buf
# default compression level for WOFF 1.0 tables and metadata # default compression level for WOFF 1.0 tables and metadata