Make caching on disk a decorator, and update implementations for Tex and Text mobjects

This commit is contained in:
Grant Sanderson 2024-12-05 10:09:15 -06:00
parent 89ddfadf6b
commit 43821ab2ba
6 changed files with 104 additions and 74 deletions

View file

@ -77,11 +77,7 @@ class SingleStringTex(SVGMobject):
) )
def get_svg_string_by_content(self, content: str) -> str: def get_svg_string_by_content(self, content: str) -> str:
return get_cached_value( return latex_to_svg(content, self.template, self.additional_preamble)
key=hash_string(str((content, self.template, self.additional_preamble))),
value_func=lambda: latex_to_svg(content, self.template, self.additional_preamble),
message=f"Writing {self.tex_string}..."
)
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)

View file

@ -6,7 +6,6 @@ from pathlib import Path
from manimlib.mobject.svg.string_mobject import StringMobject from manimlib.mobject.svg.string_mobject import StringMobject
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.cache import get_cached_value
from manimlib.utils.color import color_to_hex from manimlib.utils.color import color_to_hex
from manimlib.utils.color import hex_to_int from manimlib.utils.color import hex_to_int
from manimlib.utils.tex_file_writing import latex_to_svg from manimlib.utils.tex_file_writing import latex_to_svg
@ -86,11 +85,7 @@ class Tex(StringMobject):
) )
def get_svg_string_by_content(self, content: str) -> str: def get_svg_string_by_content(self, content: str) -> str:
return get_cached_value( return latex_to_svg(content, self.template, self.additional_preamble, short_tex=self.tex_string)
key=hash_string(str((content, self.template, self.additional_preamble))),
value_func=lambda: latex_to_svg(content, self.template, self.additional_preamble),
message=f"Writing {self.tex_string}..."
)
def _handle_scale_side_effects(self, scale_factor: float) -> Self: def _handle_scale_side_effects(self, scale_factor: float) -> Self:
self.font_size *= scale_factor self.font_size *= scale_factor

View file

@ -16,7 +16,7 @@ from manimlib.constants import DEFAULT_PIXEL_WIDTH, FRAME_WIDTH
from manimlib.constants import NORMAL from manimlib.constants import NORMAL
from manimlib.logger import log from manimlib.logger import log
from manimlib.mobject.svg.string_mobject import StringMobject from manimlib.mobject.svg.string_mobject import StringMobject
from manimlib.utils.cache import get_cached_value from manimlib.utils.cache import cache_on_disk
from manimlib.utils.color import color_to_hex from manimlib.utils.color import color_to_hex
from manimlib.utils.color import int_to_hex from manimlib.utils.color import int_to_hex
from manimlib.utils.customization import get_customization from manimlib.utils.customization import get_customization
@ -51,6 +51,57 @@ class _Alignment:
self.value = _Alignment.VAL_DICT[s.upper()] self.value = _Alignment.VAL_DICT[s.upper()]
@cache_on_disk
def markup_to_svg_string(
markup_str: str,
justify: bool = False,
indent: float = 0,
alignment: str = "",
line_width: float | None = None,
) -> str:
validate_error = manimpango.MarkupUtils.validate(markup_str)
if validate_error:
raise ValueError(
f"Invalid markup string \"{markup_str}\"\n" + \
f"{validate_error}"
)
# `manimpango` is under construction,
# so the following code is intended to suit its interface
alignment = _Alignment(alignment)
if line_width is None:
pango_width = -1
else:
pango_width = line_width / FRAME_WIDTH * DEFAULT_PIXEL_WIDTH
# Write the result to a temporary svg file, and return it's contents.
# TODO, better would be to have this not write to file at all
with tempfile.NamedTemporaryFile(suffix='.svg', mode='r+') as tmp:
manimpango.MarkupUtils.text2svg(
text=markup_str,
font="", # Already handled
slant="NORMAL", # Already handled
weight="NORMAL", # Already handled
size=1, # Already handled
_=0, # Empty parameter
disable_liga=False,
file_name=tmp.name,
START_X=0,
START_Y=0,
width=DEFAULT_CANVAS_WIDTH,
height=DEFAULT_CANVAS_HEIGHT,
justify=justify,
indent=indent,
line_spacing=None, # Already handled
alignment=alignment,
pango_width=pango_width
)
# Read the contents
tmp.seek(0)
return tmp.read()
class MarkupText(StringMobject): class MarkupText(StringMobject):
# See https://docs.gtk.org/Pango/pango_markup.html # See https://docs.gtk.org/Pango/pango_markup.html
MARKUP_TAGS = { MARKUP_TAGS = {
@ -172,59 +223,13 @@ class MarkupText(StringMobject):
) )
def get_svg_string_by_content(self, content: str) -> str: def get_svg_string_by_content(self, content: str) -> str:
key = hash_string(str(( self.content = content
return markup_to_svg_string(
content, content,
self.justify, justify=self.justify,
self.indent, indent=self.indent,
self.alignment, alignment=self.alignment,
self.line_width line_width=self.line_width
)))
return get_cached_value(key, lambda: self.markup_to_svg_string(content))
def markup_to_svg_string(self, markup_str: str) -> str:
self.validate_markup_string(markup_str)
# `manimpango` is under construction,
# so the following code is intended to suit its interface
alignment = _Alignment(self.alignment)
if self.line_width is None:
pango_width = -1
else:
pango_width = self.line_width / FRAME_WIDTH * DEFAULT_PIXEL_WIDTH
with tempfile.NamedTemporaryFile(suffix='.svg', mode='r+') as tmp:
manimpango.MarkupUtils.text2svg(
text=markup_str,
font="", # Already handled
slant="NORMAL", # Already handled
weight="NORMAL", # Already handled
size=1, # Already handled
_=0, # Empty parameter
disable_liga=False,
file_name=tmp.name,
START_X=0,
START_Y=0,
width=DEFAULT_CANVAS_WIDTH,
height=DEFAULT_CANVAS_HEIGHT,
justify=self.justify,
indent=self.indent,
line_spacing=None, # Already handled
alignment=alignment,
pango_width=pango_width
)
# Read the contents
tmp.seek(0)
return tmp.read()
@staticmethod
def validate_markup_string(markup_str: str) -> None:
validate_error = manimpango.MarkupUtils.validate(markup_str)
if not validate_error:
return
raise ValueError(
f"Invalid markup string \"{markup_str}\"\n" + \
f"{validate_error}"
) )
# Toolkits # Toolkits

