Source code for tidal.plot_pgf

from __future__ import annotations

import shutil
import warnings
from typing import Literal

import matplotlib as mpl
from matplotlib import rcParams

# Try to use PGF backend, but fall back to default if unavailable.
# Check for TeX availability FIRST — mpl.use("pgf") can succeed even when
# no LaTeX executable is installed, causing deferred crashes on render.
pgf_available = False
_pgf_backend_error = None

if any(shutil.which(cmd) is not None for cmd in ["pdflatex", "xelatex", "lualatex"]):
    try:
        mpl.use("pgf")
        pgf_available = True
    except RuntimeError as e:
        _pgf_backend_error = str(e)
        warnings.warn(
            f"PGF backend not available: {e}. Using default backend instead.",
            UserWarning,
            stacklevel=2,
        )
else:
    _pgf_backend_error = "No LaTeX executable found (pdflatex, xelatex, lualatex)"


[docs] def check_tex_available() -> bool: """Check if LaTeX executables are available on the system.""" return any( shutil.which(cmd) is not None for cmd in ["pdflatex", "xelatex", "lualatex"] )
def _check_pdf_converter_available() -> bool: """Check if PDF to PNG converter is available (ghostscript or similar).""" return shutil.which("gs") is not None or shutil.which("convert") is not None
[docs] def enable_pgf( texsystem: Literal["pdflatex", "xelatex", "lualatex"] = "pdflatex", *, serif: bool = True, base_size: int = 10, fallback_on_error: bool = True, ) -> None: """Enable PGF backend for matplotlib with LaTeX rendering. If LaTeX or PGF backend is not available, falls back to standard matplotlib settings with similar font configuration. Args: texsystem: LaTeX system to use (pdflatex, xelatex, or lualatex) serif: Whether to use serif fonts base_size: Base font size fallback_on_error: Whether to fall back to standard matplotlib on error Raises ------ RuntimeError If PGF backend, LaTeX system, or PDF converter is not available and fallback_on_error is False. """ if not pgf_available: if fallback_on_error: _setup_fallback_fonts(serif=serif, base_size=base_size) return msg = f"PGF backend not available ({_pgf_backend_error}) and fallback disabled" raise RuntimeError(msg) if not check_tex_available(): if fallback_on_error: warnings.warn( f"LaTeX system '{texsystem}' not found. Falling back to standard matplotlib.", UserWarning, stacklevel=2, ) _setup_fallback_fonts(serif=serif, base_size=base_size) return msg = f"LaTeX system '{texsystem}' not available and fallback disabled" raise RuntimeError(msg) if not _check_pdf_converter_available(): if fallback_on_error: warnings.warn( "PDF to PNG converter not found (ghostscript). " "Falling back to standard matplotlib.", UserWarning, stacklevel=2, ) _setup_fallback_fonts(serif=serif, base_size=base_size) return msg = "PDF to PNG converter (ghostscript) not available and fallback disabled" raise RuntimeError(msg) try: rcParams.update( { "text.usetex": True, "pgf.texsystem": texsystem, "pgf.rcfonts": False, "font.family": "serif" if serif else "sans-serif", "font.size": base_size, "axes.labelsize": base_size, "axes.titlesize": base_size, "legend.fontsize": base_size - 2, "xtick.labelsize": base_size - 2, "ytick.labelsize": base_size - 2, "axes.unicode_minus": False, } ) except Exception as e: if fallback_on_error: warnings.warn( f"Failed to configure PGF backend: {e}. Falling back to standard matplotlib.", UserWarning, stacklevel=2, ) _setup_fallback_fonts(serif=serif, base_size=base_size) else: raise
def _setup_fallback_fonts(*, serif: bool = True, base_size: int = 10) -> None: """Set up fallback font configuration when PGF/LaTeX is not available.""" # Ensure we're not using the pgf backend if mpl.get_backend() == "pgf": mpl.use("Agg") # Use Agg backend which is reliable and supports all formats rcParams.update( { "text.usetex": False, "font.family": "serif" if serif else "sans-serif", "font.size": base_size, "axes.labelsize": base_size, "axes.titlesize": base_size, "legend.fontsize": base_size - 2, "xtick.labelsize": base_size - 2, "ytick.labelsize": base_size - 2, "axes.unicode_minus": False, } )