Skip to content

Commit

Permalink
Add android provider (#3)
Browse files Browse the repository at this point in the history
* Add an android font filename provider
* Bump version to 0.1.0
  • Loading branch information
moi15moi authored Jun 9, 2023
1 parent 32d29b5 commit 047d7dc
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 10 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ It uses some APIs to find the font filename:
- Windows: [DirectWrite API](https://learn.microsoft.com/en-us/windows/win32/directwrite/direct-write-portal)
- macOS: [Core Text API](https://developer.apple.com/documentation/coretext)
- Linux: [Fontconfig API](https://www.freedesktop.org/wiki/Software/fontconfig/)
- Android: [NDK Font API](https://developer.android.com/ndk/reference/group/font)

## Installation
```
Expand All @@ -13,15 +14,17 @@ pip install FindSystemFontsFilename

## How to use it
```python
from find_system_fonts_filename import get_system_fonts_filename, FontConfigNotFound, OSNotSupported
from find_system_fonts_filename import AndroidLibraryNotFound, get_system_fonts_filename, FontConfigNotFound, OSNotSupported

try:
fonts_filename = get_system_fonts_filename()
except (FontConfigNotFound, OSNotSupported):
except (AndroidLibraryNotFound, FontConfigNotFound, OSNotSupported):
# Deal with the exception
# OSNotSupported can only happen in Windows and macOS
# OSNotSupported can only happen in Windows, macOS and Android
# - Windows Vista SP2 and more are supported
# - macOS 10.6 and more are supported
# - Android SDK/API 29 and more are supported
# FontConfigNotFound can only happen on Linux when Fontconfig could't be found.
# AndroidLibraryNotFound can only happen on Android when the android library could't be found.
pass
```
4 changes: 2 additions & 2 deletions find_system_fonts_filename/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .fonts_filename import get_system_fonts_filename
from .exceptions import FontConfigNotFound, OSNotSupported
from .exceptions import AndroidLibraryNotFound, FontConfigNotFound, OSNotSupported

__version__ = "0.0.4"
__version__ = "0.1.0"
93 changes: 93 additions & 0 deletions find_system_fonts_filename/android_fonts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from contextlib import contextmanager
from ctypes import c_char_p, c_void_p, cdll, util
from os import close, devnull, dup, dup2, O_WRONLY, open
from sys import stderr, stdout
from typing import Set
from .exceptions import AndroidLibraryNotFound, OSNotSupported
from .system_fonts import SystemFonts


class AndroidFonts(SystemFonts):
_android = None

def get_system_fonts_filename() -> Set[str]:
if AndroidFonts._android is None:
AndroidFonts._load_android_library()

fonts_filename = set()

# Redirect the stderr_and_stdout to null since we don't care about android logs
# There is __android_log_set_minimum_priority to set the log level, but even if I set it to 8 (which correspond to ANDROID_LOG_SILENT),
# it was still logging: https://developer.android.com/ndk/reference/group/logging#__android_log_set_minimum_priority
with AndroidFonts._silence_stderr_and_stdout():
font_iterator = AndroidFonts._android.ASystemFontIterator_open()

while True:
font = AndroidFonts._android.ASystemFontIterator_next(font_iterator)

if font is None:
break

font_filename = AndroidFonts._android.AFont_getFontFilePath(font).decode("utf-8")
fonts_filename.add(font_filename)

AndroidFonts._android.AFont_close(font)
AndroidFonts._android.ASystemFontIterator_close(font_iterator)

return fonts_filename

@staticmethod
def _load_android_library():
android_library_name = util.find_library("android")

if android_library_name is None:
raise AndroidLibraryNotFound("You need to have the libandroid library. It is only available since the SDK/API level 29.")

AndroidFonts._android = cdll.LoadLibrary(android_library_name)

# The android device need to be at least on the level 29.
# The function android_get_device_api_level is only available since the level 29, so we can't use it. See: https://developer.android.com/ndk/reference/group/apilevels#android_get_device_api_level
# So, we try and see if the function are available
try:
# https://developer.android.com/ndk/reference/group/font#asystemfontiterator_open
AndroidFonts._android.ASystemFontIterator_open.restype = c_void_p
AndroidFonts._android.ASystemFontIterator_open.argtypes = []
except AttributeError:
raise OSNotSupported("FindSystemFontsFilename only works on Android API level 29 or more.")

# https://developer.android.com/ndk/reference/group/font#asystemfontiterator_next
AndroidFonts._android.ASystemFontIterator_next.restype = c_void_p
AndroidFonts._android.ASystemFontIterator_next.argtypes = [c_void_p]

# https://developer.android.com/ndk/reference/group/font#asystemfontiterator_close
AndroidFonts._android.ASystemFontIterator_close.restype = None
AndroidFonts._android.ASystemFontIterator_close.argtypes = [c_void_p]

# https://developer.android.com/ndk/reference/group/font#afont_getfontfilepath
AndroidFonts._android.AFont_getFontFilePath.restype = c_char_p
AndroidFonts._android.AFont_getFontFilePath.argtypes = [c_void_p]

# https://developer.android.com/ndk/reference/group/font#afont_close
AndroidFonts._android.AFont_close.restype = None
AndroidFonts._android.AFont_close.argtypes = [c_void_p]

@contextmanager
def _silence_stderr_and_stdout():
# From: https://stackoverflow.com/a/75037627/15835974
stderr_fd = stderr.fileno()
orig_stderr_fd = dup(stderr_fd)

stdout_fd = stdout.fileno()
orig_stdout_fd = dup(stdout_fd)

null_fd = open(devnull, O_WRONLY)
dup2(null_fd, stderr_fd)
dup2(null_fd, stdout_fd)
try:
yield
finally:
dup2(orig_stderr_fd, stderr_fd)
dup2(orig_stdout_fd, stdout_fd)
close(orig_stderr_fd)
close(orig_stdout_fd)
close(null_fd)
9 changes: 7 additions & 2 deletions find_system_fonts_filename/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ class OSNotSupported(Exception):


class FontConfigNotFound(Exception):
"Raised when a Fontconfig API haven't been found"
pass
"Raised when the Fontconfig API haven't been found"
pass


class AndroidLibraryNotFound(Exception):
"Raised when the android library haven't been found"
pass
13 changes: 10 additions & 3 deletions find_system_fonts_filename/fonts_filename.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
from platform import system
from os import environ
from typing import Set
from .exceptions import OSNotSupported


def get_system_fonts_filename():
def get_system_fonts_filename() -> Set[str]:
system_name = system()

if system_name == "Windows":
from .windows_fonts import WindowsFonts
return WindowsFonts.get_system_fonts_filename()

elif system_name == "Linux":
from .linux_fonts import LinuxFonts
return LinuxFonts.get_system_fonts_filename()
# ANDROID_ROOT or ANDROID_BOOTLOGO - https://stackoverflow.com/a/66174754/15835974
if any(t in environ.values() for t in ("ANDROID_ROOT", "ANDROID_BOOTLOGO")):
from .android_fonts import AndroidFonts
return AndroidFonts.get_system_fonts_filename()
else:
from .linux_fonts import LinuxFonts
return LinuxFonts.get_system_fonts_filename()

elif system_name == "Darwin":
from .mac_fonts import MacFonts
Expand Down

0 comments on commit 047d7dc

Please sign in to comment.