#!/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, )