Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ lint: ## Run the formatter and linter.
uv run ruff format .
uv run ruff check --fix .

.PHONY: typecheck
typecheck: ## Run the type checker.
uv run mypy --ignore-missing-imports src tests

.PHONY: hooks
hooks: ## Run all pre-commit hooks.
uv run pre-commit run --all-files
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,6 @@ quote-style = "double"

[dependency-groups]
dev = [
"mypy>=1.18.1",
"tox>=4.30.2",
]
13 changes: 7 additions & 6 deletions src/svglib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
import textwrap
from datetime import datetime
from os.path import basename, dirname, exists, splitext
from typing import Optional

from reportlab.graphics import renderPDF

from svglib import svglib


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

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


# command-line usage stuff
def main():
def main() -> None:
"""Main entry point for the CLI."""
ext = "pdf"
ext_caps = ext.upper()
args = dict(
format_args = dict(
prog=basename(sys.argv[0]),
version=svglib.__version__,
author=svglib.__author__,
Expand All @@ -61,8 +62,8 @@ def main():
ext=ext,
ext_caps=ext_caps,
)
args["ts_pattern"] += ".%s" % args["ext"]
desc = "{prog} v. {version}\n".format(**args)
format_args["ts_pattern"] += ".%s" % format_args["ext"]
desc = "{prog} v. {version}\n".format(**format_args)
desc += "A converter from SVG to {} (via ReportLab Graphics)\n".format(ext_caps)
epilog = textwrap.dedent(
"""\
Expand All @@ -88,7 +89,7 @@ def main():
https://github.com/deeplook/svglib

Copyleft by {author}, 2008-{copyleft_year} ({license}):
https://www.gnu.org/licenses/lgpl-3.0.html""".format(**args)
https://www.gnu.org/licenses/lgpl-3.0.html""".format(**format_args)
)
p = argparse.ArgumentParser(
description=desc,
Expand Down
83 changes: 60 additions & 23 deletions src/svglib/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import subprocess
import sys
from typing import Dict, Optional, Tuple, Union

from reportlab.pdfbase.pdfmetrics import registerFont
from reportlab.pdfbase.ttfonts import TTFError, TTFont
Expand Down Expand Up @@ -37,7 +38,7 @@ class FontMap:
them in reportlab.
"""

def __init__(self):
def __init__(self) -> None:
"""
The map has the form:
'internal_name': {
Expand All @@ -46,12 +47,14 @@ def __init__(self):
}
for faster searching we use internal keys for finding the matching font
"""
self._map = {}
self._map: Dict[str, Dict[str, Union[str, bool, int]]] = {}

self.register_default_fonts()

@staticmethod
def build_internal_name(family, weight="normal", style="normal"):
def build_internal_name(
family: str, weight: str = "normal", style: str = "normal"
) -> str:
"""
If the weight or style is given, append the capitalized weight and style
to the font name. E.g. family="Arial", weight="bold" and style="italic"
Expand All @@ -71,7 +74,12 @@ def build_internal_name(family, weight="normal", style="normal"):
return result_name

@staticmethod
def guess_font_filename(basename, weight="normal", style="normal", extension="ttf"):
def guess_font_filename(
basename: str,
weight: str = "normal",
style: str = "normal",
extension: str = "ttf",
) -> str:
"""
Try to guess the actual font filename depending on family, weight and style,
this works at least for windows on the "default" fonts like, Arial,
Expand All @@ -89,7 +97,9 @@ def guess_font_filename(basename, weight="normal", style="normal", extension="tt
filename = f"{basename}{prefix}.{extension}"
return filename

def use_fontconfig(self, font_name, weight="normal", style="normal"):
def use_fontconfig(
self, font_name: str, weight: str = "normal", style: str = "normal"
) -> Tuple[Optional[str], bool]:
NOT_FOUND = (None, False)
# Searching with Fontconfig
try:
Expand Down Expand Up @@ -124,7 +134,7 @@ def use_fontconfig(self, font_name, weight="normal", style="normal"):
}
return font_name, exact

def register_default_fonts(self):
def register_default_fonts(self) -> None:
self.register_font("Times New Roman", rlgFontName="Times-Roman")
self.register_font("Times New Roman", weight="bold", rlgFontName="Times-Bold")
self.register_font(
Expand Down Expand Up @@ -198,8 +208,13 @@ def register_default_fonts(self):
)

def register_font_family(
self, family, normal, bold=None, italic=None, bolditalic=None
):
self,
family: str,
normal: str,
bold: Optional[str] = None,
italic: Optional[str] = None,
bolditalic: Optional[str] = None,
) -> None:
self.register_font(family, normal)
if bold is not None:
self.register_font(family, bold, weight="bold")
Expand All @@ -210,12 +225,12 @@ def register_font_family(

def register_font(
self,
font_family,
font_path=None,
weight="normal",
style="normal",
rlgFontName=None,
):
font_family: str,
font_path: Optional[str] = None,
weight: str = "normal",
style: str = "normal",
rlgFontName: Optional[str] = None,
) -> Tuple[Optional[str], bool]:
"""
Register a font identified by its family, weight and style linked to an
actual fontfile. Or map an svg font family, weight and style combination
Expand Down Expand Up @@ -253,47 +268,69 @@ def register_font(
except TTFError:
return NOT_FOUND

def find_font(self, font_name, weight="normal", style="normal"):
# If we reach here, no registration was possible
return NOT_FOUND

def find_font(
self, font_name: str, weight: str = "normal", style: str = "normal"
) -> Tuple[str, bool]:
"""Return the font and a Boolean indicating if the match is exact."""
internal_name = FontMap.build_internal_name(font_name, weight, style)
# Step 1 check if the font is one of the buildin standard fonts
if internal_name in STANDARD_FONT_NAMES:
return internal_name, True
# Step 2 Check if font is already registered
if internal_name in self._map:
font_entry = self._map[internal_name]
return (
self._map[internal_name]["rlgFont"],
self._map[internal_name]["exact"],
str(font_entry["rlgFont"]),
bool(font_entry["exact"]),
)
# Step 3 Try to auto register the font
# Try first to register the font if it exists as ttf
guessed_filename = FontMap.guess_font_filename(font_name, weight, style)
reg_name, exact = self.register_font(font_name, guessed_filename)
if reg_name is not None:
return reg_name, exact
return self.use_fontconfig(font_name, weight, style)
fontconfig_result = self.use_fontconfig(font_name, weight, style)
if fontconfig_result[0] is not None:
return fontconfig_result[0], fontconfig_result[1]
# Fallback to default font if nothing found
return DEFAULT_FONT_NAME, False


_font_map = FontMap() # the global font map


def register_font(
font_name, font_path=None, weight="normal", style="normal", rlgFontName=None
):
font_name: str,
font_path: Optional[str] = None,
weight: str = "normal",
style: str = "normal",
rlgFontName: Optional[str] = None,
) -> Tuple[Optional[str], bool]:
"""
Register a font by name or alias and path to font including file extension.
"""
return _font_map.register_font(font_name, font_path, weight, style, rlgFontName)


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


def register_font_family(self, family, normal, bold=None, italic=None, bolditalic=None):
def register_font_family(
family: str,
normal: str,
bold: Optional[str] = None,
italic: Optional[str] = None,
bolditalic: Optional[str] = None,
) -> None:
_font_map.register_font_family(family, normal, bold, italic, bolditalic)


def get_global_font_map():
def get_global_font_map() -> FontMap:
return _font_map
Loading
Loading