Skip to content

Commit 43055c7

Browse files
committed
Add type-checking
1 parent e8c812f commit 43055c7

File tree

11 files changed

+443
-205
lines changed

11 files changed

+443
-205
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ lint: ## Run the formatter and linter.
2929
uv run ruff format .
3030
uv run ruff check --fix .
3131

32+
.PHONY: typecheck
33+
typecheck: ## Run the type checker.
34+
uv run mypy --ignore-missing-imports src tests
35+
3236
.PHONY: hooks
3337
hooks: ## Run all pre-commit hooks.
3438
uv run pre-commit run --all-files

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,6 @@ quote-style = "double"
8585

8686
[dependency-groups]
8787
dev = [
88+
"mypy>=1.18.1",
8889
"tox>=4.30.2",
8990
]

src/svglib/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
import textwrap
99
from datetime import datetime
1010
from os.path import basename, dirname, exists, splitext
11+
from typing import Optional
1112

1213
from reportlab.graphics import renderPDF
1314

1415
from svglib import svglib
1516

1617

17-
def svg2pdf(path, outputPat=None):
18+
def svg2pdf(path: str, outputPat: Optional[str] = None) -> None:
1819
"Convert an SVG file to a PDF one."
1920

2021
# derive output filename from output pattern
@@ -45,11 +46,11 @@ def svg2pdf(path, outputPat=None):
4546

4647

