Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 5 additions & 5 deletions .github/workflows/run_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ jobs:
architecture: ${{ matrix.architecture }}

- name: Install Python requirements
run: pip install --upgrade --upgrade-strategy eager .

- name: Install pytest
run: |
pip install pytest
run: pip install --upgrade --upgrade-strategy eager .[dev]

- name: Run tests
run: |
pytest

- name: Typecheck with mypy
run: |
mypy
8 changes: 6 additions & 2 deletions find_system_fonts_filename/android/android_fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

class AndroidFonts(SystemFonts):

@staticmethod
def get_system_fonts_filename() -> Set[str]:
android = Android()
fonts_filename = set()
Expand All @@ -37,14 +38,17 @@ def get_system_fonts_filename() -> Set[str]:
return fonts_filename


def install_font(font_filename: Path, windows_flags: bool) -> None:
@staticmethod
def install_font(font_filename: Path, add_font_to_registry: bool = False) -> None:
raise OSNotSupported("You cannot install font on android.")


def uninstall_font(font_filename: Path, windows_flags: bool) -> None:
@staticmethod
def uninstall_font(font_filename: Path, remove_font_in_registry: bool) -> None:
raise OSNotSupported("You cannot uninstall font on android.")


@staticmethod
@contextmanager
def _silence_stderr_and_stdout():
# From: https://stackoverflow.com/a/75037627/15835974
Expand Down
4 changes: 2 additions & 2 deletions find_system_fonts_filename/fonts_filename.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from os import name
from pathlib import Path
from platform import system
from typing import Set
from typing import Set, Type
from .exceptions import OSNotSupported
from .system_fonts import SystemFonts

Expand All @@ -13,7 +13,7 @@
]


def get_system_fonts_class() -> SystemFonts:
def get_system_fonts_class() -> Type[SystemFonts]:
system_name = system()

if system_name == "Windows":
Expand Down
7 changes: 5 additions & 2 deletions find_system_fonts_filename/mac/mac_fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class MacFonts(SystemFonts):
# So, we also need to check the file extension to see if the file is valid.
VALID_FONT_FORMATS = ["ttf", "otf", "ttc", "otc"]

@staticmethod
def get_system_fonts_filename() -> Set[str]:
if not MacVersionHelpers.is_mac_version_or_greater(10, 6):
raise OSNotSupported("FindSystemFontsFilename only works on Mac 10.6 or more")
Expand Down Expand Up @@ -60,7 +61,8 @@ def get_system_fonts_filename() -> Set[str]:
return fonts_filename


def install_font(font_filename: Path, windows_flags: bool) -> None:
@staticmethod
def install_font(font_filename: Path, add_font_to_registry: bool = False) -> None:
if not MacVersionHelpers.is_mac_version_or_greater(10, 6):
raise OSNotSupported("FindSystemFontsFilename only works on Mac 10.6 or more")

Expand All @@ -79,7 +81,8 @@ def install_font(font_filename: Path, windows_flags: bool) -> None:
raise FindSystemFontsFilenameException(f"The font file \"{font_filename}\" could not be installed.")


def uninstall_font(font_filename: Path, windows_flags: bool) -> None:
@staticmethod
def uninstall_font(font_filename: Path, remove_font_in_registry: bool) -> None:
if not MacVersionHelpers.is_mac_version_or_greater(10, 6):
raise OSNotSupported("FindSystemFontsFilename only works on Mac 10.6 or more")

Expand Down
6 changes: 3 additions & 3 deletions find_system_fonts_filename/mac/version_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def is_mac_version_or_greater(minimum_major: int, minimum_minor: int) -> bool:

system_major, system_minor = mac_ver()[0].split(".")[:2]

system_major = int(system_major)
system_minor = int(system_minor)
system_major_int = int(system_major)
system_minor_int = int(system_minor)

return system_major > minimum_major or (system_major == minimum_major and system_minor >= minimum_minor)
return system_major_int > minimum_major or (system_major_int == minimum_major and system_minor_int >= minimum_minor)
Empty file.
9 changes: 7 additions & 2 deletions find_system_fonts_filename/unix/unix_fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from shutil import copyfile
from ctypes import byref, c_char_p
from typing import Set
from ..exceptions import FindSystemFontsFilenameException, OSNotSupported
from ..exceptions import FindSystemFontsFilenameException, OSNotSupported, SystemApiError
from ..system_fonts import SystemFonts

