"""Render slide equations as sans-serif math PNGs, all at one uniform font size.

Typesetting: XeLaTeX + unicode-math.
  text/upright letters and digits : Carlito  (metric-compatible with Calibri,
                                              the slide typeface)
  italic variables                : Carlito Italic
  operators, integrals, radicals,
  large delimiters, symbols       : DejaVu Math TeX Gyre (a sans-serif
                                    OpenType math font)

Every equation is set at the same LaTeX size and rasterised at the same
resolution, so the on-slide font size is identical across the whole deck.
`eqs/manifest.json` records the natural width and height in inches at the
target point size; the deck builder places each image at that natural size
(shrinking only if it would not fit its allotted box).
"""
import json
import os
import re
import shutil
import subprocess

OUT = "/home/claude/eos_lecture/eqs"
TMP = "/tmp/eqbuild"
INK = "1C242B"

DPI = 600          # rasterisation resolution
BASE_PT = 10       # standalone class base size
TARGET_PT = 21     # on-slide math size
SCALE = TARGET_PT / BASE_PT

EQS = {
    # ---------------------------------------------------------------- Chapter 1
    "e_Z":        r"Z \equiv \frac{PV}{RT}",
    "e_lnphi":    r"\ln\varphi = \int_0^P (Z-1)\,\frac{\mathrm{d}P}{P}",
    "e_hdep":     r"H-H^{\mathrm{ig}} = -RT^{2}\int_0^P\left(\frac{\partial Z}{\partial T}\right)_{\!P}\frac{\mathrm{d}P}{P}",
    "e_vle":      r"\hat f_i^{\,V} = \hat f_i^{\,L} \quad\Longrightarrow\quad K_i \equiv \frac{y_i}{x_i} = \frac{\hat\varphi_i^{\,L}}{\hat\varphi_i^{\,V}}",
    # ---------------------------------------------------------------- Chapter 2
    "e_ideal":    r"PV = RT \qquad\Longleftrightarrow\qquad Z = 1",
    "e_kinetic":  r"P = \frac{1}{3}\,\frac{N}{V}\,m\langle v^{2}\rangle \quad\Longrightarrow\quad PV = Nk_{\mathrm{B}}T",
    # ---------------------------------------------------------------- Chapter 3
    "e_vdw":      r"P = \frac{RT}{V-b} \;-\; \frac{a}{V^{2}}",
    "e_bexcl":    r"b = \frac{2\pi}{3}N_{\mathrm{A}}\sigma^{3}",
    "e_critcond": r"\left(\frac{\partial P}{\partial V}\right)_{\!T_{\mathrm{c}}} = \left(\frac{\partial^{2} P}{\partial V^{2}}\right)_{\!T_{\mathrm{c}}} = 0",
    "e_vdwab":    r"a = \frac{27R^{2}T_{\mathrm{c}}^{2}}{64P_{\mathrm{c}}}, \qquad b = \frac{RT_{\mathrm{c}}}{8P_{\mathrm{c}}}, \qquad Z_{\mathrm{c}} = 3/8 = 0.375",
    "e_maxwell":  r"\int_{V^{L}}^{V^{V}}\left[P^{\mathrm{sat}} - P(V)\right]\mathrm{d}V = 0",
    # ---------------------------------------------------------------- Chapter 4
    "e_zslope":   r"\lim_{P\to 0}\left(\frac{\partial Z}{\partial P}\right)_{\!T} = \frac{B_{2}(T)}{RT}",
    "e_boyle":    r"B_{2}(T_{\mathrm{B}}) = 0",
    # ---------------------------------------------------------------- Chapter 5
    "e_virial":   r"Z = 1 + B_{2}\rho + B_{3}\rho^{2} + \cdots",
    "e_virialP":  r"Z = 1 + B'P + C'P^{2} + \cdots, \qquad B' = \frac{B_{2}}{RT}",
    "e_B2stat":   r"B_{2}(T) = -2\pi N_{\mathrm{A}}\int_0^{\infty}\left[\mathrm{e}^{-u(r)/k_{\mathrm{B}}T}-1\right]r^{2}\,\mathrm{d}r",
    # ---------------------------------------------------------------- Chapter 6
    "e_csvdw":    r"P_{\mathrm{r}} = \frac{8T_{\mathrm{r}}}{3V_{\mathrm{r}}-1} - \frac{3}{V_{\mathrm{r}}^{2}}",
    "e_cs2":      r"Z = Z(T_{\mathrm{r}},P_{\mathrm{r}}) \quad \text{for every fluid}",
    # ---------------------------------------------------------------- Chapter 7
    "e_omega":    r"\omega \;\equiv\; -1 - \log_{10}\left(P_{\mathrm{r}}^{\mathrm{sat}}\right)_{T_{\mathrm{r}}=0.7}",
    "e_pitzer":   r"Z = Z^{(0)}(T_{\mathrm{r}},P_{\mathrm{r}}) \;+\; \omega\,Z^{(1)}(T_{\mathrm{r}},P_{\mathrm{r}})",
    # ---------------------------------------------------------------- Chapter 8
    "e_rk":       r"P = \frac{RT}{V-b} \;-\; \frac{a}{\sqrt{T}\,V(V+b)}",
    "e_rkab":     r"a = \frac{0.42748\,R^{2}T_{\mathrm{c}}^{5/2}}{P_{\mathrm{c}}}, \qquad b = \frac{0.08664\,RT_{\mathrm{c}}}{P_{\mathrm{c}}}, \qquad Z_{\mathrm{c}} = 1/3 = 0.3333",
    # ---------------------------------------------------------------- Chapter 9
    "e_srk":      r"P = \frac{RT}{V-b} \;-\; \frac{a_{\mathrm{c}}\,\alpha(T_{\mathrm{r}},\omega)}{V(V+b)}",
    "e_srkalpha": r"\alpha = \left[1+m\left(1-\sqrt{T_{\mathrm{r}}}\right)\right]^{2}, \qquad m = 0.480+1.574\,\omega-0.176\,\omega^{2}",
    # --------------------------------------------------------------- Chapter 10
    "e_pr":       r"P = \frac{RT}{V-b} \;-\; \frac{a_{\mathrm{c}}\,\alpha(T_{\mathrm{r}},\omega)}{V(V+b)+b(V-b)}",
    "e_pralpha":  r"\kappa = 0.37464 + 1.54226\,\omega - 0.26992\,\omega^{2}",
    "e_prcubic":  r"Z^{3} - (1-B)Z^{2} + \left(A-3B^{2}-2B\right)Z - \left(AB-B^{2}-B^{3}\right) = 0",
    "e_prphi":    r"\ln\varphi = Z-1-\ln(Z-B) \;-\; \frac{A}{2\sqrt{2}\,B}\,\ln\frac{Z+(1+\sqrt{2})B}{Z+(1-\sqrt{2})B}",
    "e_prab":     r"a_{\mathrm{c}} = \frac{0.45724\,R^{2}T_{\mathrm{c}}^{2}}{P_{\mathrm{c}}}, \qquad b = \frac{0.07780\,RT_{\mathrm{c}}}{P_{\mathrm{c}}}, \qquad Z_{\mathrm{c}} = 0.3074",
    # --------------------------------------------------------------- Chapter 11
    "e_saft":     r"\tilde a^{\mathrm{res}} = \tilde a^{\mathrm{hs}} + \tilde a^{\mathrm{chain}} + \tilde a^{\mathrm{disp}} + \tilde a^{\mathrm{assoc}}",
    "e_saftP":    r"P = \rho^{2}\left(\frac{\partial\left(A^{\mathrm{res}}/N\right)}{\partial\rho}\right)_{\!T} + \rho\,k_{\mathrm{B}}T",
    "e_cs":       r"\tilde a^{\mathrm{hs}} = \frac{4\eta-3\eta^{2}}{(1-\eta)^{2}}",
    "e_chain":    r"\tilde a^{\mathrm{chain}} = (1-m)\ln g^{\mathrm{hs}}(\sigma)",
    "e_assoc":    r"\tilde a^{\mathrm{assoc}} = \sum_{A}\left[\ln X^{A} - \frac{X^{A}}{2}\right] + \frac{M}{2}",
    "e_delta":    r"\Delta^{AB} = g^{\mathrm{hs}}\,\kappa^{AB}\left[\mathrm{e}^{\varepsilon^{AB}/k_{\mathrm{B}}T}-1\right]",
    # --------------------------------------------------------------- Chapter 12
    "e_debye":    r"\kappa^{-1} = \left(\frac{\varepsilon_{\mathrm{r}}\varepsilon_{0}k_{\mathrm{B}}T}{e^{2}\sum_i \rho_i z_i^{2}}\right)^{\!1/2}",
    "e_dhll":     r"\log_{10}\gamma_{\pm} = -A\,|z_{+}z_{-}|\sqrt{I}",
    "e_born":     r"A^{\mathrm{Born}} = -\frac{N_{\mathrm{A}}e^{2}}{8\pi\varepsilon_{0}}\sum_i x_i z_i^{2}\left(1-\frac{1}{\varepsilon_{\mathrm{r}}}\right)\frac{1}{r_i}",
    "e_elec":     r"\tilde a^{\mathrm{res}} = \tilde a^{\mathrm{SAFT}} + \tilde a^{\mathrm{Born}} + \tilde a^{\mathrm{DH/MSA}}",
    # --------------------------------------------------------------- Chapter 13
    "e_mlA":      r"A^{\mathrm{res}}_{\theta}(T,\rho,\mathbf{d}) \;\longrightarrow\; P,\;\mu_i,\;c_v \;\; \text{by automatic differentiation}",
    "e_constraint": r"\lim_{\rho\to 0}A^{\mathrm{res}} = 0, \qquad c_v > 0, \qquad \oint V\,\mathrm{d}P = 0",
    # ------------------------------------------------------------------- shared
    "e_amix":     r"a_{\mathrm{mix}} = \sum_i\sum_j y_iy_j\sqrt{a_ia_j}\,(1-k_{ij}), \qquad b_{\mathrm{mix}} = \sum_i y_ib_i",
    "e_zsign":    r"Z<1: \text{ attraction dominates} \qquad Z>1: \text{ repulsion dominates}",
    "e_ab":       r"A \equiv \frac{a P}{R^{2}T^{2}}, \qquad B \equiv \frac{bP}{RT}",
    "e_alphadef": r"a(T) = a_{\mathrm{c}}\,\alpha(T_{\mathrm{r}},\omega), \qquad \alpha(1,\omega) = 1",
}

