Skip to content

Commit 78b96c0

Browse files
authored
Merge pull request #7643 from radarhere/type_hints
Added type hints to FontFile and subclasses
2 parents ca9b49f + 0aebd57 commit 78b96c0

6 files changed

Lines changed: 94 additions & 41 deletions

File tree

Tests/test_fontfile.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from __future__ import annotations
2+
import pytest
3+
4+
from PIL import FontFile
5+
6+
7+
def test_save(tmp_path):
8+
tempname = str(tmp_path / "temp.pil")
9+
10+
font = FontFile.FontFile()
11+
with pytest.raises(ValueError):
12+
font.save(tempname)

src/PIL/BdfFontFile.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
"""
2323
from __future__ import annotations
2424

25+
from typing import BinaryIO
26+
2527
from . import FontFile, Image
2628

2729
bdf_slant = {
@@ -36,7 +38,17 @@
3638
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
3739

3840

39-
def bdf_char(f):
41+
def bdf_char(
42+
f: BinaryIO,
43+
) -> (
44+
tuple[
45+
str,
46+
int,
47+
tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]],
48+
Image.Image,
49+
]
50+
| None
51+
):
4052
# skip to STARTCHAR
4153
while True:
4254
s = f.readline()
@@ -56,13 +68,12 @@ def bdf_char(f):
5668
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
5769

5870
# load bitmap
59-
bitmap = []
71+
bitmap = bytearray()
6072
while True:
6173
s = f.readline()
6274
if not s or s[:7] == b"ENDCHAR":
6375
break
64-
bitmap.append(s[:-1])
65-
bitmap = b"".join(bitmap)
76+
bitmap += s[:-1]
6677

6778
# The word BBX
6879
# followed by the width in x (BBw), height in y (BBh),
@@ -92,7 +103,7 @@ def bdf_char(f):
92103
class BdfFontFile(FontFile.FontFile):
93104
"""Font file plugin for the X11 BDF format."""
94105

95-
def __init__(self, fp):
106+
def __init__(self, fp: BinaryIO):
96107
super().__init__()
97108

98109
s = fp.readline()

src/PIL/FontFile.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
from __future__ import annotations
1717

1818
import os
19+
from typing import BinaryIO
1920

2021
from . import Image, _binary
2122

2223
WIDTH = 800
2324

2425

25-
def puti16(fp, values):
26+
def puti16(
27+
fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int]
28+
) -> None:
2629
"""Write network order (big-endian) 16-bit sequence"""
2730
for v in values:
2831
if v < 0:
@@ -33,16 +36,34 @@ def puti16(fp, values):
3336
class FontFile:
3437
"""Base class for raster font file handlers."""
3538

36-
bitmap = None
37-
38-
def __init__(self):
39-
self.info = {}
40-
self.glyph = [None] * 256
41-
42-
def __getitem__(self, ix):
39+
bitmap: Image.Image | None = None
40+
41+
def __init__(self) -> None:
42+
self.info: dict[bytes, bytes | int] = {}
43+
self.glyph: list[
44+
tuple[
45+
tuple[int, int],
46+
tuple[int, int, int, int],
47+
tuple[int, int, int, int],
48+
Image.Image,
49+
]
50+
| None
51+
] = [None] * 256
52+
53+
def __getitem__(
54+
self, ix: int
55+
) -> (
56+
tuple[
57+
tuple[int, int],
58+
tuple[int, int, int, int],
59+
tuple[int, int, int, int],
60+
Image.Image,
61+
]
62+
| None
63+
):
4364
return self.glyph[ix]
4465

45-
def compile(self):
66+
def compile(self) -> None:
4667
"""Create metrics and bitmap"""
4768

4869
if self.bitmap:
@@ -51,7 +72,7 @@ def compile(self):
5172
# create bitmap large enough to hold all data
5273
h = w = maxwidth = 0
5374
lines = 1
54-
for glyph in self:
75+
for glyph in self.glyph:
5576
if glyph:
5677
d, dst, src, im = glyph
5778
h = max(h, src[3] - src[1])
@@ -65,13 +86,16 @@ def compile(self):
6586
ysize = lines * h
6687

6788
if xsize == 0 and ysize == 0:
68-
return ""
89+
return
6990

7091
self.ysize = h
7192

7293
# paste glyphs into bitmap
7394
self.bitmap = Image.new("1", (xsize, ysize))
74-
self.metrics = [None] * 256
95+
self.metrics: list[
96+
tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]]
97+
| None
98+
] = [None] * 256
7599
x = y = 0
76100
for i in range(256):
77101
glyph = self[i]
@@ -88,12 +112,15 @@ def compile(self):
88112
self.bitmap.paste(im.crop(src), s)
89113
self.metrics[i] = d, dst, s
90114

91-
def save(self, filename):
115+
def save(self, filename: str) -> None:
92116
"""Save font"""
93117

94118
self.compile()
95119

96120
# font data
121+
if not self.bitmap:
122+
msg = "No bitmap created"
123+
raise ValueError(msg)
97124
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
98125

99126
# font metrics
@@ -104,6 +131,6 @@ def save(self, filename):
104131
for id in range(256):
105132
m = self.metrics[id]
106133
if not m:
107-
puti16(fp, [0] * 10)
134+
puti16(fp, (0,) * 10)
108135
else:
109136
puti16(fp, m[0] + m[1] + m[2])

src/PIL/Image.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,7 @@ def copy(self) -> Image:
11941194

11951195
__copy__ = copy
11961196

1197-
def crop(self, box=None):
1197+
def crop(self, box=None) -> Image:
11981198
"""
11991199
Returns a rectangular region from this image. The box is a
12001200
4-tuple defining the left, upper, right, and lower pixel
@@ -1659,7 +1659,7 @@ def entropy(self, mask=None, extrema=None):
16591659
return self.im.entropy(extrema)
16601660
return self.im.entropy()
16611661

