diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 569c2e85bb4..321dd85603a 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -75,6 +75,16 @@ def test_draw(request: pytest.FixtureRequest, tmp_path: Path) -> None: assert_image_equal_tofile(im, "Tests/images/test_draw_pbm_target.png") +def test_to_imagefont() -> None: + with open(fontname, "rb") as test_file: + pcffont = PcfFontFile.PcfFontFile(test_file) + imagefont = pcffont.to_imagefont() + im = Image.new("L", (130, 30), "white") + draw = ImageDraw.Draw(im) + draw.text((0, 0), message, "black", font=imagefont) + assert_image_equal_tofile(im, "Tests/images/test_draw_pbm_target.png") + + def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) diff --git a/Tests/test_fontfile.py b/Tests/test_fontfile.py index 575dada86cb..1a9069fd892 100644 --- a/Tests/test_fontfile.py +++ b/Tests/test_fontfile.py @@ -1,5 +1,6 @@ from __future__ import annotations +from io import BytesIO from pathlib import Path import pytest @@ -7,6 +8,15 @@ from PIL import FontFile, Image +def test_puti16() -> None: + fp = BytesIO() + FontFile.puti16(fp, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + assert fp.getvalue() == ( + b"\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04" + b"\x00\x05\x00\x06\x00\x07\x00\x08\x00\t" + ) + + def test_compile() -> None: font = FontFile.FontFile() font.glyph[0] = ((0, 0), (0, 0, 0, 0), (0, 0, 0, 1), Image.new("L", (0, 0))) @@ -24,5 +34,11 @@ def test_save(tmp_path: Path) -> None: tempname = str(tmp_path / "temp.pil") font = FontFile.FontFile() - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="No bitmap created"): font.save(tempname) + + +def test_to_imagefont() -> None: + font = FontFile.FontFile() + with pytest.raises(ValueError, match="No bitmap created"): + font.to_imagefont() diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index aac55fe6b05..920a05e65e9 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -8,10 +8,14 @@ The :py:mod:`~PIL.ImageFont` module defines a class with the same name. Instance this class store bitmap fonts, and are used with the :py:meth:`PIL.ImageDraw.ImageDraw.text` method. -PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use -`pilfont.py `_ -from :pypi:`pillow-scripts` to convert BDF and -PCF font descriptors (X window font formats) to this format. +Pillow uses its own font file format to store bitmap fonts, limited to 256 characters. You +can use :py:meth:`~PIL.FontFile.FontFile.to_imagefont` to convert BDF and PCF font +descriptors (X Window font formats) to this format:: + + from PIL import PcfFontFile + with open("Tests/fonts/10x20-ISO8859-1.pcf", "rb") as fp: + font = PcfFontFile.PcfFontFile(fp) + imagefont = font.to_imagefont() Starting with version 1.1.4, PIL can be configured to support TrueType and OpenType fonts (as well as other font formats supported by the FreeType diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 1e0c1c166b5..341431d3f45 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -18,7 +18,7 @@ import os from typing import BinaryIO -from . import Image, _binary +from . import Image, ImageFont, _binary WIDTH = 800 @@ -110,6 +110,22 @@ def compile(self) -> None: self.bitmap.paste(im.crop(src), s) self.metrics[i] = d, dst, s + def _encode_metrics(self) -> bytes: + values: list[int] = [] + for i in range(256): + m = self.metrics[i] + if m: + values.extend(m[0] + m[1] + m[2]) + else: + values.extend((0,) * 10) + + data = bytearray() + for v in values: + if v < 0: + v += 65536 + data += _binary.o16be(v) + return bytes(data) + def save(self, filename: str) -> None: """Save font""" @@ -126,9 +142,18 @@ def save(self, filename: str) -> None: fp.write(b"PILfont\n") fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!! fp.write(b"DATA\n") - for id in range(256): - m = self.metrics[id] - if not m: - puti16(fp, (0,) * 10) - else: - puti16(fp, m[0] + m[1] + m[2]) + fp.write(self._encode_metrics()) + + def to_imagefont(self) -> ImageFont.ImageFont: + """Convert to ImageFont""" + + self.compile() + + # font data + if not self.bitmap: + msg = "No bitmap created" + raise ValueError(msg) + + imagefont = ImageFont.ImageFont() + imagefont._load(self.bitmap, self._encode_metrics()) + return imagefont diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index ae003d139c9..ea7f4dc5477 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -149,6 +149,9 @@ def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: # read PILfont metrics data = file.read(256 * 20) + self._load(image, data) + + def _load(self, image: Image.Image, data: bytes) -> None: image.load() self.font = Image.core.font(image.im, data)