{ "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": [ "" ] }, "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 }