Source code for memeplotlib._meme

"""Object-oriented Meme API."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

import matplotlib.pyplot as plt

from memeplotlib._cache import TemplateCache
from memeplotlib._config import config
from memeplotlib._rendering import render_meme
from memeplotlib._template import Template, _resolve_template

if TYPE_CHECKING:
    from matplotlib.axes import Axes
    from matplotlib.figure import Figure


[docs] class Meme: """A meme builder with a fluent (chainable) API. Parameters ---------- template : str or Template Template identifier (memegen ID, file path, URL) or a :class:`Template` instance. *lines : str Initial text lines. font : str or None, optional Font family name. color : str or None, optional Text fill color. outline_color : str or None, optional Text outline color. outline_width : float or None, optional Outline stroke width. fontsize : float or None, optional Font size in points. style : str or None, optional Text transform -- ``"upper"``, ``"lower"``, or ``"none"``. Examples -------- >>> from memeplotlib import Meme >>> Meme("buzz").top("memes").bottom("memes everywhere").show() # doctest: +SKIP >>> m = Meme("drake") # doctest: +SKIP >>> m.top("writing tests") # doctest: +SKIP >>> m.bottom("shipping to prod") # doctest: +SKIP >>> fig, ax = m.render() # doctest: +SKIP >>> m.save("output.png") # doctest: +SKIP """ def __init__( self, template: str | Template, *lines: str, font: str | None = None, color: str | None = None, outline_color: str | None = None, outline_width: float | None = None, fontsize: float | None = None, style: str | None = None, ): if isinstance(template, Template): self._template = template else: self._template_str = template self._template: Template | None = None self._lines: list[str] = list(lines) self._font = font self._color = color self._outline_color = outline_color self._outline_width = outline_width self._fontsize = fontsize self._style = style self._fig: Figure | None = None self._ax: Axes | None = None self._cache = TemplateCache() def _get_template(self) -> Template: if self._template is None: self._template = _resolve_template(self._template_str) return self._template
[docs] def top(self, text: str) -> Meme: """Set the top text line (index 0). Parameters ---------- text : str The text to place at the top of the meme. Returns ------- Meme Self, for method chaining. """ if len(self._lines) == 0: self._lines.append(text) else: self._lines[0] = text return self
[docs] def bottom(self, text: str) -> Meme: """Set the bottom text line (index 1). Parameters ---------- text : str The text to place at the bottom of the meme. Returns ------- Meme Self, for method chaining. """ while len(self._lines) < 2: self._lines.append("") self._lines[1] = text return self
[docs] def text(self, index: int, text: str) -> Meme: """Set text at a specific line index. Parameters ---------- index : int Zero-based line index. text : str The text to place at the given position. Returns ------- Meme Self, for method chaining. """ while len(self._lines) <= index: self._lines.append("") self._lines[index] = text return self
[docs] def render( self, ax: Axes | None = None, figsize: tuple[float, float] | None = None, dpi: int | None = None, ) -> tuple[Figure, Axes]: """Render the meme and return the Figure and Axes. Parameters ---------- ax : Axes or None, optional Existing axes to render onto. figsize : tuple of (float, float) or None, optional Figure size in inches ``(width, height)``. dpi : int or None, optional Dots per inch. Returns ------- tuple of (Figure, Axes) The rendered matplotlib Figure and Axes. """ template = self._get_template() fig, ax_out = render_meme( template, self._lines, ax=ax, figsize=figsize, dpi=dpi, font=self._font, color=self._color, outline_color=self._outline_color, outline_width=self._outline_width, fontsize=self._fontsize, style=self._style, cache=self._cache, ) self._fig = fig self._ax = ax_out return fig, ax_out
[docs] def show(self) -> None: """Render and display the meme. Calls :meth:`render` if the meme has not been rendered yet, then displays the figure with ``matplotlib.pyplot.show()``. """ if self._fig is None: self.render() plt.show()
[docs] def save(self, path: str | Path, dpi: int | None = None, **kwargs) -> None: """Render and save the meme to a file. Parameters ---------- path : str or Path Output file path (e.g., ``"meme.png"``). dpi : int or None, optional Dots per inch for the saved image. **kwargs Additional keyword arguments passed to :meth:`matplotlib.figure.Figure.savefig`. """ if self._fig is None: self.render(dpi=dpi) self._fig.savefig( str(path), dpi=dpi or config.dpi, bbox_inches="tight", pad_inches=0, **kwargs, )