We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Rendering TeX strings containing % broken
This unfortunately broke rendering of TeX strings containing `%`. Trying to create `Tex(r"\%")` fails in v0.15.0.

_Originally posted by @behackl in https://github.com/ManimCommunity/manim/issues/2574#issuecomment-1054726581_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `manim/utils/tex_file_writing.py`
Content:
```
1 """Interface for writing, compiling, and converting ``.tex`` files.
2 
3 .. SEEALSO::
4 
5     :mod:`.mobject.svg.tex_mobject`
6 
7 """
8 
9 from __future__ import annotations
10 
11 import hashlib
12 import os
13 import re
14 import unicodedata
15 from pathlib import Path
16 
17 from .. import config, logger
18 
19 
20 def tex_hash(expression):
21     id_str = str(expression)
22     hasher = hashlib.sha256()
23     hasher.update(id_str.encode())
24     # Truncating at 16 bytes for cleanliness
25     return hasher.hexdigest()[:16]
26 
27 
28 def tex_to_svg_file(expression, environment=None, tex_template=None):
29     """Takes a tex expression and returns the svg version of the compiled tex
30 
31     Parameters
32     ----------
33     expression : :class:`str`
34         String containing the TeX expression to be rendered, e.g. ``\\sqrt{2}`` or ``foo``
35     environment : Optional[:class:`str`], optional
36         The string containing the environment in which the expression should be typeset, e.g. ``align*``
37     tex_template : Optional[:class:`~.TexTemplate`], optional
38         Template class used to typesetting. If not set, use default template set via `config["tex_template"]`
39 
40     Returns
41     -------
42     :class:`str`
43         Path to generated SVG file.
44     """
45     if tex_template is None:
46         tex_template = config["tex_template"]
47     tex_file = generate_tex_file(expression, environment, tex_template)
48     dvi_file = compile_tex(
49         tex_file,
50         tex_template.tex_compiler,
51         tex_template.output_format,
52     )
53     return convert_to_svg(dvi_file, tex_template.output_format)
54 
55 
56 def generate_tex_file(expression, environment=None, tex_template=None):
57     """Takes a tex expression (and an optional tex environment),
58     and returns a fully formed tex file ready for compilation.
59 
60     Parameters
61     ----------
62     expression : :class:`str`
63         String containing the TeX expression to be rendered, e.g. ``\\sqrt{2}`` or ``foo``
64     environment : Optional[:class:`str`], optional
65         The string containing the environment in which the expression should be typeset, e.g. ``align*``
66     tex_template : Optional[:class:`~.TexTemplate`], optional
67         Template class used to typesetting. If not set, use default template set via `config["tex_template"]`
68 
69     Returns
70     -------
71     :class:`str`
72         Path to generated TeX file
73     """
74     if tex_template is None:
75         tex_template = config["tex_template"]
76     if environment is not None:
77         output = tex_template.get_texcode_for_expression_in_env(expression, environment)
78     else:
79         output = tex_template.get_texcode_for_expression(expression)
80 
81     tex_dir = config.get_dir("tex_dir")
82     if not os.path.exists(tex_dir):
83         os.makedirs(tex_dir)
84 
85     result = os.path.join(tex_dir, tex_hash(output)) + ".tex"
86     if not os.path.exists(result):
87         logger.info(f"Writing {expression} to %(path)s", {"path": f"{result}"})
88         with open(result, "w", encoding="utf-8") as outfile:
89             outfile.write(output)
90     return result
91 
92 
93 def tex_compilation_command(tex_compiler, output_format, tex_file, tex_dir):
94     """Prepares the tex compilation command with all necessary cli flags
95 
96     Parameters
97     ----------
98     tex_compiler : :class:`str`
99         String containing the compiler to be used, e.g. ``pdflatex`` or ``lualatex``
100     output_format : :class:`str`
101         String containing the output format generated by the compiler, e.g. ``.dvi`` or ``.pdf``
102     tex_file : :class:`str`
103         File name of TeX file to be typeset.
104     tex_dir : :class:`str`
105         Path to the directory where compiler output will be stored.
106 
107     Returns
108     -------
109     :class:`str`
110         Compilation command according to given parameters
111     """
112     if tex_compiler in {"latex", "pdflatex", "luatex", "lualatex"}:
113         commands = [
114             tex_compiler,
115             "-interaction=batchmode",
116             f'-output-format="{output_format[1:]}"',
117             "-halt-on-error",
118             f'-output-directory="{tex_dir}"',
119             f'"{tex_file}"',
120             ">",
121             os.devnull,
122         ]
123     elif tex_compiler == "xelatex":
124         if output_format == ".xdv":
125             outflag = "-no-pdf"
126         elif output_format == ".pdf":
127             outflag = ""
128         else:
129             raise ValueError("xelatex output is either pdf or xdv")
130         commands = [
131             "xelatex",
132             outflag,
133             "-interaction=batchmode",
134             "-halt-on-error",
135             f'-output-directory="{tex_dir}"',
136             f'"{tex_file}"',
137             ">",
138             os.devnull,
139         ]
140     else:
141         raise ValueError(f"Tex compiler {tex_compiler} unknown.")
142     return " ".join(commands)
143 
144 
145 def insight_inputenc_error(matching):
146     code_point = chr(int(matching[1], 16))
147     name = unicodedata.name(code_point)
148     yield f"TexTemplate does not support character '{name}' (U+{matching[1]})."
149     yield "See the documentation for manim.mobject.svg.tex_mobject for details on using a custom TexTemplate."
150 
151 
152 def insight_package_not_found_error(matching):
153     yield f"You do not have package {matching[1]} installed."
154     yield f"Install {matching[1]} it using your LaTeX package manager, or check for typos."
155 
156 
157 def compile_tex(tex_file, tex_compiler, output_format):
158     """Compiles a tex_file into a .dvi or a .xdv or a .pdf
159 
160     Parameters
161     ----------
162     tex_file : :class:`str`
163         File name of TeX file to be typeset.
164     tex_compiler : :class:`str`
165         String containing the compiler to be used, e.g. ``pdflatex`` or ``lualatex``
166     output_format : :class:`str`
167         String containing the output format generated by the compiler, e.g. ``.dvi`` or ``.pdf``
168 
169     Returns
170     -------
171     :class:`str`
172         Path to generated output file in desired format (DVI, XDV or PDF).
173     """
174     result = tex_file.replace(".tex", output_format)
175     result = Path(result).as_posix()
176     tex_file = Path(tex_file).as_posix()
177     tex_dir = Path(config.get_dir("tex_dir")).as_posix()
178     if not os.path.exists(result):
179         command = tex_compilation_command(
180             tex_compiler,
181             output_format,
182             tex_file,
183             tex_dir,
184         )
185         exit_code = os.system(command)
186         if exit_code != 0:
187             log_file = tex_file.replace(".tex", ".log")
188             print_all_tex_errors(log_file, tex_compiler, tex_file)
189             raise ValueError(
190                 f"{tex_compiler} error converting to"
191                 f" {output_format[1:]}. See log output above or"
192                 f" the log file: {log_file}",
193             )
194     return result
195 
196 
197 def convert_to_svg(dvi_file, extension, page=1):
198     """Converts a .dvi, .xdv, or .pdf file into an svg using dvisvgm.
199 
200     Parameters
201     ----------
202     dvi_file : :class:`str`
203         File name of the input file to be converted.
204     extension : :class:`str`
205         String containing the file extension and thus indicating the file type, e.g. ``.dvi`` or ``.pdf``
206     page : Optional[:class:`int`], optional
207         Page to be converted if input file is multi-page.
208 
209     Returns
210     -------
211     :class:`str`
212         Path to generated SVG file.
213     """
214     result = dvi_file.replace(extension, ".svg")
215     result = Path(result).as_posix()
216     dvi_file = Path(dvi_file).as_posix()
217     if not os.path.exists(result):
218         commands = [
219             "dvisvgm",
220             "--pdf" if extension == ".pdf" else "",
221             "-p " + str(page),
222             f'"{dvi_file}"',
223             "-n",
224             "-v 0",
225             "-o " + f'"{result}"',
226             ">",
227             os.devnull,
228         ]
229         os.system(" ".join(commands))
230 
231     # if the file does not exist now, this means conversion failed
232     if not os.path.exists(result):
233         raise ValueError(
234             f"Your installation does not support converting {extension} files to SVG."
235             f" Consider updating dvisvgm to at least version 2.4."
236             f" If this does not solve the problem, please refer to our troubleshooting guide at:"
237             f" https://docs.manim.community/en/stable/installation/troubleshooting.html",
238         )
239 
240     return result
241 
242 
243 def print_all_tex_errors(log_file, tex_compiler, tex_file):
244     if not Path(log_file).exists():
245         raise RuntimeError(
246             f"{tex_compiler} failed but did not produce a log file. "
247             "Check your LaTeX installation.",
248         )
249     with open(log_file) as f:
250         tex_compilation_log = f.readlines()
251         error_indices = [
252             index
253             for index, line in enumerate(tex_compilation_log)
254             if line.startswith("!")
255         ]
256         if error_indices:
257             with open(tex_file) as g:
258                 tex = g.readlines()
259                 for error_index in error_indices:
260                     print_tex_error(tex_compilation_log, error_index, tex)
261 
262 
263 LATEX_ERROR_INSIGHTS = [
264     (
265         r"inputenc Error: Unicode character (?:.*) \(U\+([0-9a-fA-F]+)\)",
266         insight_inputenc_error,
267     ),
268     (
269         r"LaTeX Error: File `(.*?[clsty])' not found",
270         insight_package_not_found_error,
271     ),
272 ]
273 
274 
275 def print_tex_error(tex_compilation_log, error_start_index, tex_source):
276     logger.error(
277         f"LaTeX compilation error: {tex_compilation_log[error_start_index][2:]}",
278     )
279 
280     # TeX errors eventually contain a line beginning 'l.xxx` where xxx is the line number that caused the compilation
281     # failure. This code finds the next such line after the error current error message
282     line_of_tex_error = (
283         int(
284             [
285                 log_line
286                 for log_line in tex_compilation_log[error_start_index:]
287                 if log_line.startswith("l.")
288             ][0]
289             .split(" ")[0]
290             .split(".")[1],
291         )
292         - 1
293     )
294     # our tex error may be on a line outside our user input because of post-processing
295     if line_of_tex_error >= len(tex_source):
296         return None
297 
298     context = ["Context of error: \n"]
299     if line_of_tex_error < 3:
300         context += tex_source[: line_of_tex_error + 3]
301         context[-4] = "-> " + context[-4]
302     elif line_of_tex_error > len(tex_source) - 3:
303         context += tex_source[line_of_tex_error - 1 :]
304         context[1] = "-> " + context[1]
305     else:
306         context += tex_source[line_of_tex_error - 3 : line_of_tex_error + 3]
307         context[-4] = "-> " + context[-4]
308 
309     context = "".join(context)
310     logger.error(context)
311 
312     for insights in LATEX_ERROR_INSIGHTS:
313         prob, get_insight = insights
314         matching = re.search(
315             prob,
316             "".join(tex_compilation_log[error_start_index])[2:],
317         )
318         if matching is not None:
319             for insight in get_insight(matching):
320                 logger.info(insight)
321
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/manim/utils/tex_file_writing.py b/manim/utils/tex_file_writing.py
--- a/manim/utils/tex_file_writing.py
+++ b/manim/utils/tex_file_writing.py
@@ -84,7 +84,10 @@
 
     result = os.path.join(tex_dir, tex_hash(output)) + ".tex"
     if not os.path.exists(result):