1662-
def paste(self, im, box=None, mask=None):
1662+
def paste(self, im, box=None, mask=None) -> None:
16631663
"""
16641664
Pastes another image into this image. The box argument is either
16651665
a 2-tuple giving the upper left corner, a 4-tuple defining the
@@ -2352,7 +2352,7 @@ def transform(x, y, matrix):
23522352
(w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor
23532353
)
23542354

2355-
def save(self, fp, format=None, **params):
2355+
def save(self, fp, format=None, **params) -> None:
23562356
"""
23572357
Saves this image under the given filename. If no format is
23582358
specified, the format to use is determined from the filename
@@ -2903,7 +2903,7 @@ def _check_size(size):
29032903
return True
29042904

29052905

2906-
def new(mode, size, color=0):
2906+
def new(mode, size, color=0) -> Image:
29072907
"""
29082908
Creates a new image with the given mode and size.
29092909
@@ -2942,7 +2942,7 @@ def new(mode, size, color=0):
29422942
return im._new(core.fill(mode, size, color))
29432943

29442944

2945-
def frombytes(mode, size, data, decoder_name="raw", *args):
2945+
def frombytes(mode, size, data, decoder_name="raw", *args) -> Image:
29462946
"""
29472947
Creates a copy of an image memory from pixel data in a buffer.
29482948

src/PIL/PcfFontFile.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from __future__ import annotations
1919

2020
import io
21+
from typing import BinaryIO, Callable
2122

2223
from . import FontFile, Image
2324
from ._binary import i8
@@ -41,15 +42,15 @@
4142
PCF_GLYPH_NAMES = 1 << 7
4243
PCF_BDF_ACCELERATORS = 1 << 8
4344