4748
# command-line usage stuff
48-
def main():
49+
def main() -> None:
4950
"""Main entry point for the CLI."""
5051
ext = "pdf"
5152
ext_caps = ext.upper()
52-
args = dict(
53+
format_args = dict(
5354
prog=basename(sys.argv[0]),
5455
version=svglib.__version__,
5556
author=svglib.__author__,
@@ -61,8 +62,8 @@ def main():
6162
ext=ext,
6263
ext_caps=ext_caps,
6364
)
64-
args["ts_pattern"] += ".%s" % args["ext"]
65-
desc = "{prog} v. {version}\n".format(**args)
65+
format_args["ts_pattern"] += ".%s" % format_args["ext"]
66+
desc = "{prog} v. {version}\n".format(**format_args)
6667
desc += "A converter from SVG to {} (via ReportLab Graphics)\n".format(ext_caps)
6768
epilog = textwrap.dedent(
6869
"""\
@@ -88,7 +89,7 @@ def main():
8889
https://github.com/deeplook/svglib
8990
9091
Copyleft by {author}, 2008-{copyleft_year} ({license}):
91-
https://www.gnu.org/licenses/lgpl-3.0.html""".format(**args)
92+
https://www.gnu.org/licenses/lgpl-3.0.html""".format(**format_args)
9293
)
9394
p = argparse.ArgumentParser(
9495
description=desc,

src/svglib/fonts.py

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import subprocess
77
import sys
8+
from typing import Dict, Optional, Tuple, Union
89

910
from reportlab.pdfbase.pdfmetrics import registerFont
1011
from reportlab.pdfbase.ttfonts import TTFError, TTFont
@@ -37,7 +38,7 @@ class FontMap:
3738
them in reportlab.
3839
"""
3940

40-
def __init__(self):
41+
def __init__(self) -> None:
4142
"""
4243
The map has the form:
4344
'internal_name': {
@@ -46,12 +47,14 @@ def __init__(self):
4647
}
4748
for faster searching we use internal keys for finding the matching font
4849
"""
49-
self._map = {}
50+
self._map: Dict[str, Dict[str, Union[str, bool, int]]] = {}
5051

5152
self.register_default_fonts()
5253

5354
@staticmethod
54-
def build_internal_name(family, weight="normal", style="normal"):
55+
def build_internal_name(
56+
family: str, weight: str = "normal", style: str = "normal"
57+
) -> str:
5558
"""
5659
If the weight or style is given, append the capitalized weight and style
5760
to the font name. E.g. family="Arial", weight="bold" and style="italic"
@@ -71,7 +74,12 @@ def build_internal_name(family, weight="normal", style="normal"):
7174
return result_name
7275

7376
@staticmethod
74-
def guess_font_filename(basename, weight="normal", style="normal", extension="ttf"):
77+
def guess_font_filename(
78+
basename: str,
79+
weight: str = "normal",
80+
style: str = "normal",
81+
extension: str = "ttf",
82+
) -> str:
7583
"""
7684
Try to guess the actual font filename depending on family, weight and style,
7785
this works at least for windows on the "default" fonts like, Arial,
@@ -89,7 +97,9 @@ def guess_font_filename(basename, weight="normal", style="normal", extension="tt
8997
filename = f"{basename}{prefix}.{extension}"
9098
return filename
9199

92-
def use_fontconfig(self, font_name, weight="normal", style="normal"):
100+
def use_fontconfig(
101+
self, font_name: str, weight: str = "normal", style: str = "normal"
102+
) -> Tuple[Optional[str], bool]:
93103
NOT_FOUND = (None, False)
94104
# Searching with Fontconfig
95105
try:
@@ -124,7 +134,7 @@ def use_fontconfig(self, font_name, weight="normal", style="normal"):
124134
}
125135
return font_name, exact
126136

127-
def register_default_fonts(self):
137+
def register_default_fonts(self) -> None:
128138
self.register_font("Times New Roman", rlgFontName="Times-Roman")
129139
self.register_font("Times New Roman", weight="bold", rlgFontName="Times-Bold")
130140
self.register_font(
@@ -198,8 +208,13 @@ def register_default_fonts(self):
198208
)
199209

200210
def register_font_family(
201-
self, family, normal, bold=None, italic=None, bolditalic=None
202-
):
211+
self,
212+
family: str,
213+
normal: str,
214+
bold: Optional[str] = None,
215+
italic: Optional[str] = None,
216+
bolditalic: Optional[str] = None,
217+
) -> None:
203218
self.register_font(family, normal)
204219
if bold is not None:
205220
self.register_font(family, bold, weight="bold")
@@ -210,12 +225,12 @@ def register_font_family(
210225

211226
def register_font(
212227
self,
213-
font_family,
214-
font_path=None,
215-
weight="normal",
216-
style="normal",
217-
rlgFontName=None,
218-
):
228+
font_family: str,
229+
font_path: Optional[str] = None,
230+
weight: str = "normal",
231+
style: str = "normal",
232+
rlgFontName: Optional[str] = None,
233+
) -> Tuple[Optional[str], bool]:
219234
"""
220235
Register a font identified by its family, weight and style linked to an
221236
actual fontfile. Or map an svg font family, weight and style combination
@@ -253,47 +268,69 @@ def register_font(
253268
except TTFError:
254269
return NOT_FOUND
255270

256-
def find_font(self, font_name, weight="normal", style="normal"):
271+
# If we reach here, no registration was possible
272+
return NOT_FOUND
273+
274+
def find_font(
275+
self, font_name: str, weight: str = "normal", style: str = "normal"
276+
) -> Tuple[str, bool]:
257277
"""Return the font and a Boolean indicating if the match is exact."""
258278
internal_name = FontMap.build_internal_name(font_name, weight, style)
259279
# Step 1 check if the font is one of the buildin standard fonts
260280
if internal_name in STANDARD_FONT_NAMES:
261281
return internal_name, True
262282
# Step 2 Check if font is already registered
263283
if internal_name in self._map:
284+
font_entry = self._map[internal_name]
264285
return (
265-
self._map[internal_name]["rlgFont"],
266-
self._map[internal_name]["exact"],
286+
str(font_entry["rlgFont"]),
287+
bool(font_entry["exact"]),
267288
)
268289
# Step 3 Try to auto register the font
269290
# Try first to register the font if it exists as ttf
270291
guessed_filename = FontMap.guess_font_filename(font_name, weight, style)
271292
reg_name, exact = self.register_font(font_name, guessed_filename)
272293
if reg_name is not None:
273294
return reg_name, exact
274-
return self.use_fontconfig(font_name, weight, style)
295+
fontconfig_result = self.use_fontconfig(font_name, weight, style)
296+
if fontconfig_result[0] is not None:
297+
return fontconfig_result[0], fontconfig_result[1]
298+
# Fallback to default font if nothing found
299+
return DEFAULT_FONT_NAME, False
275300

276301

277302
_font_map = FontMap() # the global font map
278303

279304

280305
def register_font(
281-
font_name, font_path=None, weight="normal", style="normal", rlgFontName=None
282-
):
306+
font_name: str,
307+
font_path: Optional[str] = None,
308+
weight: str = "normal",
309+
style: str = "normal",
310+
rlgFontName: Optional[str] = None,
311+
) -> Tuple[Optional[str], bool]:
283312
"""
284313
Register a font by name or alias and path to font including file extension.
285314
"""
286315
return _font_map.register_font(font_name, font_path, weight, style, rlgFontName)
287316

288317

289-
def find_font(font_name, weight="normal", style="normal"):
318+
def find_font(
319+
font_name: str, weight: str = "normal", style: str = "normal"
320+
) -> Tuple[str, bool]:
290321
"""Return the font and a Boolean indicating if the match is exact."""
291322
return _font_map.find_font(font_name, weight, style)
292323

293324

294-
def register_font_family(self, family, normal, bold=None, italic=None, bolditalic=None):
325+
def register_font_family(
326+
family: str,
327+
normal: str,
328+
bold: Optional[str] = None,
329+
italic: Optional[str] = None,
330+
bolditalic: Optional[str] = None,
331+
) -> None:
295332
_font_map.register_font_family(family, normal, bold, italic, bolditalic)
296333

297334

298-
def get_global_font_map():
335+
def get_global_font_map() -> FontMap:
299336
return _font_map

0 commit comments

Comments
 (0)