diff --git a/test/fixtures/page_generation/page.py b/test/fixtures/page_generation/page.py index 4c5deae..142bc2d 100644 --- a/test/fixtures/page_generation/page.py +++ b/test/fixtures/page_generation/page.py @@ -1,13 +1,16 @@ import io import itertools +import os import random import string import textwrap from functools import lru_cache +from pathlib import Path from typing import Tuple, Union, Iterable, List import albumentations as A import cv2 as cv +import loguru import numpy as np import pandas as pd import pytest @@ -92,6 +95,9 @@ from funcy import ( rcompose, lsplit, lfilter, + lzip, + keep, + lkeep, ) from cv_analysis.locations import TEST_PAGE_TEXTURES_DIR @@ -178,8 +184,8 @@ def dpi(request): @pytest.fixture( params=[ # "brown", - "sepia", - # "gray", + # "sepia", + "gray", # "white", # "light_red", # "light_blue", @@ -202,8 +208,8 @@ def texture_name(request): @pytest.fixture( params=[ - # 30, - 70, + 30, + # 70, # 150, ] ) @@ -362,7 +368,7 @@ class ContentRectangle(Rectangle): class ContentGenerator: def __init__(self): - self.constrain_layouts = False + self.constrain_layouts = True def __call__(self, boxes: List[Rectangle]) -> Image: random.shuffle(boxes) @@ -375,7 +381,7 @@ class ContentGenerator: text_boxes = merge_related_rectangles(text_boxes) text_boxes = lmap(generate_random_text_block, text_boxes) - plots = lmap(generate_random_plot, every_nth(2, char_boxes)) + plots = lmap(generate_random_table, every_nth(2, char_boxes)) tables = lmap(generate_random_table, every_nth(2, char_boxes[1:])) boxes = text_boxes + plots + tables @@ -479,6 +485,74 @@ def dump_plt_to_image(rectangle): return image +class RandomFontPicker: + def __init__(self, font_dir=None): + self.fonts = get_fonts(font_dir) + self.fonts_lower = [font.lower() for font in self.fonts] + + self.test_image = Image.new("RGB", (200, 200), (255, 255, 255)) + self.draw = ImageDraw.Draw(self.test_image) + + def pick_random_font_available_on_system(self, includes=None, excludes=None) -> ImageFont: # FIXME: Slow! + + self.shuffle_fonts() + + includes_pattern = (lambda f: includes.lower() in f) if includes else (lambda f: True) + excludes_pattern = (lambda f: excludes.lower() not in f) if excludes else (lambda f: True) + + 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) + loguru.logger.debug(f"Using font: {font}") + return font + + def shuffle_fonts(self): + l = lzip(self.fonts, self.fonts_lower) + random.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): + loguru.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(font_dir=None): + return RandomFontPicker(font_dir=font_dir) + + +def pick_random_mono_space_font_available_on_system(): + font_picker = get_font_picker() + return font_picker.pick_random_mono_space_font_available_on_system() + + +def pick_random_font_available_on_system(): + font_picker = get_font_picker() + return font_picker.pick_random_font_available_on_system(includes="mono") + + class RandomPlot(RandomContentRectangle): def __init__(self, x1, y1, x2, y2, seed=None): super().__init__(x1, y1, x2, y2, seed=seed) @@ -600,7 +674,7 @@ def write_lines_to_image(lines: List[str], rectangle: Rectangle, font=None) -> I def write_line(line, line_number): draw.text((0, line_number * text_size), line, font=font, fill=(0, 0, 0, 200)) - font = font or ImageFont.load_default() + font = font or pick_random_mono_space_font_available_on_system() image = Image.new("RGBA", (rectangle.width, rectangle.height), (0, 255, 255, 0)) draw = ImageDraw.Draw(image) @@ -616,7 +690,7 @@ class RandomTextBlock(ContentRectangle): def __init__(self, x1, y1, x2, y2): super().__init__(x1, y1, x2, y2) self.blank_line_percentage = random.uniform(0, 0.5) - self.font = ImageFont.load_default() + self.font = pick_random_font_available_on_system() def __call__(self, *args, **kwargs): pass @@ -688,7 +762,7 @@ class PagePartitioner: self.top_margin_percentage = 0.1 self.bottom_margin_percentage = 0.1 - self.margin_percentage = 0.005 + self.margin_percentage = 0.007 self.max_depth = 3 self.initial_recursion_probability = 1 self.recursion_probability_decay = 0.1