mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
Construct TexTemplate class to convert tex to svg
This commit is contained in:
parent
edca4a93fa
commit
f0984487ea
10 changed files with 177 additions and 228 deletions
|
@ -64,6 +64,56 @@ JOINT_TYPE_MAP = {
|
||||||
"miter": 3,
|
"miter": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Related to Tex
|
||||||
|
PRESET_PREAMBLE = {
|
||||||
|
"default": (
|
||||||
|
"\\usepackage[english]{babel}",
|
||||||
|
"\\usepackage[utf8]{inputenc}",
|
||||||
|
"\\usepackage[T1]{fontenc}",
|
||||||
|
"\\usepackage{amsmath}",
|
||||||
|
"\\usepackage{amssymb}",
|
||||||
|
"\\usepackage{dsfont}",
|
||||||
|
"\\usepackage{setspace}",
|
||||||
|
"\\usepackage{tipa}",
|
||||||
|
"\\usepackage{relsize}",
|
||||||
|
"\\usepackage{textcomp}",
|
||||||
|
"\\usepackage{mathrsfs}",
|
||||||
|
"\\usepackage{calligra}",
|
||||||
|
"\\usepackage{wasysym}",
|
||||||
|
"\\usepackage{ragged2e}",
|
||||||
|
"\\usepackage{physics}",
|
||||||
|
"\\usepackage{xcolor}",
|
||||||
|
"\\usepackage{microtype}",
|
||||||
|
"\\usepackage{pifont}",
|
||||||
|
"\\DisableLigatures{encoding = *, family = * }",
|
||||||
|
"\\linespread{1}",
|
||||||
|
),
|
||||||
|
"ctex": (
|
||||||
|
"\\usepackage[UTF8]{ctex}",
|
||||||
|
"\\usepackage[english]{babel}",
|
||||||
|
"\\usepackage{amsmath}",
|
||||||
|
"\\usepackage{amssymb}",
|
||||||
|
"\\usepackage{dsfont}",
|
||||||
|
"\\usepackage{setspace}",
|
||||||
|
"\\usepackage{tipa}",
|
||||||
|
"\\usepackage{relsize}",
|
||||||
|
"\\usepackage{textcomp}",
|
||||||
|
"\\usepackage{mathrsfs}",
|
||||||
|
"\\usepackage{calligra}",
|
||||||
|
"\\usepackage{wasysym}",
|
||||||
|
"\\usepackage{ragged2e}",
|
||||||
|
"\\usepackage{physics}",
|
||||||
|
"\\usepackage{xcolor}",
|
||||||
|
"\\usepackage{microtype}",
|
||||||
|
"\\linespread{1}",
|
||||||
|
),
|
||||||
|
"minimized": (
|
||||||
|
"\\usepackage{amsmath}",
|
||||||
|
"\\usepackage{amssymb}",
|
||||||
|
"\\usepackage{xcolor}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
# Related to Text
|
# Related to Text
|
||||||
NORMAL = "NORMAL"
|
NORMAL = "NORMAL"
|
||||||
ITALIC = "ITALIC"
|
ITALIC = "ITALIC"
|
||||||
|
|
|
@ -18,13 +18,12 @@ directories:
|
||||||
temporary_storage: ""
|
temporary_storage: ""
|
||||||
tex:
|
tex:
|
||||||
executable: "latex"
|
executable: "latex"
|
||||||
template_file: "tex_template.tex"
|
intermediate_filetype: ".dvi"
|
||||||
intermediate_filetype: "dvi"
|
preamble: "default"
|
||||||
text_to_replace: "[tex_expression]"
|
|
||||||
# For ctex, use the following configuration
|
# For ctex, use the following configuration
|
||||||
# executable: "xelatex -no-pdf"
|
# executable: "xelatex -no-pdf"
|
||||||
# template_file: "ctex_template.tex"
|
# intermediate_filetype: ".xdv"
|
||||||
# intermediate_filetype: "xdv"
|
# preamble: "ctex"
|
||||||
universal_import_line: "from manimlib import *"
|
universal_import_line: "from manimlib import *"
|
||||||
style:
|
style:
|
||||||
font: "Consolas"
|
font: "Consolas"
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from manimlib.mobject.svg.string_mobject import StringMobject
|
from manimlib.mobject.svg.string_mobject import StringMobject
|
||||||
from manimlib.utils.tex_file_writing import display_during_execution
|
from manimlib.utils.tex_file_writing import TexTemplate
|
||||||
from manimlib.utils.tex_file_writing import get_tex_config
|
|
||||||
from manimlib.utils.tex_file_writing import tex_to_svg_file
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -37,6 +35,7 @@ class MTex(StringMobject):
|
||||||
"alignment": "\\centering",
|
"alignment": "\\centering",
|
||||||
"tex_environment": "align*",
|
"tex_environment": "align*",
|
||||||
"tex_to_color_map": {},
|
"tex_to_color_map": {},
|
||||||
|
"tex_template": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, tex_string: str, **kwargs):
|
def __init__(self, tex_string: str, **kwargs):
|
||||||
|
@ -60,17 +59,13 @@ class MTex(StringMobject):
|
||||||
self.tex_string,
|
self.tex_string,
|
||||||
self.alignment,
|
self.alignment,
|
||||||
self.tex_environment,
|
self.tex_environment,
|
||||||
self.tex_to_color_map
|
self.tex_to_color_map,
|
||||||
|
self.tex_template
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_file_path_by_content(self, content: str) -> str:
|
def get_file_path_by_content(self, content: str) -> str:
|
||||||
tex_config = get_tex_config()
|
tex_template = self.tex_template or TexTemplate()
|
||||||
full_tex = tex_config["tex_body"].replace(
|
file_path = tex_template.get_svg_file_path(content)
|
||||||
tex_config["text_to_replace"],
|
|
||||||
content
|
|
||||||
)
|
|
||||||
with display_during_execution(f"Writing \"{self.string}\""):
|
|
||||||
file_path = tex_to_svg_file(full_tex)
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
# Parsing
|
# Parsing
|
||||||
|
|
|
@ -12,9 +12,7 @@ from manimlib.mobject.geometry import Line
|
||||||
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
||||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||||
from manimlib.utils.config_ops import digest_config
|
from manimlib.utils.config_ops import digest_config
|
||||||
from manimlib.utils.tex_file_writing import display_during_execution
|
from manimlib.utils.tex_file_writing import TexTemplate
|
||||||
from manimlib.utils.tex_file_writing import get_tex_config
|
|
||||||
from manimlib.utils.tex_file_writing import tex_to_svg_file
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -44,6 +42,7 @@ class SingleStringTex(SVGMobject):
|
||||||
"alignment": "\\centering",
|
"alignment": "\\centering",
|
||||||
"math_mode": True,
|
"math_mode": True,
|
||||||
"organize_left_to_right": False,
|
"organize_left_to_right": False,
|
||||||
|
"tex_template": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, tex_string: str, **kwargs):
|
def __init__(self, tex_string: str, **kwargs):
|
||||||
|
@ -64,27 +63,21 @@ class SingleStringTex(SVGMobject):
|
||||||
self.path_string_config,
|
self.path_string_config,
|
||||||
self.tex_string,
|
self.tex_string,
|
||||||
self.alignment,
|
self.alignment,
|
||||||
self.math_mode
|
self.math_mode,
|
||||||
|
self.tex_template
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_file_path(self) -> str:
|
def get_file_path(self) -> str:
|
||||||
full_tex = self.get_tex_file_body(self.tex_string)
|
content = self.get_tex_file_body(self.tex_string)
|
||||||
with display_during_execution(f"Writing \"{self.tex_string}\""):
|
tex_template = self.tex_template or TexTemplate()
|
||||||
file_path = tex_to_svg_file(full_tex)
|
file_path = tex_template.get_svg_file_path(content)
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
def get_tex_file_body(self, tex_string: str) -> str:
|
def get_tex_file_body(self, tex_string: str) -> str:
|
||||||
new_tex = self.get_modified_expression(tex_string)
|
new_tex = self.get_modified_expression(tex_string)
|
||||||
if self.math_mode:
|
if self.math_mode:
|
||||||
new_tex = "\\begin{align*}\n" + new_tex + "\n\\end{align*}"
|
new_tex = "\\begin{align*}\n" + new_tex + "\n\\end{align*}"
|
||||||
|
return self.alignment + "\n" + new_tex
|
||||||
new_tex = self.alignment + "\n" + new_tex
|
|
||||||
|
|
||||||
tex_config = get_tex_config()
|
|
||||||
return tex_config["tex_body"].replace(
|
|
||||||
tex_config["text_to_replace"],
|
|
||||||
new_tex
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_modified_expression(self, tex_string: str) -> str:
|
def get_modified_expression(self, tex_string: str) -> str:
|
||||||
return self.modify_special_strings(tex_string.strip())
|
return self.modify_special_strings(tex_string.strip())
|
||||||
|
|
|
@ -18,7 +18,7 @@ from manimlib.utils.config_ops import digest_config
|
||||||
from manimlib.utils.customization import get_customization
|
from manimlib.utils.customization import get_customization
|
||||||
from manimlib.utils.directories import get_downloads_dir
|
from manimlib.utils.directories import get_downloads_dir
|
||||||
from manimlib.utils.directories import get_text_dir
|
from manimlib.utils.directories import get_text_dir
|
||||||
from manimlib.utils.tex_file_writing import tex_hash
|
from manimlib.utils.simple_functions import hash_string
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ class MarkupText(StringMobject):
|
||||||
self.line_width
|
self.line_width
|
||||||
))
|
))
|
||||||
svg_file = os.path.join(
|
svg_file = os.path.join(
|
||||||
get_text_dir(), tex_hash(hash_content) + ".svg"
|
get_text_dir(), hash_string(hash_content) + ".svg"
|
||||||
)
|
)
|
||||||
if not os.path.exists(svg_file):
|
if not os.path.exists(svg_file):
|
||||||
self.markup_to_svg(content, svg_file)
|
self.markup_to_svg(content, svg_file)
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
\documentclass[preview]{standalone}
|
|
||||||
\usepackage[UTF8]{ctex}
|
|
||||||
|
|
||||||
\usepackage[english]{babel}
|
|
||||||
\usepackage{amsmath}
|
|
||||||
\usepackage{amssymb}
|
|
||||||
\usepackage{dsfont}
|
|
||||||
\usepackage{setspace}
|
|
||||||
\usepackage{tipa}
|
|
||||||
\usepackage{relsize}
|
|
||||||
\usepackage{textcomp}
|
|
||||||
\usepackage{mathrsfs}
|
|
||||||
\usepackage{calligra}
|
|
||||||
\usepackage{wasysym}
|
|
||||||
\usepackage{ragged2e}
|
|
||||||
\usepackage{physics}
|
|
||||||
\usepackage{xcolor}
|
|
||||||
\usepackage{microtype}
|
|
||||||
\linespread{1}
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
|
|
||||||
[tex_expression]
|
|
||||||
|
|
||||||
\end{document}
|
|
|
@ -1,28 +0,0 @@
|
||||||
\documentclass[preview]{standalone}
|
|
||||||
|
|
||||||
\usepackage[english]{babel}
|
|
||||||
\usepackage[utf8]{inputenc}
|
|
||||||
\usepackage[T1]{fontenc}
|
|
||||||
\usepackage{amsmath}
|
|
||||||
\usepackage{amssymb}
|
|
||||||
\usepackage{dsfont}
|
|
||||||
\usepackage{setspace}
|
|
||||||
\usepackage{tipa}
|
|
||||||
\usepackage{relsize}
|
|
||||||
\usepackage{textcomp}
|
|
||||||
\usepackage{mathrsfs}
|
|
||||||
\usepackage{calligra}
|
|
||||||
\usepackage{wasysym}
|
|
||||||
\usepackage{ragged2e}
|
|
||||||
\usepackage{physics}
|
|
||||||
\usepackage{xcolor}
|
|
||||||
\usepackage{microtype}
|
|
||||||
\usepackage{pifont}
|
|
||||||
\DisableLigatures{encoding = *, family = * }
|
|
||||||
\linespread{1}
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
|
|
||||||
[tex_expression]
|
|
||||||
|
|
||||||
\end{document}
|
|
|
@ -9,7 +9,7 @@ from rich import box
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.prompt import Confirm
|
from rich.prompt import Confirm
|
||||||
from rich.prompt import Prompt
|
from rich.prompt import Prompt
|
||||||
from rich.rule import Rule
|
from rich.rule import Rule
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
@ -44,9 +44,8 @@ def init_customization() -> None:
|
||||||
},
|
},
|
||||||
"tex": {
|
"tex": {
|
||||||
"executable": "",
|
"executable": "",
|
||||||
"template_file": "",
|
|
||||||
"intermediate_filetype": "",
|
"intermediate_filetype": "",
|
||||||
"text_to_replace": "[tex_expression]",
|
"preamble": "",
|
||||||
},
|
},
|
||||||
"universal_import_line": "from manimlib import *",
|
"universal_import_line": "from manimlib import *",
|
||||||
"style": {
|
"style": {
|
||||||
|
@ -83,7 +82,7 @@ def init_customization() -> None:
|
||||||
# print("Initialize configuration")
|
# print("Initialize configuration")
|
||||||
try:
|
try:
|
||||||
scope = Prompt.ask(
|
scope = Prompt.ask(
|
||||||
" Select the scope of the configuration",
|
" Select the scope of the configuration",
|
||||||
choices=["global", "local"],
|
choices=["global", "local"],
|
||||||
default="local"
|
default="local"
|
||||||
)
|
)
|
||||||
|
@ -129,13 +128,13 @@ def init_customization() -> None:
|
||||||
)
|
)
|
||||||
if tex == "latex":
|
if tex == "latex":
|
||||||
tex_config["executable"] = "latex"
|
tex_config["executable"] = "latex"
|
||||||
tex_config["template_file"] = "tex_template.tex"
|
tex_config["intermediate_filetype"] = ".dvi"
|
||||||
tex_config["intermediate_filetype"] = "dvi"
|
tex_config["preamble"] = "default"
|
||||||
else:
|
else:
|
||||||
tex_config["executable"] = "xelatex -no-pdf"
|
tex_config["executable"] = "xelatex -no-pdf"
|
||||||
tex_config["template_file"] = "ctex_template.tex"
|
tex_config["intermediate_filetype"] = ".xdv"
|
||||||
tex_config["intermediate_filetype"] = "xdv"
|
tex_config["preamble"] = "ctex"
|
||||||
|
|
||||||
console.print("[bold]Styles:[/bold]")
|
console.print("[bold]Styles:[/bold]")
|
||||||
configuration["style"]["background_color"] = Prompt.ask(
|
configuration["style"]["background_color"] = Prompt.ask(
|
||||||
" Which [bold]background color[/bold] do you want [italic](hex code)",
|
" Which [bold]background color[/bold] do you want [italic](hex code)",
|
||||||
|
@ -172,7 +171,7 @@ def init_customization() -> None:
|
||||||
file_name = os.path.join(os.getcwd(), "custom_config.yml")
|
file_name = os.path.join(os.getcwd(), "custom_config.yml")
|
||||||
with open(file_name, "w", encoding="utf-8") as f:
|
with open(file_name, "w", encoding="utf-8") as f:
|
||||||
yaml.dump(configuration, f)
|
yaml.dump(configuration, f)
|
||||||
|
|
||||||
console.print(f"\n:rocket: You have successfully set up a {scope} configuration file!")
|
console.print(f"\n:rocket: You have successfully set up a {scope} configuration file!")
|
||||||
console.print(f"You can manually modify it in: [cyan]`{file_name}`[/cyan]")
|
console.print(f"You can manually modify it in: [cyan]`{file_name}`[/cyan]")
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
import hashlib
|
||||||
import inspect
|
import inspect
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
@ -76,3 +77,9 @@ def binary_search(function,
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
return mh
|
return mh
|
||||||
|
|
||||||
|
|
||||||
|
def hash_string(string):
|
||||||
|
# Truncating at 16 bytes for cleanliness
|
||||||
|
hasher = hashlib.sha256(string.encode())
|
||||||
|
return hasher.hexdigest()[:16]
|
||||||
|
|
|
@ -1,143 +1,102 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from contextlib import contextmanager
|
|
||||||
import hashlib
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import re
|
||||||
|
|
||||||
from manimlib.config import get_custom_config
|
from manimlib.config import get_custom_config
|
||||||
from manimlib.config import get_manim_dir
|
from manimlib.constants import PRESET_PREAMBLE
|
||||||
from manimlib.logger import log
|
from manimlib.logger import log
|
||||||
from manimlib.utils.directories import get_tex_dir
|
from manimlib.utils.directories import get_tex_dir
|
||||||
|
from manimlib.utils.simple_functions import hash_string
|
||||||
|
|
||||||
SAVED_TEX_CONFIG = {}
|
|
||||||
|
|
||||||
|
|
||||||
def get_tex_config() -> dict[str, str]:
|
|
||||||
"""
|
|
||||||
Returns a dict which should look something like this:
|
|
||||||
{
|
|
||||||
"executable": "latex",
|
|
||||||
"template_file": "tex_template.tex",
|
|
||||||
"intermediate_filetype": "dvi",
|
|
||||||
"text_to_replace": "YourTextHere",
|
|
||||||
"tex_body": "..."
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
# Only load once, then save thereafter
|
|
||||||
if not SAVED_TEX_CONFIG:
|
|
||||||
custom_config = get_custom_config()
|
|
||||||
SAVED_TEX_CONFIG.update(custom_config["tex"])
|
|
||||||
# Read in template file
|
|
||||||
template_filename = os.path.join(
|
|
||||||
get_manim_dir(), "manimlib", "tex_templates",
|
|
||||||
SAVED_TEX_CONFIG["template_file"],
|
|
||||||
)
|
|
||||||
with open(template_filename, "r", encoding="utf-8") as file:
|
|
||||||
SAVED_TEX_CONFIG["tex_body"] = file.read()
|
|
||||||
return SAVED_TEX_CONFIG
|
|
||||||
|
|
||||||
|
|
||||||
def tex_hash(tex_file_content: str) -> int:
|
|
||||||
# Truncating at 16 bytes for cleanliness
|
|
||||||
hasher = hashlib.sha256(tex_file_content.encode())
|
|
||||||
return hasher.hexdigest()[:16]
|
|
||||||
|
|
||||||
|
|
||||||
def tex_to_svg_file(tex_file_content: str) -> str:
|
|
||||||
svg_file = os.path.join(
|
|
||||||
get_tex_dir(), tex_hash(tex_file_content) + ".svg"
|
|
||||||
)
|
|
||||||
if not os.path.exists(svg_file):
|
|
||||||
# If svg doesn't exist, create it
|
|
||||||
tex_to_svg(tex_file_content, svg_file)
|
|
||||||
return svg_file
|
|
||||||
|
|
||||||
|
|
||||||
def tex_to_svg(tex_file_content: str, svg_file: str) -> str:
|
|
||||||
tex_file = svg_file.replace(".svg", ".tex")
|
|
||||||
with open(tex_file, "w", encoding="utf-8") as outfile:
|
|
||||||
outfile.write(tex_file_content)
|
|
||||||
svg_file = dvi_to_svg(tex_to_dvi(tex_file))
|
|
||||||
|
|
||||||
# Cleanup superfluous documents
|
|
||||||
tex_dir, name = os.path.split(svg_file)
|
|
||||||
stem, end = name.split(".")
|
|
||||||
for file in filter(lambda s: s.startswith(stem), os.listdir(tex_dir)):
|
|
||||||
if not file.endswith(end):
|
|
||||||
os.remove(os.path.join(tex_dir, file))
|
|
||||||
|
|
||||||
return svg_file
|
|
||||||
|
|
||||||
|
|
||||||
def tex_to_dvi(tex_file: str) -> str:
|
|
||||||
tex_config = get_tex_config()
|
|
||||||
program = tex_config["executable"]
|
|
||||||
file_type = tex_config["intermediate_filetype"]
|
|
||||||
result = tex_file.replace(".tex", "." + file_type)
|
|
||||||
if not os.path.exists(result):
|
|
||||||
commands = [
|
|
||||||
program,
|
|
||||||
"-interaction=batchmode",
|
|
||||||
"-halt-on-error",
|
|
||||||
f"-output-directory=\"{os.path.dirname(tex_file)}\"",
|
|
||||||
f"\"{tex_file}\"",
|
|
||||||
">",
|
|
||||||
os.devnull
|
|
||||||
]
|
|
||||||
exit_code = os.system(" ".join(commands))
|
|
||||||
if exit_code != 0:
|
|
||||||
log_file = tex_file.replace(".tex", ".log")
|
|
||||||
log.error("LaTeX Error! Not a worry, it happens to the best of us.")
|
|
||||||
with open(log_file, "r", encoding="utf-8") as file:
|
|
||||||
for line in file.readlines():
|
|
||||||
if line.startswith("!"):
|
|
||||||
log.debug(f"The error could be: `{line[2:-1]}`")
|
|
||||||
raise LatexError()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def dvi_to_svg(dvi_file: str) -> str:
|
|
||||||
"""
|
|
||||||
Converts a dvi, which potentially has multiple slides, into a
|
|
||||||
directory full of enumerated pngs corresponding with these slides.
|
|
||||||
Returns a list of PIL Image objects for these images sorted as they
|
|
||||||
where in the dvi
|
|
||||||
"""
|
|
||||||
file_type = get_tex_config()["intermediate_filetype"]
|
|
||||||
result = dvi_file.replace("." + file_type, ".svg")
|
|
||||||
if not os.path.exists(result):
|
|
||||||
commands = [
|
|
||||||
"dvisvgm",
|
|
||||||
"\"{}\"".format(dvi_file),
|
|
||||||
"-n",
|
|
||||||
"-v",
|
|
||||||
"0",
|
|
||||||
"-o",
|
|
||||||
"\"{}\"".format(result),
|
|
||||||
">",
|
|
||||||
os.devnull
|
|
||||||
]
|
|
||||||
os.system(" ".join(commands))
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# TODO, perhaps this should live elsewhere
|
|
||||||
@contextmanager
|
|
||||||
def display_during_execution(message: str) -> None:
|
|
||||||
# Only show top line
|
|
||||||
to_print = message.split("\n")[0]
|
|
||||||
max_characters = os.get_terminal_size().columns - 1
|
|
||||||
if len(to_print) > max_characters:
|
|
||||||
to_print = to_print[:max_characters - 3] + "..."
|
|
||||||
try:
|
|
||||||
print(to_print, end="\r")
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
print(" " * len(to_print), end="\r")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LatexError(Exception):
|
class LatexError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TexTemplate:
|
||||||
|
def __init__(self, preamble_type: str | None = None):
|
||||||
|
tex_config = get_custom_config()["tex"]
|
||||||
|
self.executable = tex_config["executable"]
|
||||||
|
self.dvi_ext = tex_config["intermediate_filetype"]
|
||||||
|
if preamble_type is None:
|
||||||
|
preamble_type = tex_config["preamble"]
|
||||||
|
self.preamble = list(PRESET_PREAMBLE.get(
|
||||||
|
preamble_type, PRESET_PREAMBLE["default"]
|
||||||
|
))
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.get_tex_file_content(""))
|
||||||
|
|
||||||
|
def get_tex_file_content(self, content: str) -> str:
|
||||||
|
return "\n\n".join((
|
||||||
|
"\\documentclass[preview]{standalone}",
|
||||||
|
"\n".join(self.preamble),
|
||||||
|
"\\begin{document}",
|
||||||
|
content,
|
||||||
|
"\\end{document}"
|
||||||
|
)) + "\n"
|
||||||
|
|
||||||
|
def get_svg_file_path(self, content: str) -> str:
|
||||||
|
full_tex = self.get_tex_file_content(content)
|
||||||
|
hash_code = hash_string(full_tex)
|
||||||
|
tex_dir = get_tex_dir()
|
||||||
|
root = os.path.join(tex_dir, hash_code)
|
||||||
|
svg_file_path = root + ".svg"
|
||||||
|
if os.path.exists(svg_file_path):
|
||||||
|
return svg_file_path
|
||||||
|
|
||||||
|
# If svg doesn't exist, create it
|
||||||
|
replaced_content = content.replace("\n", " ")
|
||||||
|
displayed_msg = f"Writing \"{replaced_content}\""
|
||||||
|
max_characters = os.get_terminal_size().columns - 1
|
||||||
|
if len(displayed_msg) > max_characters:
|
||||||
|
displayed_msg = displayed_msg[:max_characters - 3] + "..."
|
||||||
|
print(displayed_msg, end="\r")
|
||||||
|
|
||||||
|
with open(root + ".tex", "w", encoding="utf-8") as tex_file:
|
||||||
|
tex_file.write(full_tex)
|
||||||
|
|
||||||
|
# tex to dvi
|
||||||
|
if os.system(" ".join((
|
||||||
|
self.executable,
|
||||||
|
"-interaction=batchmode",
|
||||||
|
"-halt-on-error",
|
||||||
|
f"-output-directory=\"{tex_dir}\"",
|
||||||
|
f"\"{root}.tex\"",
|
||||||
|
">",
|
||||||
|
os.devnull
|
||||||
|
))):
|
||||||
|
log.error("LaTeX Error! Not a worry, it happens to the best of us.")
|
||||||
|
with open(root + ".log", "r", encoding="utf-8") as log_file:
|
||||||
|
error_match_obj = re.search(r"(?<=\n! ).*", log_file.read())
|
||||||
|
if error_match_obj:
|
||||||
|
log.debug("The error could be: `%s`", error_match_obj.group())
|
||||||
|
raise LatexError()
|
||||||
|
|
||||||
|
# dvi to svg
|
||||||
|
os.system(" ".join((
|
||||||
|
"dvisvgm",
|
||||||
|
f"\"{root}{self.dvi_ext}\"",
|
||||||
|
"-n",
|
||||||
|
"-v",
|
||||||
|
"0",
|
||||||
|
"-o",
|
||||||
|
f"\"{svg_file_path}\"",
|
||||||
|
">",
|
||||||
|
os.devnull
|
||||||
|
)))
|
||||||
|
|
||||||
|
# Cleanup superfluous documents
|
||||||
|
for ext in (".tex", self.dvi_ext, ".log", ".aux"):
|
||||||
|
try:
|
||||||
|
os.remove(root + ext)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(" " * len(displayed_msg), end="\r")
|
||||||
|
return svg_file_path
|
||||||
|
|
||||||
|
def add_preamble(self, *preamble_strs: str):
|
||||||
|
self.preamble.extend(preamble_strs)
|
||||||
|
return self
|
||||||
|
|
Loading…
Add table
Reference in a new issue