16 KiB
16 KiB
In [ ]:
import urllib.request
import urllib.parse
from pathlib import Path
from typing import Optional
from PIL import Image, ImageDraw, ImageFont
from IPython.display import display, Markdown
import yaml
repo_root = Path.cwd().resolve().parent
root_dir = repo_root / 'assets' / 'sd-fonts'
output_image_dir = repo_root / 'docs' / 'images' / 'sd-font-previews'
output_image_dir.mkdir(parents=True, exist_ok=True)
output_markdown_path = repo_root / 'docs' / 'sd-fonts-preview.md'
manifest_path = repo_root / 'lib' / 'EpdFont' / 'scripts' / 'sd-fonts.yaml'
cache_dir = repo_root / 'scripts' / 'font-cache'
cache_dir.mkdir(parents=True, exist_ok=True)
with manifest_path.open('r', encoding='utf-8') as manifest_file:
manifest = yaml.safe_load(manifest_file)
family_map = {family['name']: family for family in manifest['families']}
print(f'Repo root: {repo_root}')
print(f'Root font directory: {root_dir.resolve()}')
print(f'Preview image output directory: {output_image_dir.resolve()}')
print(f'Markdown output path: {output_markdown_path.resolve()}')[31m---------------------------------------------------------------------------[39m [31mNameError[39m Traceback (most recent call last) [36mCell[39m[36m [39m[32mIn[9][39m[32m, line 15[39m [32m 13[39m output_markdown_path = repo_root / [33m'[39m[33mdocs[39m[33m'[39m / [33m'[39m[33msd-fonts-preview.md[39m[33m'[39m [32m 14[39m manifest_path = repo_root / [33m'[39m[33mlib[39m[33m'[39m / [33m'[39m[33mEpdFont[39m[33m'[39m / [33m'[39m[33mscripts[39m[33m'[39m / [33m'[39m[33msd-fonts.yaml[39m[33m'[39m [32m---> [39m[32m15[39m cache_dir = Path([34;43m__file__[39;49m).resolve().parent / [33m'[39m[33mfont-cache[39m[33m'[39m [32m 16[39m cache_dir.mkdir(parents=[38;5;28;01mTrue[39;00m, exist_ok=[38;5;28;01mTrue[39;00m) [32m 18[39m [38;5;28;01mwith[39;00m manifest_path.open([33m'[39m[33mr[39m[33m'[39m, encoding=[33m'[39m[33mutf-8[39m[33m'[39m) [38;5;28;01mas[39;00m manifest_file: [31mNameError[39m: name '__file__' is not defined
In [8]:
# Discover available font families
font_families = [p.name for p in sorted(root_dir.iterdir()) if p.is_dir()]
print(f'Found {len(font_families)} font families:')
for family in font_families:
print(f' - {family}')
def download_font(url: str, cache_dir: Path) -> Path:
parsed = urllib.parse.urlparse(url)
filename = Path(parsed.path).name
local_path = cache_dir / filename
if not local_path.exists():
print(f'Downloading {url}...')
urllib.request.urlretrieve(url, local_path)
return local_path
def resolve_instanced_font_path(family_name: str, style_name: str) -> Optional[Path]:
instanced_dir = repo_root / 'lib' / 'EpdFont' / 'scripts' / 'instanced_fonts' / family_name
if not instanced_dir.exists():
return None
matches = sorted(instanced_dir.glob(f'{style_name}*.ttf'))
return matches[0] if matches else None
def resolve_font_path(family_name: str) -> Optional[Path]:
family = family_map.get(family_name)
if family is None:
return None
styles = family.get('styles', {})
style = styles.get('regular') or next(iter(styles.values()), None)
if style is None:
return None
if 'path' in style:
local_path = repo_root / 'lib' / 'EpdFont' / style['path']
if local_path.exists():
return local_path
instanced = resolve_instanced_font_path(family_name, 'regular')
if instanced is not None:
return instanced
if 'url' in style:
return download_font(style['url'], cache_dir)
return NoneFound 23 font families: - Alegreya - Arimo - AtkinsonHyperlegibleNext - Bitter - Gelasio - GentiumBookPlus - IBMPlexMono - IBMPlexSans - IBMPlexSerif - Inter - LexicaUltralegible - Literata - Lora - Merriweather - NotoSansExtended - NotoSerifExtended - OpenDyslexic - Readerly - SourceCodePro - SourceSans3 - SourceSerif4 - Spectral - Vollkorn
[31m---------------------------------------------------------------------------[39m [31mNameError[39m Traceback (most recent call last) [36mCell[39m[36m [39m[32mIn[8][39m[32m, line 18[39m [32m 14[39m urllib.request.urlretrieve(url, local_path) [32m 15[39m [38;5;28;01mreturn[39;00m local_path [32m---> [39m[32m18[39m [38;5;28;01mdef[39;00m[38;5;250m [39m[34mresolve_instanced_font_path[39m(family_name: [38;5;28mstr[39m, style_name: [38;5;28mstr[39m) -> [43mOptional[49m[Path]: [32m 19[39m instanced_dir = repo_root / [33m'[39m[33mlib[39m[33m'[39m / [33m'[39m[33mEpdFont[39m[33m'[39m / [33m'[39m[33mscripts[39m[33m'[39m / [33m'[39m[33minstanced_fonts[39m[33m'[39m / family_name [32m 20[39m [38;5;28;01mif[39;00m [38;5;129;01mnot[39;00m instanced_dir.exists(): [31mNameError[39m: name 'Optional' is not defined
In [4]:
sample_text = 'Everyone has the right to freedom of thought...'
preview_texts = {family: f'{family} - {sample_text}' for family in font_families}
for family, text in preview_texts.items():
print(f'{family}: {text}')Alegreya: Alegreya - Everyone has the right to freedom of thought... Arimo: Arimo - Everyone has the right to freedom of thought... AtkinsonHyperlegibleNext: AtkinsonHyperlegibleNext - Everyone has the right to freedom of thought... Bitter: Bitter - Everyone has the right to freedom of thought... Gelasio: Gelasio - Everyone has the right to freedom of thought... GentiumBookPlus: GentiumBookPlus - Everyone has the right to freedom of thought... IBMPlexMono: IBMPlexMono - Everyone has the right to freedom of thought... IBMPlexSans: IBMPlexSans - Everyone has the right to freedom of thought... IBMPlexSerif: IBMPlexSerif - Everyone has the right to freedom of thought... Inter: Inter - Everyone has the right to freedom of thought... LexicaUltralegible: LexicaUltralegible - Everyone has the right to freedom of thought... Literata: Literata - Everyone has the right to freedom of thought... Lora: Lora - Everyone has the right to freedom of thought... Merriweather: Merriweather - Everyone has the right to freedom of thought... NotoSansExtended: NotoSansExtended - Everyone has the right to freedom of thought... NotoSerifExtended: NotoSerifExtended - Everyone has the right to freedom of thought... OpenDyslexic: OpenDyslexic - Everyone has the right to freedom of thought... Readerly: Readerly - Everyone has the right to freedom of thought... SourceCodePro: SourceCodePro - Everyone has the right to freedom of thought... SourceSans3: SourceSans3 - Everyone has the right to freedom of thought... SourceSerif4: SourceSerif4 - Everyone has the right to freedom of thought... Spectral: Spectral - Everyone has the right to freedom of thought... Vollkorn: Vollkorn - Everyone has the right to freedom of thought...
In [6]:
# Render and save preview images
font_size = 28
image_width = 1080
image_height = 220
for family, text in preview_texts.items():
font_path = resolve_font_path(family)
try:
if font_path is None:
raise FileNotFoundError('No source font path available')
font = ImageFont.truetype(str(font_path), font_size)
except Exception as exc:
print(f'Warning: could not load {family} from {font_path}: {exc}')
font = ImageFont.load_default()
image = Image.new('RGB', (image_width, image_height), 'white')
draw = ImageDraw.Draw(image)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = 20
y = (image_height - text_height) // 2
draw.text((x, y), text, fill='black', font=font)
output_path = output_image_dir / f'{family}.png'
image.save(output_path)
print(f'Saved {output_path}')
print('All preview images generated.')Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Alegreya.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Arimo.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\AtkinsonHyperlegibleNext.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Bitter.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Gelasio.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\GentiumBookPlus.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\IBMPlexMono.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\IBMPlexSans.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\IBMPlexSerif.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Inter.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\LexicaUltralegible.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Literata.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Lora.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Merriweather.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\NotoSansExtended.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\NotoSerifExtended.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\OpenDyslexic.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Readerly.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\SourceCodePro.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\SourceSans3.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\SourceSerif4.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Spectral.png Saved C:\_development\crosspoint-reader\docs\images\sd-font-previews\Vollkorn.png All preview images generated.
In [7]:
# Generate markdown listing with embedded preview images
lines = [
'# SD Font Preview',
'',
'This page lists the available SD font families under `assets/sd-fonts` and embeds rendered preview images for each family.',
'',
'| Font Family | Preview |',
'|---|---|',
]
for family in font_families:
image_rel_path = f'images/sd-font-previews/{family}.png'
lines.append(f'| {family} |  |')
output_markdown_path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
print(f'Generated markdown page at {output_markdown_path}')
# Display a preview of the generated markdown in the notebook
display(Markdown('\n'.join(lines[:8]) + '\n...'))Generated markdown page at C:\_development\crosspoint-reader\docs\sd-fonts-preview.md
<IPython.core.display.Markdown object>