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

Add support for pickling TrueType fonts #5826

Merged
merged 3 commits into from Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 50 additions & 2 deletions Tests/test_pickle.py
Expand Up @@ -2,9 +2,12 @@

import pytest

from PIL import Image
from PIL import Image, ImageDraw, ImageFont

from .helper import skip_unless_feature
from .helper import assert_image_equal, skip_unless_feature

FONT_SIZE = 20
FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"


def helper_pickle_file(tmp_path, pickle, protocol, test_file, mode):
Expand Down Expand Up @@ -92,3 +95,48 @@ def test_pickle_tell():

# Assert
assert unpickled_image.tell() == 0


def helper_assert_pickled_font_images(font1, font2):
# Arrange
im1 = Image.new(mode="RGBA", size=(300, 100))
im2 = Image.new(mode="RGBA", size=(300, 100))
draw1 = ImageDraw.Draw(im1)
draw2 = ImageDraw.Draw(im2)
txt = "Hello World!"

# Act
draw1.text((10, 10), txt, font=font1)
draw2.text((10, 10), txt, font=font2)

# Assert
assert_image_equal(im1, im2)


@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
hugovk marked this conversation as resolved.
Show resolved Hide resolved
def test_pickle_font_string(protocol):
# Arrange
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)

# Act: roundtrip
pickled_font = pickle.dumps(font, protocol)
unpickled_font = pickle.loads(pickled_font)

# Assert
helper_assert_pickled_font_images(font, unpickled_font)


@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
hugovk marked this conversation as resolved.
Show resolved Hide resolved
def test_pickle_font_file(tmp_path, protocol):
# Arrange
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
filename = str(tmp_path / "temp.pkl")

# Act: roundtrip
with open(filename, "wb") as f:
pickle.dump(font, f, protocol)
with open(filename, "rb") as f:
unpickled_font = pickle.load(f)

# Assert
helper_assert_pickled_font_images(font, unpickled_font)
16 changes: 16 additions & 0 deletions docs/releasenotes/9.0.0.rst
Expand Up @@ -72,6 +72,22 @@ Support has been added for the "title" argument in
argument will also now be supported, e.g. ``im.show(title="My Image")`` and
``ImageShow.show(im, title="My Image")``.

Added support for pickling TrueType fonts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

TrueType fonts may now be pickled and unpickled. For example:

.. code-block:: python

import pickle
from PIL import ImageFont

font = ImageFont.truetype("arial.ttf", size=30)
pickled_font = pickle.dumps(font, protocol=pickle.HIGHEST_PROTOCOL)

# Later...
unpickled_font = pickle.loads(pickled_font)

radarhere marked this conversation as resolved.
Show resolved Hide resolved
Security
========

Expand Down
7 changes: 7 additions & 0 deletions src/PIL/ImageFont.py
Expand Up @@ -196,6 +196,13 @@ def load_from_bytes(f):
else:
load_from_bytes(font)

def __getstate__(self):
return [self.path, self.size, self.index, self.encoding, self.layout_engine]

def __setstate__(self, state):
path, size, index, encoding, layout_engine = state
self.__init__(path, size, index, encoding, layout_engine)

def _multiline_split(self, text):
split_character = "\n" if isinstance(text, str) else b"\n"
return text.split(split_character)
Expand Down