Added type hints to FontFile and subclasses

This commit is contained in:
Andrew Murray 2023-12-27 11:36:16 +11:00
parent e9252a9353
commit 85818cd616
5 changed files with 76 additions and 38 deletions

View File

@ -22,6 +22,8 @@ Parse X Bitmap Distribution Format (BDF)
""" """
from __future__ import annotations from __future__ import annotations
from typing import BinaryIO
from . import FontFile, Image from . import FontFile, Image
bdf_slant = { bdf_slant = {
@ -36,7 +38,17 @@ bdf_slant = {
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"} bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
def bdf_char(f): def bdf_char(
f: BinaryIO,
) -> (
tuple[
str,
int,
tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]],
Image.Image,
]
| None
):
# skip to STARTCHAR # skip to STARTCHAR
while True: while True:
s = f.readline() s = f.readline()
@ -56,13 +68,12 @@ def bdf_char(f):
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
# load bitmap # load bitmap
bitmap = [] bitmap = bytearray()
while True: while True:
s = f.readline() s = f.readline()
if not s or s[:7] == b"ENDCHAR": if not s or s[:7] == b"ENDCHAR":
break break
bitmap.append(s[:-1]) bitmap += s[:-1]
bitmap = b"".join(bitmap)
# The word BBX # The word BBX
# followed by the width in x (BBw), height in y (BBh), # followed by the width in x (BBw), height in y (BBh),
@ -92,7 +103,7 @@ def bdf_char(f):
class BdfFontFile(FontFile.FontFile): class BdfFontFile(FontFile.FontFile):
"""Font file plugin for the X11 BDF format.""" """Font file plugin for the X11 BDF format."""
def __init__(self, fp): def __init__(self, fp: BinaryIO):
super().__init__() super().__init__()
s = fp.readline() s = fp.readline()

View File

@ -16,13 +16,16 @@
from __future__ import annotations from __future__ import annotations
import os import os
from typing import BinaryIO
from . import Image, _binary from . import Image, _binary
WIDTH = 800 WIDTH = 800
def puti16(fp, values): def puti16(
fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int]
) -> None:
"""Write network order (big-endian) 16-bit sequence""" """Write network order (big-endian) 16-bit sequence"""
for v in values: for v in values:
if v < 0: if v < 0:
@ -33,16 +36,34 @@ def puti16(fp, values):
class FontFile: class FontFile:
"""Base class for raster font file handlers.""" """Base class for raster font file handlers."""
bitmap = None bitmap: Image.Image | None = None
def __init__(self): def __init__(self) -> None:
self.info = {} self.info: dict[bytes, bytes | int] = {}
self.glyph = [None] * 256 self.glyph: list[
tuple[
tuple[int, int],
tuple[int, int, int, int],
tuple[int, int, int, int],
Image.Image,
]
| None
] = [None] * 256
def __getitem__(self, ix): def __getitem__(
self, ix: int
) -> (
tuple[
tuple[int, int],
tuple[int, int, int, int],
tuple[int, int, int, int],
Image.Image,
]
| None
):
return self.glyph[ix] return self.glyph[ix]
def compile(self): def compile(self) -> None:
"""Create metrics and bitmap""" """Create metrics and bitmap"""
if self.bitmap: if self.bitmap:
@ -51,7 +72,7 @@ class FontFile:
# create bitmap large enough to hold all data # create bitmap large enough to hold all data
h = w = maxwidth = 0 h = w = maxwidth = 0
lines = 1 lines = 1
for glyph in self: for glyph in self.glyph:
if glyph: if glyph:
d, dst, src, im = glyph d, dst, src, im = glyph
h = max(h, src[3] - src[1]) h = max(h, src[3] - src[1])
@ -71,7 +92,10 @@ class FontFile:
# paste glyphs into bitmap # paste glyphs into bitmap
self.bitmap = Image.new("1", (xsize, ysize)) self.bitmap = Image.new("1", (xsize, ysize))
self.metrics = [None] * 256 self.metrics: list[
tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]]
| None
] = [None] * 256
x = y = 0 x = y = 0
for i in range(256): for i in range(256):
glyph = self[i] glyph = self[i]
@ -88,7 +112,7 @@ class FontFile:
self.bitmap.paste(im.crop(src), s) self.bitmap.paste(im.crop(src), s)
self.metrics[i] = d, dst, s self.metrics[i] = d, dst, s
def save(self, filename): def save(self, filename: str) -> None:
"""Save font""" """Save font"""
self.compile() self.compile()
@ -104,6 +128,6 @@ class FontFile:
for id in range(256): for id in range(256):
m = self.metrics[id] m = self.metrics[id]
if not m: if not m:
puti16(fp, [0] * 10) puti16(fp, (0,) * 10)
else: else:
puti16(fp, m[0] + m[1] + m[2]) puti16(fp, m[0] + m[1] + m[2])

View File

