Add FontFile.to_imagefont() (#9419)

This commit is contained in:
Andrew Murray 2026-03-07 17:24:43 +11:00 committed by GitHub
commit 2c87ce2d3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 70 additions and 12 deletions

View File

@ -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)

View File

@ -1,5 +1,6 @@
from __future__ import annotations
from io import BytesIO
from pathlib import Path
import pytest
@ -7,6 +8,15 @@ import pytest
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()

View File

@ -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 <https://github.com/python-pillow/pillow-scripts/blob/main/Scripts/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

View File

@ -18,7 +18,7 @@ from __future__ import annotations
import os
from typing import BinaryIO
from . import Image, _binary
from . import Image, ImageFont, _binary
WIDTH = 800
@ -110,6 +110,22 @@ class FontFile:
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 @@ class FontFile:
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

View File

@ -149,6 +149,9 @@ class ImageFont:
# 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)