348 lines
16 KiB
Plaintext
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 |  |\n",
|
|
"| Arimo |  |\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} |  |')\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
|
|
}
|