@ -1194,7 +1194,7 @@ class Image:
__copy__ = copy __copy__ = copy
def crop(self, box=None): def crop(self, box=None) -> Image:
""" """
Returns a rectangular region from this image. The box is a Returns a rectangular region from this image. The box is a
4-tuple defining the left, upper, right, and lower pixel 4-tuple defining the left, upper, right, and lower pixel
@ -1659,7 +1659,7 @@ class Image:
return self.im.entropy(extrema) return self.im.entropy(extrema)
return self.im.entropy() return self.im.entropy()
def paste(self, im, box=None, mask=None): def paste(self, im, box=None, mask=None) -> None:
""" """
Pastes another image into this image. The box argument is either Pastes another image into this image. The box argument is either
a 2-tuple giving the upper left corner, a 4-tuple defining the a 2-tuple giving the upper left corner, a 4-tuple defining the
@ -2352,7 +2352,7 @@ class Image:
(w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor (w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor
) )
def save(self, fp, format=None, **params): def save(self, fp, format=None, **params) -> None:
""" """
Saves this image under the given filename. If no format is Saves this image under the given filename. If no format is
specified, the format to use is determined from the filename specified, the format to use is determined from the filename
@ -2903,7 +2903,7 @@ def _check_size(size):
return True return True
def new(mode, size, color=0): def new(mode, size, color=0) -> Image:
""" """
Creates a new image with the given mode and size. Creates a new image with the given mode and size.
@ -2942,7 +2942,7 @@ def new(mode, size, color=0):
return im._new(core.fill(mode, size, color)) return im._new(core.fill(mode, size, color))
def frombytes(mode, size, data, decoder_name="raw", *args): def frombytes(mode, size, data, decoder_name="raw", *args) -> Image:
""" """
Creates a copy of an image memory from pixel data in a buffer. Creates a copy of an image memory from pixel data in a buffer.

View File

@ -18,6 +18,7 @@
from __future__ import annotations from __future__ import annotations
import io import io
from typing import BinaryIO, Callable
from . import FontFile, Image from . import FontFile, Image
from ._binary import i8 from ._binary import i8
@ -49,7 +50,7 @@ BYTES_PER_ROW = [
] ]
def sz(s, o): def sz(s: bytes, o: int) -> bytes:
return s[o : s.index(b"\0", o)] return s[o : s.index(b"\0", o)]
@ -58,7 +59,7 @@ class PcfFontFile(FontFile.FontFile):
name = "name" name = "name"
def __init__(self, fp, charset_encoding="iso8859-1"): def __init__(self, fp: BinaryIO, charset_encoding: str = "iso8859-1"):
self.charset_encoding = charset_encoding self.charset_encoding = charset_encoding
magic = l32(fp.read(4)) magic = l32(fp.read(4))
@ -104,7 +105,9 @@ class PcfFontFile(FontFile.FontFile):
bitmaps[ix], bitmaps[ix],
) )
def _getformat(self, tag): def _getformat(
self, tag: int
) -> tuple[BinaryIO, int, Callable[[bytes], int], Callable[[bytes], int]]:
format, size, offset = self.toc[tag] format, size, offset = self.toc[tag]
fp = self.fp fp = self.fp
@ -119,7 +122,7 @@ class PcfFontFile(FontFile.FontFile):
return fp, format, i16, i32 return fp, format, i16, i32
def _load_properties(self): def _load_properties(self) -> dict[bytes, bytes | int]:
# #
# font properties # font properties
@ -138,18 +141,16 @@ class PcfFontFile(FontFile.FontFile):
data = fp.read(i32(fp.read(4))) data = fp.read(i32(fp.read(4)))
for k, s, v in p: for k, s, v in p:
k = sz(data, k) property_value: bytes | int = sz(data, v) if s else v
if s: properties[sz(data, k)] = property_value
v = sz(data, v)
properties[k] = v
return properties return properties
def _load_metrics(self): def _load_metrics(self) -> list[tuple[int, int, int, int, int, int, int, int]]:
# #
# font metrics # font metrics
metrics = [] metrics: list[tuple[int, int, int, int, int, int, int, int]] = []
fp, format, i16, i32 = self._getformat(PCF_METRICS) fp, format, i16, i32 = self._getformat(PCF_METRICS)
@ -182,7 +183,9 @@ class PcfFontFile(FontFile.FontFile):
return metrics return metrics
def _load_bitmaps(self, metrics): def _load_bitmaps(
self, metrics: list[tuple[int, int, int, int, int, int, int, int]]
) -> list[Image.Image]:
# #
# bitmap data # bitmap data
@ -207,7 +210,7 @@ class PcfFontFile(FontFile.FontFile):
data = fp.read(bitmapsize) data = fp.read(bitmapsize)
pad = BYTES_PER_ROW[padindex] pad: Callable[[int], int] = BYTES_PER_ROW[padindex]
mode = "1;R" mode = "1;R"
if bitorder: if bitorder:
mode = "1" mode = "1"
@ -222,7 +225,7 @@ class PcfFontFile(FontFile.FontFile):
return bitmaps return bitmaps
def _load_encoding(self): def _load_encoding(self) -> list[int | None]:
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS) fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
first_col, last_col = i16(fp.read(2)), i16(fp.read(2)) first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
@ -233,7 +236,7 @@ class PcfFontFile(FontFile.FontFile):
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1) nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
# map character code to bitmap index # map character code to bitmap index
encoding = [None] * min(256, nencoding) encoding: list[int | None] = [None] * min(256, nencoding)
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)] encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]

View File

@ -18,7 +18,7 @@ from __future__ import annotations
from struct import pack, unpack_from from struct import pack, unpack_from
def i8(c): def i8(c) -> int:
return c if c.__class__ is int else c[0] return c if c.__class__ is int else c[0]
@ -57,7 +57,7 @@ def si16be(c, o=0):
return unpack_from(">h", c, o)[0] return unpack_from(">h", c, o)[0]
def i32le(c, o=0): def i32le(c, o=0) -> int:
""" """
Converts a 4-bytes (32 bits) string to an unsigned integer. Converts a 4-bytes (32 bits) string to an unsigned integer.
@ -94,7 +94,7 @@ def o32le(i):
return pack("<I", i) return pack("<I", i)
def o16be(i): def o16be(i) -> bytes:
return pack(">H", i) return pack(">H", i)