__all__ = ["UnixFonts"]
Expand All @@ -16,6 +16,7 @@ class UnixFonts(SystemFonts):
FC_FONT_FORMAT.FT_FONT_FORMAT_CFF,
]

@staticmethod
def get_system_fonts_filename() -> Set[str]:
"""
Inspired by: https://stackoverflow.com/questions/10542832/how-to-use-fontconfig-to-get-font-list-c-c/14634033#14634033
Expand Down Expand Up @@ -43,6 +44,8 @@ def get_system_fonts_filename() -> Set[str]:
font_format = FC_FONT_FORMAT(font_format_ptr.value)

if font_format in UnixFonts.VALID_FONT_FORMATS:
if file_path_ptr.value is None:
raise SystemApiError("An unexpected error has occurred while getting the font filename.")
# Decode with utf-8 since FcChar8
fonts_filename.add(file_path_ptr.value.decode())

Expand All @@ -54,7 +57,8 @@ def get_system_fonts_filename() -> Set[str]:
return fonts_filename


def install_font(font_filename: Path, windows_flags: bool) -> None:
@staticmethod
def install_font(font_filename: Path, add_font_to_registry: bool = False) -> None:
font_config = FontConfig()
version = font_config.FcGetVersion()

Expand All @@ -81,6 +85,7 @@ def install_font(font_filename: Path, windows_flags: bool) -> None:
font_config.FcConfigDestroy(config)


@staticmethod
def uninstall_font(font_filename: Path, windows_flags: bool) -> None:
font_config = FontConfig()
version = font_config.FcGetVersion()
Expand Down
65 changes: 34 additions & 31 deletions find_system_fonts_filename/windows/windows_fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ def get_filepath_from_IDWriteFontFace(font_face) -> Set[str]:
for font_file in font_files:
font_file_reference_key = wintypes.LPCVOID()
font_file_reference_key_size = wintypes.UINT()
font_file.GetReferenceKey(byref(font_file_reference_key), byref(font_file_reference_key_size))
font_file.GetReferenceKey(byref(font_file_reference_key), byref(font_file_reference_key_size)) # type: ignore

loader = POINTER(IDWriteFontFileLoader)()
font_file.GetLoader(byref(loader))
font_file.GetLoader(byref(loader)) # type: ignore

local_loader = loader.QueryInterface(IDWriteLocalFontFileLoader)
local_loader = loader.QueryInterface(IDWriteLocalFontFileLoader) # type: ignore

is_supported_font_type = wintypes.BOOLEAN()
font_file_type = wintypes.UINT()
font_face_type = wintypes.UINT()
number_of_faces = wintypes.UINT()
font_file.Analyze(byref(is_supported_font_type), byref(font_file_type), byref(font_face_type), byref(number_of_faces))
font_file.Analyze(byref(is_supported_font_type), byref(font_file_type), byref(font_face_type), byref(number_of_faces)) # type: ignore

if DWRITE_FONT_FILE_TYPE(font_file_type.value) not in WindowsFonts.VALID_FONT_FORMATS:
continue
Expand All @@ -72,12 +72,12 @@ def get_filepath_from_IDWriteFontFace(font_face) -> Set[str]:
return fonts_filename


def enum_fonts_2(logfont: POINTER(ENUMLOGFONTEXW), text_metric: POINTER(TEXTMETRICW), font_type: wintypes.DWORD, lparam: wintypes.LPARAM):
enum_data: EnumData = py_object.from_address(lparam).value
def enum_fonts_2(logfont: POINTER(ENUMLOGFONTEXW), text_metric: POINTER(TEXTMETRICW), font_type: wintypes.DWORD, lparam: wintypes.LPARAM) -> wintypes.INT:
enum_data: EnumData = py_object.from_address(lparam).value # type: ignore

# It seems that font_type can be 0. In those case, the font format is .fon
# We also discard RASTER_FONTTYPE which are bitmap font
if not (font_type & enum_data.gdi.RASTER_FONTTYPE) and font_type:
if not (font_type.value & enum_data.gdi.RASTER_FONTTYPE) and font_type.value:
# Replace the lfFaceName with the elfFullName.
# See why here: https://github.com/libass/libass/issues/744
lfFaceName = create_unicode_buffer(enum_data.gdi.LF_FACESIZE)
Expand All @@ -93,14 +93,14 @@ def enum_fonts_2(logfont: POINTER(ENUMLOGFONTEXW), text_metric: POINTER(TEXTMETR

enum_data.gdi.DeleteObject(hfont)

return True
return wintypes.INT(1)


def enum_fonts_1(logfont: POINTER(ENUMLOGFONTEXW), text_metric: POINTER(TEXTMETRICW), font_type: wintypes.DWORD, lparam: wintypes.LPARAM):
enum_data: EnumData = py_object.from_address(lparam).value
def enum_fonts_1(logfont: POINTER(ENUMLOGFONTEXW), text_metric: POINTER(TEXTMETRICW), font_type: wintypes.DWORD, lparam: wintypes.LPARAM) -> wintypes.INT:
enum_data: EnumData = py_object.from_address(lparam).value # type: ignore
enum_data.gdi.EnumFontFamiliesW(enum_data.dc, logfont.contents.elfLogFont.lfFaceName, enum_data.gdi.ENUMFONTFAMEXPROC(enum_fonts_2), lparam)

return True
return wintypes.INT(1)


class EnumData:
Expand All @@ -120,6 +120,7 @@ class WindowsFonts(SystemFonts):
DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_TRUETYPE_COLLECTION,
]

@staticmethod
def get_system_fonts_filename() -> Set[str]:
windows_version = getwindowsversion()

Expand All @@ -129,13 +130,13 @@ def get_system_fonts_filename() -> Set[str]:
dwrite = DWrite()
gdi = GDI32()
msvcrt = MSVCRT()
fonts_filename = set()
fonts_filename: Set[str] = set()

dwrite_factory = POINTER(IDWriteFactory)()
dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, byref(dwrite_factory._iid_), byref(dwrite_factory))
dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, byref(dwrite_factory._iid_), byref(dwrite_factory)) # type: ignore[attr-defined]

gdi_interop = POINTER(IDWriteGdiInterop)()
dwrite_factory.GetGdiInterop(byref(gdi_interop))
dwrite_factory.GetGdiInterop(byref(gdi_interop)) # type: ignore

dc = gdi.CreateCompatibleDC(None)

Expand All @@ -162,31 +163,31 @@ def get_registry_font_name(font_filename: Path) -> str:
dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, IDWriteFactory._iid_, byref(dwrite_factory))

font_file = POINTER(IDWriteFontFile)()
dwrite_factory.CreateFontFileReference(font_filename_buffer, None, byref(font_file))
dwrite_factory.CreateFontFileReference(font_filename_buffer, None, byref(font_file)) # type: ignore

is_supported_font_type = wintypes.BOOLEAN()
font_file_type = wintypes.UINT()
font_face_type = wintypes.UINT()
number_of_faces = wintypes.UINT()
font_file.Analyze(byref(is_supported_font_type), byref(font_file_type), byref(font_face_type), byref(number_of_faces))
font_file.Analyze(byref(is_supported_font_type), byref(font_file_type), byref(font_face_type), byref(number_of_faces)) # type: ignore

if not is_supported_font_type:
raise NotSupportedFontFile(f"The font file \"{font_filename}\" isn't supported on Windows.")

font_collection_loader = CustomFontCollectionLoader([font_filename])
dwrite_factory.RegisterFontCollectionLoader(font_collection_loader)
dwrite_factory.RegisterFontCollectionLoader(font_collection_loader) # type: ignore

custom_collection = POINTER(IDWriteFontCollection)()
font_loader_key = create_unicode_buffer("find_system_fonts_filename_collection_loader")
dwrite_factory.CreateCustomFontCollection(font_collection_loader,
dwrite_factory.CreateCustomFontCollection(font_collection_loader, # type: ignore
cast(font_loader_key, wintypes.LPVOID),
sizeof(font_loader_key),
byref(custom_collection))

full_names: List[str] = []
for i in range(number_of_faces.value):
font_face = POINTER(IDWriteFontFace)()
dwrite_factory.CreateFontFace(font_face_type.value, 1, byref(font_file), i, DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE, byref(font_face))
dwrite_factory.CreateFontFace(font_face_type.value, 1, byref(font_file), i, DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE, byref(font_face)) # type: ignore

"""
Converting a IDWriteFontFace to a IDWriteFont isn't easy.
Expand All @@ -195,11 +196,11 @@ def get_registry_font_name(font_filename: Path) -> str:
the font may not be available. See issue #14
"""
font = POINTER(IDWriteFont)()
custom_collection.GetFontFromFontFace(font_face, byref(font))
custom_collection.GetFontFromFontFace(font_face, byref(font)) # type: ignore

full_name = POINTER(IDWriteLocalizedStrings)()
exists = wintypes.BOOL()
font.GetInformationalStrings(
font.GetInformationalStrings( # type: ignore
DWRITE_INFORMATIONAL_STRING_ID.DWRITE_INFORMATIONAL_STRING_FULL_NAME,
byref(full_name),
byref(exists)
Expand All @@ -214,29 +215,30 @@ def get_registry_font_name(font_filename: Path) -> str:

index = wintypes.UINT()
exists = wintypes.BOOL()
full_name.FindLocaleName(locale_name, byref(index), byref(exists))
full_name.FindLocaleName(locale_name, byref(index), byref(exists)) # type: ignore

if not exists.value:
full_name.FindLocaleName("en-us", byref(index), byref(exists))
full_name.FindLocaleName("en-us", byref(index), byref(exists)) # type: ignore

if not exists.value:
index = 0
index = wintypes.UINT(0)

length = wintypes.UINT()
full_name.GetStringLength(index, byref(length))
full_name.GetStringLength(index, byref(length)) # type: ignore

family_names_buffer = create_unicode_buffer(length.value + 1)
full_name.GetString(index, family_names_buffer, len(family_names_buffer))
full_name.GetString(index, family_names_buffer, len(family_names_buffer)) # type: ignore

full_names.append(family_names_buffer.value)

registry_font_name = " & ".join(full_names) + " (FindSystemFontsFilename)"
dwrite_factory.UnregisterFontCollectionLoader(font_collection_loader)
dwrite_factory.UnregisterFontCollectionLoader(font_collection_loader) # type: ignore

return registry_font_name


def install_font(font_filename: Path, add_font_to_registry: bool) -> None:
@staticmethod
def install_font(font_filename: Path, add_font_to_registry: bool = False) -> None:
windows_version = getwindowsversion()

if not WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
Expand Down Expand Up @@ -264,7 +266,8 @@ def install_font(font_filename: Path, add_font_to_registry: bool) -> None:
user32.SendNotifyMessageW(user32.HWND_BROADCAST, user32.WM_FONTCHANGE, 0, 0)


def uninstall_font(font_filename: Path, added_font_to_registry: bool) -> None:
@staticmethod
def uninstall_font(font_filename: Path, remove_font_in_registry: bool) -> None:
windows_version = getwindowsversion()

if not WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
Expand All @@ -277,7 +280,7 @@ def uninstall_font(font_filename: Path, added_font_to_registry: bool) -> None:
gdi = GDI32()
user32 = User32()

if added_font_to_registry and is_build_17083_or_greater:
if remove_font_in_registry and is_build_17083_or_greater:
advapi32 = Advapi32()
hkey = wintypes.HKEY()
registry_font_name = WindowsFonts.get_registry_font_name(font_filename)
Expand Down Expand Up @@ -308,7 +311,7 @@ def __init__(self, dwrite_factory: POINTER(IDWriteFactory), font_files_path: Lis
self.dwrite_factory = dwrite_factory
self.font_files_path = font_files_path
self.current_index = -1
self.current_font_file = None
self.current_font_file: POINTER(IDWriteFontFile) = None

def IDWriteFontFileEnumerator_MoveNext(self, this, has_current_file: POINTER(wintypes.BOOL)) -> int:
self.current_index += 1
Expand Down
31 changes: 31 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ dynamic = ["version"]
Source = "https://github.com/moi15moi/FindSystemFontsFilename/"
Tracker = "https://github.com/moi15moi/FindSystemFontsFilename/issues/"

[project.optional-dependencies]
dev = [
"mypy>=1.0.0",
"pytest>=8.0.0",
]

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
Expand All @@ -42,3 +48,28 @@ version = { attr = "find_system_fonts_filename.__init__.__version__" }

[tool.setuptools.packages.find]
include = ["find_system_fonts_filename*"]

[tool.mypy]
exclude = "build"
files = ["."]
warn_unused_configs = true

[[tool.mypy.overrides]]
module = ["comtypes.*"]
follow_untyped_imports = true

[[tool.mypy.overrides]]
module = "find_system_fonts_filename.android.*"
platform = "android"

[[tool.mypy.overrides]]
module = "find_system_fonts_filename.mac.*"
platform = "darwin"

[[tool.mypy.overrides]]
module = "find_system_fonts_filename.unix.*"
platform = "linux"

[[tool.mypy.overrides]]
module = "find_system_fonts_filename.windows.*"
platform = "win32"