-        logger.info(f"Writing {expression} to %(path)s", {"path": f"{result}"})
+        logger.info(
+            "Writing %(expression)s to %(path)s",
+            {"expression": expression, "path": f"{result}"},
+        )
         with open(result, "w", encoding="utf-8") as outfile:
             outfile.write(output)
     return result

{"golden_diff": "diff --git a/manim/utils/tex_file_writing.py b/manim/utils/tex_file_writing.py\n--- a/manim/utils/tex_file_writing.py\n+++ b/manim/utils/tex_file_writing.py\n@@ -84,7 +84,10 @@\n \n     result = os.path.join(tex_dir, tex_hash(output)) + \".tex\"\n     if not os.path.exists(result):\n-        logger.info(f\"Writing {expression} to %(path)s\", {\"path\": f\"{result}\"})\n+        logger.info(\n+            \"Writing %(expression)s to %(path)s\",\n+            {\"expression\": expression, \"path\": f\"{result}\"},\n+        )\n         with open(result, \"w\", encoding=\"utf-8\") as outfile:\n             outfile.write(output)\n     return result\n", "issue": "Rendering TeX strings containing % broken\nThis unfortunately broke rendering of TeX strings containing `%`. Trying to create `Tex(r\"\\%\")` fails in v0.15.0.\r\n\r\n_Originally posted by @behackl in https://github.com/ManimCommunity/manim/issues/2574#issuecomment-1054726581_\n", "before_files": [{"content": "\"\"\"Interface for writing, compiling, and converting ``.tex`` files.\n\n.. SEEALSO::\n\n    :mod:`.mobject.svg.tex_mobject`\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport hashlib\nimport os\nimport re\nimport unicodedata\nfrom pathlib import Path\n\nfrom .. import config, logger\n\n\ndef tex_hash(expression):\n    id_str = str(expression)\n    hasher = hashlib.sha256()\n    hasher.update(id_str.encode())\n    # Truncating at 16 bytes for cleanliness\n    return hasher.hexdigest()[:16]\n\n\ndef tex_to_svg_file(expression, environment=None, tex_template=None):\n    \"\"\"Takes a tex expression and returns the svg version of the compiled tex\n\n    Parameters\n    ----------\n    expression : :class:`str`\n        String containing the TeX expression to be rendered, e.g. ``\\\\sqrt{2}`` or ``foo``\n    environment : Optional[:class:`str`], optional\n        The string containing the environment in which the expression should be typeset, e.g. ``align*``\n    tex_template : Optional[:class:`~.TexTemplate`], optional\n        Template class used to typesetting. If not set, use default template set via `config[\"tex_template\"]`\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated SVG file.\n    \"\"\"\n    if tex_template is None:\n        tex_template = config[\"tex_template\"]\n    tex_file = generate_tex_file(expression, environment, tex_template)\n    dvi_file = compile_tex(\n        tex_file,\n        tex_template.tex_compiler,\n        tex_template.output_format,\n    )\n    return convert_to_svg(dvi_file, tex_template.output_format)\n\n\ndef generate_tex_file(expression, environment=None, tex_template=None):\n    \"\"\"Takes a tex expression (and an optional tex environment),\n    and returns a fully formed tex file ready for compilation.\n\n    Parameters\n    ----------\n    expression : :class:`str`\n        String containing the TeX expression to be rendered, e.g. ``\\\\sqrt{2}`` or ``foo``\n    environment : Optional[:class:`str`], optional\n        The string containing the environment in which the expression should be typeset, e.g. ``align*``\n    tex_template : Optional[:class:`~.TexTemplate`], optional\n        Template class used to typesetting. If not set, use default template set via `config[\"tex_template\"]`\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated TeX file\n    \"\"\"\n    if tex_template is None:\n        tex_template = config[\"tex_template\"]\n    if environment is not None:\n        output = tex_template.get_texcode_for_expression_in_env(expression, environment)\n    else:\n        output = tex_template.get_texcode_for_expression(expression)\n\n    tex_dir = config.get_dir(\"tex_dir\")\n    if not os.path.exists(tex_dir):\n        os.makedirs(tex_dir)\n\n    result = os.path.join(tex_dir, tex_hash(output)) + \".tex\"\n    if not os.path.exists(result):\n        logger.info(f\"Writing {expression} to %(path)s\", {\"path\": f\"{result}\"})\n        with open(result, \"w\", encoding=\"utf-8\") as outfile:\n            outfile.write(output)\n    return result\n\n\ndef tex_compilation_command(tex_compiler, output_format, tex_file, tex_dir):\n    \"\"\"Prepares the tex compilation command with all necessary cli flags\n\n    Parameters\n    ----------\n    tex_compiler : :class:`str`\n        String containing the compiler to be used, e.g. ``pdflatex`` or ``lualatex``\n    output_format : :class:`str`\n        String containing the output format generated by the compiler, e.g. ``.dvi`` or ``.pdf``\n    tex_file : :class:`str`\n        File name of TeX file to be typeset.\n    tex_dir : :class:`str`\n        Path to the directory where compiler output will be stored.\n\n    Returns\n    -------\n    :class:`str`\n        Compilation command according to given parameters\n    \"\"\"\n    if tex_compiler in {\"latex\", \"pdflatex\", \"luatex\", \"lualatex\"}:\n        commands = [\n            tex_compiler,\n            \"-interaction=batchmode\",\n            f'-output-format=\"{output_format[1:]}\"',\n            \"-halt-on-error\",\n            f'-output-directory=\"{tex_dir}\"',\n            f'\"{tex_file}\"',\n            \">\",\n            os.devnull,\n        ]\n    elif tex_compiler == \"xelatex\":\n        if output_format == \".xdv\":\n            outflag = \"-no-pdf\"\n        elif output_format == \".pdf\":\n            outflag = \"\"\n        else:\n            raise ValueError(\"xelatex output is either pdf or xdv\")\n        commands = [\n            \"xelatex\",\n            outflag,\n            \"-interaction=batchmode\",\n            \"-halt-on-error\",\n            f'-output-directory=\"{tex_dir}\"',\n            f'\"{tex_file}\"',\n            \">\",\n            os.devnull,\n        ]\n    else:\n        raise ValueError(f\"Tex compiler {tex_compiler} unknown.\")\n    return \" \".join(commands)\n\n\ndef insight_inputenc_error(matching):\n    code_point = chr(int(matching[1], 16))\n    name = unicodedata.name(code_point)\n    yield f\"TexTemplate does not support character '{name}' (U+{matching[1]}).\"\n    yield \"See the documentation for manim.mobject.svg.tex_mobject for details on using a custom TexTemplate.\"\n\n\ndef insight_package_not_found_error(matching):\n    yield f\"You do not have package {matching[1]} installed.\"\n    yield f\"Install {matching[1]} it using your LaTeX package manager, or check for typos.\"\n\n\ndef compile_tex(tex_file, tex_compiler, output_format):\n    \"\"\"Compiles a tex_file into a .dvi or a .xdv or a .pdf\n\n    Parameters\n    ----------\n    tex_file : :class:`str`\n        File name of TeX file to be typeset.\n    tex_compiler : :class:`str`\n        String containing the compiler to be used, e.g. ``pdflatex`` or ``lualatex``\n    output_format : :class:`str`\n        String containing the output format generated by the compiler, e.g. ``.dvi`` or ``.pdf``\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated output file in desired format (DVI, XDV or PDF).\n    \"\"\"\n    result = tex_file.replace(\".tex\", output_format)\n    result = Path(result).as_posix()\n    tex_file = Path(tex_file).as_posix()\n    tex_dir = Path(config.get_dir(\"tex_dir\")).as_posix()\n    if not os.path.exists(result):\n        command = tex_compilation_command(\n            tex_compiler,\n            output_format,\n            tex_file,\n            tex_dir,\n        )\n        exit_code = os.system(command)\n        if exit_code != 0:\n            log_file = tex_file.replace(\".tex\", \".log\")\n            print_all_tex_errors(log_file, tex_compiler, tex_file)\n            raise ValueError(\n                f\"{tex_compiler} error converting to\"\n                f\" {output_format[1:]}. See log output above or\"\n                f\" the log file: {log_file}\",\n            )\n    return result\n\n\ndef convert_to_svg(dvi_file, extension, page=1):\n    \"\"\"Converts a .dvi, .xdv, or .pdf file into an svg using dvisvgm.\n\n    Parameters\n    ----------\n    dvi_file : :class:`str`\n        File name of the input file to be converted.\n    extension : :class:`str`\n        String containing the file extension and thus indicating the file type, e.g. ``.dvi`` or ``.pdf``\n    page : Optional[:class:`int`], optional\n        Page to be converted if input file is multi-page.\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated SVG file.\n    \"\"\"\n    result = dvi_file.replace(extension, \".svg\")\n    result = Path(result).as_posix()\n    dvi_file = Path(dvi_file).as_posix()\n    if not os.path.exists(result):\n        commands = [\n            \"dvisvgm\",\n            \"--pdf\" if extension == \".pdf\" else \"\",\n            \"-p \" + str(page),\n            f'\"{dvi_file}\"',\n            \"-n\",\n            \"-v 0\",\n            \"-o \" + f'\"{result}\"',\n            \">\",\n            os.devnull,\n        ]\n        os.system(\" \".join(commands))\n\n    # if the file does not exist now, this means conversion failed\n    if not os.path.exists(result):\n        raise ValueError(\n            f\"Your installation does not support converting {extension} files to SVG.\"\n            f\" Consider updating dvisvgm to at least version 2.4.\"\n            f\" If this does not solve the problem, please refer to our troubleshooting guide at:\"\n            f\" https://docs.manim.community/en/stable/installation/troubleshooting.html\",\n        )\n\n    return result\n\n\ndef print_all_tex_errors(log_file, tex_compiler, tex_file):\n    if not Path(log_file).exists():\n        raise RuntimeError(\n            f\"{tex_compiler} failed but did not produce a log file. \"\n            \"Check your LaTeX installation.\",\n        )\n    with open(log_file) as f:\n        tex_compilation_log = f.readlines()\n        error_indices = [\n            index\n            for index, line in enumerate(tex_compilation_log)\n            if line.startswith(\"!\")\n        ]\n        if error_indices:\n            with open(tex_file) as g:\n                tex = g.readlines()\n                for error_index in error_indices:\n                    print_tex_error(tex_compilation_log, error_index, tex)\n\n\nLATEX_ERROR_INSIGHTS = [\n    (\n        r\"inputenc Error: Unicode character (?:.*) \\(U\\+([0-9a-fA-F]+)\\)\",\n        insight_inputenc_error,\n    ),\n    (\n        r\"LaTeX Error: File `(.*?[clsty])' not found\",\n        insight_package_not_found_error,\n    ),\n]\n\n\ndef print_tex_error(tex_compilation_log, error_start_index, tex_source):\n    logger.error(\n        f\"LaTeX compilation error: {tex_compilation_log[error_start_index][2:]}\",\n    )\n\n    # TeX errors eventually contain a line beginning 'l.xxx` where xxx is the line number that caused the compilation\n    # failure. This code finds the next such line after the error current error message\n    line_of_tex_error = (\n        int(\n            [\n                log_line\n                for log_line in tex_compilation_log[error_start_index:]\n                if log_line.startswith(\"l.\")\n            ][0]\n            .split(\" \")[0]\n            .split(\".\")[1],\n        )\n        - 1\n    )\n    # our tex error may be on a line outside our user input because of post-processing\n    if line_of_tex_error >= len(tex_source):\n        return None\n\n    context = [\"Context of error: \\n\"]\n    if line_of_tex_error < 3:\n        context += tex_source[: line_of_tex_error + 3]\n        context[-4] = \"-> \" + context[-4]\n    elif line_of_tex_error > len(tex_source) - 3:\n        context += tex_source[line_of_tex_error - 1 :]\n        context[1] = \"-> \" + context[1]\n    else:\n        context += tex_source[line_of_tex_error - 3 : line_of_tex_error + 3]\n        context[-4] = \"-> \" + context[-4]\n\n    context = \"\".join(context)\n    logger.error(context)\n\n    for insights in LATEX_ERROR_INSIGHTS:\n        prob, get_insight = insights\n        matching = re.search(\n            prob,\n            \"\".join(tex_compilation_log[error_start_index])[2:],\n        )\n        if matching is not None:\n            for insight in get_insight(matching):\n                logger.info(insight)\n", "path": "manim/utils/tex_file_writing.py"}], "after_files": [{"content": "\"\"\"Interface for writing, compiling, and converting ``.tex`` files.\n\n.. SEEALSO::\n\n    :mod:`.mobject.svg.tex_mobject`\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport hashlib\nimport os\nimport re\nimport unicodedata\nfrom pathlib import Path\n\nfrom .. import config, logger\n\n\ndef tex_hash(expression):\n    id_str = str(expression)\n    hasher = hashlib.sha256()\n    hasher.update(id_str.encode())\n    # Truncating at 16 bytes for cleanliness\n    return hasher.hexdigest()[:16]\n\n\ndef tex_to_svg_file(expression, environment=None, tex_template=None):\n    \"\"\"Takes a tex expression and returns the svg version of the compiled tex\n\n    Parameters\n    ----------\n    expression : :class:`str`\n        String containing the TeX expression to be rendered, e.g. ``\\\\sqrt{2}`` or ``foo``\n    environment : Optional[:class:`str`], optional\n        The string containing the environment in which the expression should be typeset, e.g. ``align*``\n    tex_template : Optional[:class:`~.TexTemplate`], optional\n        Template class used to typesetting. If not set, use default template set via `config[\"tex_template\"]`\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated SVG file.\n    \"\"\"\n    if tex_template is None:\n        tex_template = config[\"tex_template\"]\n    tex_file = generate_tex_file(expression, environment, tex_template)\n    dvi_file = compile_tex(\n        tex_file,\n        tex_template.tex_compiler,\n        tex_template.output_format,\n    )\n    return convert_to_svg(dvi_file, tex_template.output_format)\n\n\ndef generate_tex_file(expression, environment=None, tex_template=None):\n    \"\"\"Takes a tex expression (and an optional tex environment),\n    and returns a fully formed tex file ready for compilation.\n\n    Parameters\n    ----------\n    expression : :class:`str`\n        String containing the TeX expression to be rendered, e.g. ``\\\\sqrt{2}`` or ``foo``\n    environment : Optional[:class:`str`], optional\n        The string containing the environment in which the expression should be typeset, e.g. ``align*``\n    tex_template : Optional[:class:`~.TexTemplate`], optional\n        Template class used to typesetting. If not set, use default template set via `config[\"tex_template\"]`\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated TeX file\n    \"\"\"\n    if tex_template is None:\n        tex_template = config[\"tex_template\"]\n    if environment is not None:\n        output = tex_template.get_texcode_for_expression_in_env(expression, environment)\n    else:\n        output = tex_template.get_texcode_for_expression(expression)\n\n    tex_dir = config.get_dir(\"tex_dir\")\n    if not os.path.exists(tex_dir):\n        os.makedirs(tex_dir)\n\n    result = os.path.join(tex_dir, tex_hash(output)) + \".tex\"\n    if not os.path.exists(result):\n        logger.info(\n            \"Writing %(expression)s to %(path)s\",\n            {\"expression\": expression, \"path\": f\"{result}\"},\n        )\n        with open(result, \"w\", encoding=\"utf-8\") as outfile:\n            outfile.write(output)\n    return result\n\n\ndef tex_compilation_command(tex_compiler, output_format, tex_file, tex_dir):\n    \"\"\"Prepares the tex compilation command with all necessary cli flags\n\n    Parameters\n    ----------\n    tex_compiler : :class:`str`\n        String containing the compiler to be used, e.g. ``pdflatex`` or ``lualatex``\n    output_format : :class:`str`\n        String containing the output format generated by the compiler, e.g. ``.dvi`` or ``.pdf``\n    tex_file : :class:`str`\n        File name of TeX file to be typeset.\n    tex_dir : :class:`str`\n        Path to the directory where compiler output will be stored.\n\n    Returns\n    -------\n    :class:`str`\n        Compilation command according to given parameters\n    \"\"\"\n    if tex_compiler in {\"latex\", \"pdflatex\", \"luatex\", \"lualatex\"}:\n        commands = [\n            tex_compiler,\n            \"-interaction=batchmode\",\n            f'-output-format=\"{output_format[1:]}\"',\n            \"-halt-on-error\",\n            f'-output-directory=\"{tex_dir}\"',\n            f'\"{tex_file}\"',\n            \">\",\n            os.devnull,\n        ]\n    elif tex_compiler == \"xelatex\":\n        if output_format == \".xdv\":\n            outflag = \"-no-pdf\"\n        elif output_format == \".pdf\":\n            outflag = \"\"\n        else:\n            raise ValueError(\"xelatex output is either pdf or xdv\")\n        commands = [\n            \"xelatex\",\n            outflag,\n            \"-interaction=batchmode\",\n            \"-halt-on-error\",\n            f'-output-directory=\"{tex_dir}\"',\n            f'\"{tex_file}\"',\n            \">\",\n            os.devnull,\n        ]\n    else:\n        raise ValueError(f\"Tex compiler {tex_compiler} unknown.\")\n    return \" \".join(commands)\n\n\ndef insight_inputenc_error(matching):\n    code_point = chr(int(matching[1], 16))\n    name = unicodedata.name(code_point)\n    yield f\"TexTemplate does not support character '{name}' (U+{matching[1]}).\"\n    yield \"See the documentation for manim.mobject.svg.tex_mobject for details on using a custom TexTemplate.\"\n\n\ndef insight_package_not_found_error(matching):\n    yield f\"You do not have package {matching[1]} installed.\"\n    yield f\"Install {matching[1]} it using your LaTeX package manager, or check for typos.\"\n\n\ndef compile_tex(tex_file, tex_compiler, output_format):\n    \"\"\"Compiles a tex_file into a .dvi or a .xdv or a .pdf\n\n    Parameters\n    ----------\n    tex_file : :class:`str`\n        File name of TeX file to be typeset.\n    tex_compiler : :class:`str`\n        String containing the compiler to be used, e.g. ``pdflatex`` or ``lualatex``\n    output_format : :class:`str`\n        String containing the output format generated by the compiler, e.g. ``.dvi`` or ``.pdf``\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated output file in desired format (DVI, XDV or PDF).\n    \"\"\"\n    result = tex_file.replace(\".tex\", output_format)\n    result = Path(result).as_posix()\n    tex_file = Path(tex_file).as_posix()\n    tex_dir = Path(config.get_dir(\"tex_dir\")).as_posix()\n    if not os.path.exists(result):\n        command = tex_compilation_command(\n            tex_compiler,\n            output_format,\n            tex_file,\n            tex_dir,\n        )\n        exit_code = os.system(command)\n        if exit_code != 0:\n            log_file = tex_file.replace(\".tex\", \".log\")\n            print_all_tex_errors(log_file, tex_compiler, tex_file)\n            raise ValueError(\n                f\"{tex_compiler} error converting to\"\n                f\" {output_format[1:]}. See log output above or\"\n                f\" the log file: {log_file}\",\n            )\n    return result\n\n\ndef convert_to_svg(dvi_file, extension, page=1):\n    \"\"\"Converts a .dvi, .xdv, or .pdf file into an svg using dvisvgm.\n\n    Parameters\n    ----------\n    dvi_file : :class:`str`\n        File name of the input file to be converted.\n    extension : :class:`str`\n        String containing the file extension and thus indicating the file type, e.g. ``.dvi`` or ``.pdf``\n    page : Optional[:class:`int`], optional\n        Page to be converted if input file is multi-page.\n\n    Returns\n    -------\n    :class:`str`\n        Path to generated SVG file.\n    \"\"\"\n    result = dvi_file.replace(extension, \".svg\")\n    result = Path(result).as_posix()\n    dvi_file = Path(dvi_file).as_posix()\n    if not os.path.exists(result):\n        commands = [\n            \"dvisvgm\",\n            \"--pdf\" if extension == \".pdf\" else \"\",\n            \"-p \" + str(page),\n            f'\"{dvi_file}\"',\n            \"-n\",\n            \"-v 0\",\n            \"-o \" + f'\"{result}\"',\n            \">\",\n            os.devnull,\n        ]\n        os.system(\" \".join(commands))\n\n    # if the file does not exist now, this means conversion failed\n    if not os.path.exists(result):\n        raise ValueError(\n            f\"Your installation does not support converting {extension} files to SVG.\"\n            f\" Consider updating dvisvgm to at least version 2.4.\"\n            f\" If this does not solve the problem, please refer to our troubleshooting guide at:\"\n            f\" https://docs.manim.community/en/stable/installation/troubleshooting.html\",\n        )\n\n    return result\n\n\ndef print_all_tex_errors(log_file, tex_compiler, tex_file):\n    if not Path(log_file).exists():\n        raise RuntimeError(\n            f\"{tex_compiler} failed but did not produce a log file. \"\n            \"Check your LaTeX installation.\",\n        )\n    with open(log_file) as f:\n        tex_compilation_log = f.readlines()\n        error_indices = [\n            index\n            for index, line in enumerate(tex_compilation_log)\n            if line.startswith(\"!\")\n        ]\n        if error_indices:\n            with open(tex_file) as g:\n                tex = g.readlines()\n                for error_index in error_indices:\n                    print_tex_error(tex_compilation_log, error_index, tex)\n\n\nLATEX_ERROR_INSIGHTS = [\n    (\n        r\"inputenc Error: Unicode character (?:.*) \\(U\\+([0-9a-fA-F]+)\\)\",\n        insight_inputenc_error,\n    ),\n    (\n        r\"LaTeX Error: File `(.*?[clsty])' not found\",\n        insight_package_not_found_error,\n    ),\n]\n\n\ndef print_tex_error(tex_compilation_log, error_start_index, tex_source):\n    logger.error(\n        f\"LaTeX compilation error: {tex_compilation_log[error_start_index][2:]}\",\n    )\n\n    # TeX errors eventually contain a line beginning 'l.xxx` where xxx is the line number that caused the compilation\n    # failure. This code finds the next such line after the error current error message\n    line_of_tex_error = (\n        int(\n            [\n                log_line\n                for log_line in tex_compilation_log[error_start_index:]\n                if log_line.startswith(\"l.\")\n            ][0]\n            .split(\" \")[0]\n            .split(\".\")[1],\n        )\n        - 1\n    )\n    # our tex error may be on a line outside our user input because of post-processing\n    if line_of_tex_error >= len(tex_source):\n        return None\n\n    context = [\"Context of error: \\n\"]\n    if line_of_tex_error < 3:\n        context += tex_source[: line_of_tex_error + 3]\n        context[-4] = \"-> \" + context[-4]\n    elif line_of_tex_error > len(tex_source) - 3:\n        context += tex_source[line_of_tex_error - 1 :]\n        context[1] = \"-> \" + context[1]\n    else:\n        context += tex_source[line_of_tex_error - 3 : line_of_tex_error + 3]\n        context[-4] = \"-> \" + context[-4]\n\n    context = \"\".join(context)\n    logger.error(context)\n\n    for insights in LATEX_ERROR_INSIGHTS:\n        prob, get_insight = insights\n        matching = re.search(\n            prob,\n            \"\".join(tex_compilation_log[error_start_index])[2:],\n        )\n        if matching is not None:\n            for insight in get_insight(matching):\n                logger.info(insight)\n", "path": "manim/utils/tex_file_writing.py"}]}