Files
Crosspoint/scripts/sd_font_preview_generation_new.ipynb
T
2026-05-04 15:00:44 +02:00

348 lines
16 KiB
Plaintext

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