Files
scripts/genicon.py
T
2026-08-10 15:37:40 +02:00

180 lines
3.7 KiB
Python

#!/usr/bin/env python3
## requires poppler and pillow: pip3 install pillow
## run as
## - python genicon.py '$\lambda$'
## will produce favicon.png and favicon.ico
import argparse
import subprocess
import tempfile
from pathlib import Path
from PIL import Image
LATEX_TEMPLATE = r"""
\documentclass[border=2pt]{standalone}
\usepackage{amsmath}
\usepackage{amssymb}
\begin{document}
%s
\end{document}
"""
def run(command, cwd=None):
subprocess.run(
command,
cwd=cwd,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
def render_favicon(
latex=r"$\mathcal{F}$",
png_path="favicon.png",
ico_path="favicon.ico",
png_size=128,
padding=12,
):
png_path = Path(png_path)
ico_path = Path(ico_path)
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
tex_file = tmp / "favicon.tex"
tex_file.write_text(LATEX_TEMPLATE % latex, encoding="utf-8")
# 1. LaTeX -> tightly cropped PDF via standalone
run(
[
"pdflatex",
"-interaction=nonstopmode",
"-halt-on-error",
"favicon.tex",
],
cwd=tmp,
)
pdf_file = tmp / "favicon.pdf"
# 2. PDF -> high-resolution PNG
raw_png_prefix = tmp / "render"
run(
[
"pdftocairo",
"-png",
"-singlefile",
"-r",
"600",
str(pdf_file),
str(raw_png_prefix),
]
)
raw_png = tmp / "render.png"
# 3. Composite onto a pure white background
image = Image.open(raw_png).convert("RGBA")
white = Image.new("RGBA", image.size, (255, 255, 255, 255))
white.alpha_composite(image)
image = white.convert("RGB")
# 4. Scale while preserving aspect ratio
inner_size = png_size - 2 * padding
image.thumbnail(
(inner_size, inner_size),
Image.Resampling.LANCZOS,
)
canvas = Image.new(
"RGB",
(png_size, png_size),
"white",
)
x = (png_size - image.width) // 2
y = (png_size - image.height) // 2
canvas.paste(image, (x, y))
# 5. Save PNG
canvas.save(
png_path,
"PNG",
optimize=True,
)
# 6. Save ICO containing common favicon resolutions
canvas.save(
ico_path,
format="ICO",
sizes=[
(16, 16),
(32, 32),
(48, 48),
(64, 64),
(128, 128),
],
)
print(f"Created {png_path}")
print(f"Created {ico_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Render LaTeX into favicon PNG/ICO files."
)
parser.add_argument(
"latex",
nargs="?",
default=r"$\mathcal{F}$",
help=r'LaTeX to render, e.g. "$\mathcal{F}$"',
)
parser.add_argument(
"--png",
default="favicon.png",
help="Output PNG filename",
)
parser.add_argument(
"--ico",
default="favicon.ico",
help="Output ICO filename",
)
parser.add_argument(
"--size",
type=int,
default=128,
help="PNG canvas size in pixels",
)
parser.add_argument(
"--padding",
type=int,
default=12,
help="Padding around the rendered symbol",
)
args = parser.parse_args()
render_favicon(
latex=args.latex,
png_path=args.png,
ico_path=args.ico,
png_size=args.size,
padding=args.padding,
)