44-
BYTES_PER_ROW = [
45+
BYTES_PER_ROW: list[Callable[[int], int]] = [
4546
lambda bits: ((bits + 7) >> 3),
4647
lambda bits: ((bits + 15) >> 3) & ~1,
4748
lambda bits: ((bits + 31) >> 3) & ~3,
4849
lambda bits: ((bits + 63) >> 3) & ~7,
4950
]
5051

5152

52-
def sz(s, o):
53+
def sz(s: bytes, o: int) -> bytes:
5354
return s[o : s.index(b"\0", o)]
5455

5556

@@ -58,7 +59,7 @@ class PcfFontFile(FontFile.FontFile):
5859

5960
name = "name"
6061

61-
def __init__(self, fp, charset_encoding="iso8859-1"):
62+
def __init__(self, fp: BinaryIO, charset_encoding: str = "iso8859-1"):
6263
self.charset_encoding = charset_encoding
6364

6465
magic = l32(fp.read(4))
@@ -104,7 +105,9 @@ def __init__(self, fp, charset_encoding="iso8859-1"):
104105
bitmaps[ix],
105106
)
106107

107-
def _getformat(self, tag):
108+
def _getformat(
109+
self, tag: int
110+
) -> tuple[BinaryIO, int, Callable[[bytes], int], Callable[[bytes], int]]:
108111
format, size, offset = self.toc[tag]
109112

110113
fp = self.fp
@@ -119,7 +122,7 @@ def _getformat(self, tag):
119122

120123
return fp, format, i16, i32
121124

122-
def _load_properties(self):
125+
def _load_properties(self) -> dict[bytes, bytes | int]:
123126
#
124127
# font properties
125128

@@ -138,18 +141,16 @@ def _load_properties(self):
138141
data = fp.read(i32(fp.read(4)))
139142

140143
for k, s, v in p:
141-
k = sz(data, k)
142-
if s:
143-
v = sz(data, v)
144-
properties[k] = v
144+
property_value: bytes | int = sz(data, v) if s else v
145+
properties[sz(data, k)] = property_value
145146

146147
return properties
147148

148-
def _load_metrics(self):
149+
def _load_metrics(self) -> list[tuple[int, int, int, int, int, int, int, int]]:
149150
#
150151
# font metrics
151152

152-
metrics = []
153+
metrics: list[tuple[int, int, int, int, int, int, int, int]] = []
153154

154155
fp, format, i16, i32 = self._getformat(PCF_METRICS)
155156

@@ -182,7 +183,9 @@ def _load_metrics(self):
182183

183184
return metrics
184185

185-
def _load_bitmaps(self, metrics):
186+
def _load_bitmaps(
187+
self, metrics: list[tuple[int, int, int, int, int, int, int, int]]
188+
) -> list[Image.Image]:
186189
#
187190
# bitmap data
188191

@@ -222,7 +225,7 @@ def _load_bitmaps(self, metrics):
222225

223226
return bitmaps
224227

225-
def _load_encoding(self):
228+
def _load_encoding(self) -> list[int | None]:
226229
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
227230

228231
first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
@@ -233,7 +236,7 @@ def _load_encoding(self):
233236
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
234237

235238
# map character code to bitmap index
236-
encoding = [None] * min(256, nencoding)
239+
encoding: list[int | None] = [None] * min(256, nencoding)
237240

238241
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]
239242

src/PIL/_binary.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from struct import pack, unpack_from
1919

2020

21-
def i8(c):
21+
def i8(c) -> int:
2222
return c if c.__class__ is int else c[0]
2323

2424

@@ -57,7 +57,7 @@ def si16be(c, o=0):
5757
return unpack_from(">h", c, o)[0]
5858

5959

60-
def i32le(c, o=0):
60+
def i32le(c, o=0) -> int:
6161
"""
6262
Converts a 4-bytes (32 bits) string to an unsigned integer.
6363
@@ -94,7 +94,7 @@ def o32le(i):
9494
return pack("<I", i)
9595

9696

97-
def o16be(i):
97+
def o16be(i) -> bytes:
9898
return pack(">H", i)
9999

100100

0 commit comments

Comments
 (0)