Source code for manim.utils.tex

"""Utilities for processing LaTeX templates."""

from __future__ import annotations

__all__ = [
    "TexTemplate",
    "TexTemplateFromFile",
]

import copy
import os
import re
from pathlib import Path


[docs]class TexTemplate: """TeX templates are used for creating Tex() and MathTex() objects. Parameters ---------- tex_compiler The TeX compiler to be used, e.g. ``latex``, ``pdflatex`` or ``lualatex`` output_format The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf`` documentclass The command defining the documentclass, e.g. ``\\documentclass[preview]{standalone}`` preamble The document's preamble, i.e. the part between ``\\documentclass`` and ``\\begin{document}`` placeholder_text Text in the document that will be replaced by the expression to be rendered post_doc_commands Text (definitions, commands) to be inserted at right after ``\\begin{document}``, e.g. ``\\boldmath`` Attributes ---------- tex_compiler : :class:`str` The TeX compiler to be used, e.g. ``latex``, ``pdflatex`` or ``lualatex`` output_format : :class:`str` The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf`` documentclass : :class:`str` The command defining the documentclass, e.g. ``\\documentclass[preview]{standalone}`` preamble : :class:`str` The document's preamble, i.e. the part between ``\\documentclass`` and ``\\begin{document}`` placeholder_text : :class:`str` Text in the document that will be replaced by the expression to be rendered post_doc_commands : :class:`str` Text (definitions, commands) to be inserted at right after ``\\begin{document}``, e.g. ``\\boldmath`` """ default_documentclass = r"\documentclass[preview]{standalone}" default_preamble = r""" \usepackage[english]{babel} \usepackage{amsmath} \usepackage{amssymb} """ default_placeholder_text = "YourTextHere" default_tex_compiler = "latex" default_output_format = ".dvi" default_post_doc_commands = "" def __init__( self, tex_compiler: str | None = None, output_format: str | None = None, documentclass: str | None = None, preamble: str | None = None, placeholder_text: str | None = None, post_doc_commands: str | None = None, **kwargs, ): self.tex_compiler = ( tex_compiler if tex_compiler is not None else TexTemplate.default_tex_compiler ) self.output_format = ( output_format if output_format is not None else TexTemplate.default_output_format ) self.documentclass = ( documentclass if documentclass is not None else TexTemplate.default_documentclass ) self.preamble = ( preamble if preamble is not None else TexTemplate.default_preamble ) self.placeholder_text = ( placeholder_text if placeholder_text is not None else TexTemplate.default_placeholder_text ) self.post_doc_commands = ( post_doc_commands if post_doc_commands is not None else TexTemplate.default_post_doc_commands ) self._rebuild() def __eq__(self, other: TexTemplate) -> bool: return ( self.body == other.body and self.tex_compiler == other.tex_compiler and self.output_format == other.output_format and self.post_doc_commands == other.post_doc_commands )
[docs] def _rebuild(self): """Rebuilds the entire TeX template text from ``\\documentclass`` to ``\\end{document}`` according to all settings and choices.""" self.body = ( self.documentclass + "\n" + self.preamble + "\n" + r"\begin{document}" + "\n" + self.post_doc_commands + "\n" + self.placeholder_text + "\n" + "\n" + r"\end{document}" + "\n" )
[docs] def add_to_preamble(self, txt: str, prepend: bool = False): """Adds stuff to the TeX template's preamble (e.g. definitions, packages). Text can be inserted at the beginning or at the end of the preamble. Parameters ---------- txt String containing the text to be added, e.g. ``\\usepackage{hyperref}`` prepend Whether the text should be added at the beginning of the preamble, i.e. right after ``\\documentclass``. Default is to add it at the end of the preamble, i.e. right before ``\\begin{document}`` """ if prepend: self.preamble = txt + "\n" + self.preamble else: self.preamble += "\n" + txt self._rebuild() return self
[docs] def add_to_document(self, txt: str): """Adds txt to the TeX template just after \\begin{document}, e.g. ``\\boldmath`` Parameters ---------- txt String containing the text to be added. """ self.post_doc_commands += "\n" + txt + "\n" self._rebuild() return self
[docs] def get_texcode_for_expression(self, expression: str): """Inserts expression verbatim into TeX template. Parameters ---------- expression The string containing the expression to be typeset, e.g. ``$\\sqrt{2}$`` Returns ------- :class:`str` LaTeX code based on current template, containing the given ``expression`` and ready for typesetting """ return self.body.replace(self.placeholder_text, expression)
[docs] def _texcode_for_environment(self, environment: str): """Processes the tex_environment string to return the correct ``\\begin{environment}[extra]{extra}`` and ``\\end{environment}`` strings Parameters ---------- environment The tex_environment as a string. Acceptable formats include: ``{align*}``, ``align*``, ``{tabular}[t]{cccl}``, ``tabular}{cccl``, ``\\begin{tabular}[t]{cccl}``. Returns ------- Tuple[:class:`str`, :class:`str`] A pair of strings representing the opening and closing of the tex environment, e.g. ``\\begin{tabular}{cccl}`` and ``\\end{tabular}`` """ # If the environment starts with \begin, remove it if environment[0:6] == r"\begin": environment = environment[6:] # If environment begins with { strip it if environment[0] == r"{": environment = environment[1:] # The \begin command takes everything and closes with a brace begin = r"\begin{" + environment if ( begin[-1] != r"}" and begin[-1] != r"]" ): # If it doesn't end on } or ], assume missing } begin += r"}" # While the \end command terminates at the first closing brace split_at_brace = re.split(r"}", environment, 1) end = r"\end{" + split_at_brace[0] + r"}" return begin, end
[docs] def get_texcode_for_expression_in_env(self, expression: str, environment: str): r"""Inserts expression into TeX template wrapped in \begin{environment} and \end{environment} Parameters ---------- expression The string containing the expression to be typeset, e.g. ``$\\sqrt{2}$`` environment The string containing the environment in which the expression should be typeset, e.g. ``align*`` Returns ------- :class:`str` LaTeX code based on template, containing the given expression inside its environment, ready for typesetting """ begin, end = self._texcode_for_environment(environment) return self.body.replace(self.placeholder_text, f"{begin}\n{expression}\n{end}")
def copy(self) -> TexTemplate: return copy.deepcopy(self)
[docs]class TexTemplateFromFile(TexTemplate): """A TexTemplate object created from a template file (default: tex_template.tex) Parameters ---------- tex_filename Path to a valid TeX template file kwargs Arguments for :class:`~.TexTemplate`. Attributes ---------- template_file : :class:`str` Path to a valid TeX template file body : :class:`str` Content of the TeX template file tex_compiler : :class:`str` The TeX compiler to be used, e.g. ``latex``, ``pdflatex`` or ``lualatex`` output_format : :class:`str` The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf`` """ def __init__( self, *, tex_filename: str | os.PathLike = "tex_template.tex", **kwargs ): self.template_file = Path(tex_filename) super().__init__(**kwargs)
[docs] def _rebuild(self): self.body = self.template_file.read_text()
def file_not_mutable(self): raise Exception("Cannot modify TexTemplate when using a template file.")
[docs] def add_to_preamble(self, txt, prepend=False): self.file_not_mutable()
[docs] def add_to_document(self, txt): self.file_not_mutable()