View file

@ -1,22 +1,38 @@
from __future__ import annotations
import os import os
from diskcache import Cache from diskcache import Cache
from contextlib import contextmanager from contextlib import contextmanager
from functools import wraps
from manimlib.utils.directories import get_cache_dir from manimlib.utils.directories import get_cache_dir
from manimlib.utils.simple_functions import hash_string
from typing import TYPE_CHECKING
if TYPE_CHECKING:
T = TypeVar('T')
CACHE_SIZE = 1e9 # 1 Gig CACHE_SIZE = 1e9 # 1 Gig
_cache = Cache(get_cache_dir(), size_limit=CACHE_SIZE)
def get_cached_value(key, value_func, message=""): def cache_on_disk(func: Callable[..., T]) -> Callable[..., T]:
cache = Cache(get_cache_dir(), size_limit=CACHE_SIZE) @wraps(func)
def wrapper(*args, **kwargs):
key = hash_string("".join(map(str, [func.__name__, args, kwargs])))
value = _cache.get(key)
if value is None:
# print(f"Executing {func.__name__}({args[0]}, ...)")
value = func(*args, **kwargs)
_cache.set(key, value)
return value
return wrapper
value = cache.get(key)
if value is None: def clear_cache():
with display_during_execution(message): _cache.clear()
value = value_func()
cache.set(key, value)
return value
@contextmanager @contextmanager

View file

@ -11,7 +11,9 @@ CUSTOMIZATION = {}
def get_customization(): def get_customization():
if not CUSTOMIZATION: if not CUSTOMIZATION:
print(CUSTOMIZATION)
CUSTOMIZATION.update(get_custom_config()) CUSTOMIZATION.update(get_custom_config())
print(CUSTOMIZATION)
directories = CUSTOMIZATION["directories"] directories = CUSTOMIZATION["directories"]
# Unless user has specified otherwise, use the system default temp # Unless user has specified otherwise, use the system default temp
# directory for storing tex files, mobject_data, etc. # directory for storing tex files, mobject_data, etc.

View file

@ -8,6 +8,7 @@ import subprocess
from pathlib import Path from pathlib import Path
import tempfile import tempfile
from manimlib.utils.cache import cache_on_disk
from manimlib.config import get_custom_config from manimlib.config import get_custom_config
from manimlib.config import get_manim_dir from manimlib.config import get_manim_dir
from manimlib.logger import log from manimlib.logger import log
@ -62,10 +63,13 @@ def get_full_tex(content: str, preamble: str = ""):
)) + "\n" )) + "\n"
@cache_on_disk
def latex_to_svg( def latex_to_svg(
latex: str, latex: str,
template: str = "", template: str = "",
additional_preamble: str = "" additional_preamble: str = "",
short_tex: str = "",
show_message_during_execution: bool = True,
) -> str: ) -> str:
"""Convert LaTeX string to SVG string. """Convert LaTeX string to SVG string.
@ -81,6 +85,13 @@ def latex_to_svg(
LatexError: If LaTeX compilation fails LatexError: If LaTeX compilation fails
NotImplementedError: If compiler is not supported NotImplementedError: If compiler is not supported
""" """
if show_message_during_execution:
max_message_len = 80
message = f"Writing {short_tex or latex}"
if len(message) > max_message_len:
message = message[:max_message_len - 3] + "..."
print(message, end="\r")
tex_config = get_tex_config() tex_config = get_tex_config()
if template and template != tex_config["template"]: if template and template != tex_config["template"]:
tex_config = get_tex_template_config(template) tex_config = get_tex_template_config(template)
@ -147,7 +158,12 @@ def latex_to_svg(
) )
# Return SVG string # Return SVG string
return process.stdout.decode('utf-8') result = process.stdout.decode('utf-8')
if show_message_during_execution:
print(" " * len(message), end="\r")
return result
class LatexError(Exception): class LatexError(Exception):