PREAMBLE = r"""\documentclass[border=2pt,multi=eqp,crop]{standalone}
\usepackage{fontspec}
\usepackage{amsmath}
\usepackage{unicode-math}
\setmainfont{Carlito}
\setmathfont{DejaVu Math TeX Gyre}
% upright letters, digits and operators from Carlito so they match the slide font
\setmathfont[range={up,bfup,sfup,"0030-"0039}]{Carlito}
\setmathfont[range={it,bfit,sfit}]{Carlito Italic}
% glyphs Carlito does not carry must come back from the maths font
\setmathfont[range={"1D715,"2202}]{DejaVu Math TeX Gyre}   % partial derivative
\setmathfont[range={"221A,"222B,"222E,"2211,"220F}]{DejaVu Math TeX Gyre}
\usepackage{xcolor}
\definecolor{ink}{HTML}{INKHEX}
\color{ink}
\newenvironment{eqp}{}{}
\begin{document}
""".replace("INKHEX", INK)


def build():
    shutil.rmtree(TMP, ignore_errors=True)
    os.makedirs(TMP, exist_ok=True)
    os.makedirs(OUT, exist_ok=True)
    names = list(EQS)
    body = "\n".join(r"\begin{eqp}$\displaystyle " + EQS[n] + r"$\end{eqp}" for n in names)
    with open(f"{TMP}/eq.tex", "w") as fh:
        fh.write(PREAMBLE + body + "\n\\end{document}\n")

    r = subprocess.run(["xelatex", "-interaction=nonstopmode", "eq.tex"],
                       cwd=TMP, capture_output=True, text=True)
    log = open(f"{TMP}/eq.log", errors="ignore").read()
    missing = sorted(set(re.findall(r"Missing character: There is no (.+?) \(", log)))
    if missing:
        raise SystemExit("MISSING GLYPHS: " + ", ".join(missing))
    if not os.path.exists(f"{TMP}/eq.pdf"):
        raise SystemExit(r.stdout[-3000:])

    subprocess.run(["pdftoppm", "-png", "-r", str(DPI), "-forcenum", "eq.pdf", "p"],
                   cwd=TMP, check=True)
    pages = sorted((f for f in os.listdir(TMP) if f.startswith("p-") and f.endswith(".png")),
                   key=lambda f: int(f.split("-")[1].split(".")[0]))
    assert len(pages) == len(names), f"{len(pages)} pages for {len(names)} equations"

    from PIL import Image
    import numpy as np
    manifest = {}
    for n, p in zip(names, pages):
        im = Image.open(f"{TMP}/{p}").convert("RGB")
        arr = np.asarray(im).astype(np.int16)
        alpha = (255 - arr.max(axis=2)).clip(0, 255).astype("uint8")
        rgb = np.zeros_like(arr, dtype="uint8")
        rgb[..., 0], rgb[..., 1], rgb[..., 2] = 0x1C, 0x24, 0x2B
        Image.fromarray(np.dstack([rgb, alpha]), "RGBA").save(f"{OUT}/{n}.png")
        manifest[n] = {
            "w": round(im.width / DPI * SCALE, 4),   # inches at TARGET_PT
            "h": round(im.height / DPI * SCALE, 4),
        }
    with open(f"{OUT}/manifest.json", "w") as fh:
        json.dump(manifest, fh, indent=1, sort_keys=True)
    widest = max(manifest.items(), key=lambda kv: kv[1]["w"])
    print(f"rendered {len(names)} equations at {TARGET_PT} pt")
    print(f"widest: {widest[0]} = {widest[1]['w']:.2f} in")


if __name__ == "__main__":
    build()
