Matthias Bisping 1de938f2fa Refactoring: Move
Move font related functions into font module
2023-02-01 17:07:34 +01:00

107 lines
3.7 KiB
Python

import itertools
from functools import lru_cache
from pathlib import Path
from typing import List
from PIL import Image, ImageDraw, ImageFont
from funcy import lmap, complement, keep, first, lzip, omit, project
from loguru import logger
from synthesis.random import rnd
class RandomFontPicker:
def __init__(self, font_dir=None, return_default_font=False):
fonts = get_fonts(font_dir)
fonts_lower = [font.lower() for font in fonts]
domestic_fonts_mask = lmap(complement(self.looks_foreign), fonts_lower)
self.fonts = list(itertools.compress(fonts, domestic_fonts_mask))
self.fonts_lower = list(itertools.compress(fonts_lower, domestic_fonts_mask))
self.test_image = Image.new("RGB", (200, 200), (255, 255, 255))
self.draw = ImageDraw.Draw(self.test_image)
self.return_default_font = return_default_font
def looks_foreign(self, font):
# This filters out foreign fonts (e.g. 'Noto Serif Malayalam')
return len(font.split("-")[0]) > 10
def pick_random_font_available_on_system(self, includes=None, excludes=None) -> ImageFont: # FIXME: Slow!
if self.return_default_font:
return ImageFont.load_default()
includes = [i.lower() for i in includes] if includes else []
excludes = [i.lower() for i in excludes] if excludes else []
logger.debug(f"Picking font by includes={includes} and excludes={excludes}.")
def includes_pattern(font):
return not includes or any(include in font for include in includes)
def excludes_pattern(font):
return not excludes or not any(exclude in font for exclude in excludes)
self.shuffle_fonts()
mask = lmap(lambda f: includes_pattern(f) and excludes_pattern(f), self.fonts_lower)
fonts = itertools.compress(self.fonts, mask)
fonts = keep(map(self.load_font, fonts))
# fonts = filter(self.font_is_renderable, fonts) # FIXME: this does not work
font = first(fonts)
logger.info(f"Using font: {font.getname()}")
return font
def shuffle_fonts(self):
l = lzip(self.fonts, self.fonts_lower)
rnd.shuffle(l)
self.fonts, self.fonts_lower = lzip(*l)
def pick_random_mono_space_font_available_on_system(self) -> ImageFont:
return self.pick_random_font_available_on_system(includes=["mono"], excludes=["oblique"])
@lru_cache(maxsize=None)
def load_font(self, font: str):
logger.trace(f"Loading font: {font}")
try:
return ImageFont.truetype(font, size=11)
except OSError:
return None
@lru_cache(maxsize=None)
def font_is_renderable(self, font):
text_size = self.draw.textsize("Test String", font=font)
return text_size[0] > 0 and text_size[1]
def get_fonts(path: Path = None) -> List[str]:
path = path or Path("/usr/share/fonts")
fonts = list(path.rglob("*.ttf"))
fonts = [font.name for font in fonts]
return fonts
@lru_cache(maxsize=None)
def get_font_picker(**kwargs):
return RandomFontPicker(**kwargs, return_default_font=True)
@lru_cache(maxsize=None)
def pick_random_mono_space_font_available_on_system(**kwargs):
font_picker = get_font_picker(**omit(kwargs, ["includes", "excludes"]))
return font_picker.pick_random_mono_space_font_available_on_system()
@lru_cache(maxsize=None)
def pick_random_font_available_on_system(**kwargs):
kwargs["excludes"] = (
*kwargs.get(
"excludes",
),
"Kinnari",
"KacstOne",
)
font_picker = get_font_picker(**omit(kwargs, ["includes", "excludes"]))
return font_picker.pick_random_font_available_on_system(**project(kwargs, ["includes", "excludes"]))