diff --git a/docs/source/guides/using_text.rst b/docs/source/guides/using_text.rst index a71b29ec81..739510621c 100644 --- a/docs/source/guides/using_text.rst +++ b/docs/source/guides/using_text.rst @@ -424,7 +424,7 @@ may be expected. To color only ``x`` yellow, we have to do the following: class CorrectLaTeXSubstringColoring(Scene): def construct(self): equation = MathTex( - r"e^x = x^0 + x^1 + \frac{1}{2} x^2 + \frac{1}{6} x^3 + \cdots + \frac{1}{n!} x^n + \cdots", + r"e^{x} = x^0 + x^1 + \frac{1}{2} x^2 + \frac{1}{6} x^3 + \cdots + \frac{1}{n!} x^n + \cdots", substrings_to_isolate="x" ) equation.set_color_by_tex("x", YELLOW) @@ -434,6 +434,8 @@ By setting ``substrings_to_isolate`` to ``x``, we split up the :class:`~.MathTex` into substrings automatically and isolate the ``x`` components into individual substrings. Only then can :meth:`~.set_color_by_tex` be used to achieve the desired result. +If one of the ``substrings_to_isolate`` is in a sub or superscript, it needs +to be enclosed by curly brackets. Note that Manim also supports a custom syntax that allows splitting a TeX string into substrings easily: simply enclose parts of your formula diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index bd494c0211..c296130a27 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -21,12 +21,12 @@ from ..geometry.line import Line from ..geometry.polygram import Polygon, Rectangle, RoundedRectangle from ..opengl.opengl_compatibility import ConvertToOpenGL -from ..types.vectorized_mobject import VMobject +from ..types.vectorized_mobject import VGroup, VMobject __all__ = ["SVGMobject", "VMobjectFromSVGPath"] -SVG_HASH_TO_MOB_MAP: dict[int, VMobject] = {} +SVG_HASH_TO_MOB_MAP: dict[int, SVGMobject] = {} def _convert_point_to_3d(x: float, y: float) -> np.ndarray: @@ -127,6 +127,7 @@ def __init__( self.stroke_color = stroke_color self.stroke_opacity = stroke_opacity # type: ignore[assignment] self.stroke_width = stroke_width # type: ignore[assignment] + self.id_to_vgroup_dict: dict[str, VGroup] = {} if self.stroke_width is None: self.stroke_width = 0 @@ -170,6 +171,7 @@ def init_svg_mobject(self, use_svg_cache: bool) -> None: if hash_val in SVG_HASH_TO_MOB_MAP: mob = SVG_HASH_TO_MOB_MAP[hash_val].copy() self.add(*mob) + self.id_to_vgroup_dict = mob.id_to_vgroup_dict return self.generate_mobject() @@ -203,8 +205,9 @@ def generate_mobject(self) -> None: svg = se.SVG.parse(modified_file_path) modified_file_path.unlink() - mobjects = self.get_mobjects_from(svg) + mobjects, mobject_dict = self.get_mobjects_from(svg) self.add(*mobjects) + self.id_to_vgroup_dict = mobject_dict self.flip(RIGHT) # Flip y def get_file_path(self) -> Path: @@ -258,7 +261,9 @@ def generate_config_style_dict(self) -> dict[str, str]: result[svg_key] = str(svg_default_dict[style_key]) return result - def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: + def get_mobjects_from( + self, svg: se.SVG + ) -> tuple[list[VMobject], dict[str, VGroup]]: """Convert the elements of the SVG to a list of mobjects. Parameters @@ -267,36 +272,77 @@ def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: The parsed SVG file. """ result: list[VMobject] = [] - for shape in svg.elements(): - # can we combine the two continue cases into one? - if isinstance(shape, se.Group): # noqa: SIM114 - continue - elif isinstance(shape, se.Path): - mob: VMobject = self.path_to_mobject(shape) - elif isinstance(shape, se.SimpleLine): - mob = self.line_to_mobject(shape) - elif isinstance(shape, se.Rect): - mob = self.rect_to_mobject(shape) - elif isinstance(shape, (se.Circle, se.Ellipse)): - mob = self.ellipse_to_mobject(shape) - elif isinstance(shape, se.Polygon): - mob = self.polygon_to_mobject(shape) - elif isinstance(shape, se.Polyline): - mob = self.polyline_to_mobject(shape) - elif isinstance(shape, se.Text): - mob = self.text_to_mobject(shape) - elif isinstance(shape, se.Use) or type(shape) is se.SVGElement: - continue - else: - logger.warning(f"Unsupported element type: {type(shape)}") - continue - if mob is None or not mob.has_points(): - continue - self.apply_style_to_mobject(mob, shape) - if isinstance(shape, se.Transformable) and shape.apply: - self.handle_transform(mob, shape.transform) - result.append(mob) - return result + stack: list[tuple[se.SVGElement, int]] = [] + stack.append((svg, 1)) + group_id_number = 0 + vgroup_stack: list[str] = ["root"] + vgroup_names: list[str] = ["root"] + vgroups: dict[str, VGroup] = {"root": VGroup()} + while len(stack) > 0: + element, depth = stack.pop() + # Reduce stack heights + vgroup_stack = vgroup_stack[0:(depth)] + try: + group_name = str(element.values["id"]) + except Exception: + group_name = f"numbered_group_{group_id_number}" + group_id_number += 1 + vg = VGroup() + vgroup_names.append(group_name) + vgroup_stack.append(group_name) + vgroups[group_name] = vg + + if isinstance(element, (se.Group, se.Use)): + stack.extend((subelement, depth + 1) for subelement in element[::-1]) + # Add element to the parent vgroup + try: + if isinstance( + element, + ( + se.Path, + se.SimpleLine, + se.Rect, + se.Circle, + se.Ellipse, + se.Polygon, + se.Polyline, + se.Text, + ), + ): + mob = self.get_mob_from_shape_element(element) + if mob is not None: + result.append(mob) + for parent_name in vgroup_stack[:-1]: + vgroups[parent_name].add(mob) + except Exception as e: + logger.error(f"Exception occurred in 'get_mobjects_from'. Details: {e}") + + return result, vgroups + + def get_mob_from_shape_element(self, shape: se.SVGElement) -> VMobject | None: + if isinstance(shape, se.Path): + mob: VMobject | None = self.path_to_mobject(shape) + elif isinstance(shape, se.SimpleLine): + mob = self.line_to_mobject(shape) + elif isinstance(shape, se.Rect): + mob = self.rect_to_mobject(shape) + elif isinstance(shape, (se.Circle, se.Ellipse)): + mob = self.ellipse_to_mobject(shape) + elif isinstance(shape, se.Polygon): + mob = self.polygon_to_mobject(shape) + elif isinstance(shape, se.Polyline): + mob = self.polyline_to_mobject(shape) + elif isinstance(shape, se.Text): + mob = self.text_to_mobject(shape) + else: + logger.warning(f"Unsupported element type: {type(shape)}") + mob = None + if mob is None or not mob.has_points(): + return mob + self.apply_style_to_mobject(mob, shape) + if isinstance(shape, se.Transformable) and shape.apply: + self.handle_transform(mob, shape.transform) + return mob @staticmethod def handle_transform(mob: VMobject, matrix: se.Matrix) -> VMobject: diff --git a/manim/mobject/table.py b/manim/mobject/table.py index 6aa806277f..14cb7db98f 100644 --- a/manim/mobject/table.py +++ b/manim/mobject/table.py @@ -1078,11 +1078,11 @@ def construct(self): [[0,30,45,60,90], [90,60,45,30,0]], col_labels=[ - MathTex(r"\frac{\sqrt{0}}{2}"), - MathTex(r"\frac{\sqrt{1}}{2}"), - MathTex(r"\frac{\sqrt{2}}{2}"), - MathTex(r"\frac{\sqrt{3}}{2}"), - MathTex(r"\frac{\sqrt{4}}{2}")], + MathTex(r"\frac{ \sqrt{0} }{2}"), + MathTex(r"\frac{ \sqrt{1} }{2}"), + MathTex(r"\frac{ \sqrt{2} }{2}"), + MathTex(r"\frac{ \sqrt{3} }{2}"), + MathTex(r"\frac{ \sqrt{4} }{2}")], row_labels=[MathTex(r"\sin"), MathTex(r"\cos")], h_buff=1, element_to_mobject_config={"unit": r"^{\circ}"}) diff --git a/manim/mobject/text/tex_mobject.py b/manim/mobject/text/tex_mobject.py index 5d5cca069b..18546bce89 100644 --- a/manim/mobject/text/tex_mobject.py +++ b/manim/mobject/text/tex_mobject.py @@ -12,7 +12,7 @@ from __future__ import annotations -from manim.utils.color import BLACK, ManimColor, ParsableManimColor +from manim.utils.color import BLACK, ParsableManimColor __all__ = [ "SingleStringMathTex", @@ -23,10 +23,9 @@ ] -import itertools as it import operator as op import re -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from functools import reduce from textwrap import dedent from typing import Any, Self @@ -39,6 +38,10 @@ from manim.utils.tex import TexTemplate from manim.utils.tex_file_writing import tex_to_svg_file +from ..opengl.opengl_compatibility import ConvertToOpenGL + +MATHTEX_SUBSTRING = "substring" + class SingleStringMathTex(SVGMobject): """Elementary building block for rendering text with LaTeX. @@ -264,22 +267,30 @@ def __init__( self.tex_template = kwargs.pop("tex_template", config["tex_template"]) self.arg_separator = arg_separator self.substrings_to_isolate = ( - [] if substrings_to_isolate is None else substrings_to_isolate + [] if substrings_to_isolate is None else list(substrings_to_isolate) ) if tex_to_color_map is None: self.tex_to_color_map: dict[str, ParsableManimColor] = {} else: self.tex_to_color_map = tex_to_color_map + self.substrings_to_isolate.extend(self.tex_to_color_map.keys()) self.tex_environment = tex_environment self.brace_notation_split_occurred = False - self.tex_strings = self._break_up_tex_strings(tex_strings) + self.tex_strings = self._prepare_tex_strings(tex_strings) + self.matched_strings_and_ids: list[tuple[str, str]] = [] + try: + joined_string = self._join_tex_strings_with_unique_deliminters( + self.tex_strings, self.substrings_to_isolate + ) super().__init__( - self.arg_separator.join(self.tex_strings), + joined_string, tex_environment=self.tex_environment, tex_template=self.tex_template, **kwargs, ) + # Save the original tex_string + self.tex_string = self.arg_separator.join(self.tex_strings) self._break_up_by_substrings() except ValueError as compilation_error: if self.brace_notation_split_occurred: @@ -301,36 +312,109 @@ def __init__( if self.organize_left_to_right: self._organize_submobjects_left_to_right() - def _break_up_tex_strings(self, tex_strings: Sequence[str]) -> list[str]: - # Separate out anything surrounded in double braces - pre_split_length = len(tex_strings) - tex_strings_brace_splitted = [ - re.split("{{(.*?)}}", str(t)) for t in tex_strings + def _prepare_tex_strings(self, tex_strings: Iterable[str]) -> list[str]: + # Deal with the case where tex_strings contains integers instead + # of strings. + tex_strings_validated = [ + string if isinstance(string, str) else str(string) for string in tex_strings ] - tex_strings_combined = sum(tex_strings_brace_splitted, []) - if len(tex_strings_combined) > pre_split_length: + # Locate double curly bracers + tex_strings_validated_two = [] + for tex_string in tex_strings_validated: + split = re.split(r"{{|}}", tex_string) + tex_strings_validated_two.extend(split) + if len(tex_strings_validated_two) > len(tex_strings_validated): self.brace_notation_split_occurred = True - - # Separate out any strings specified in the isolate - # or tex_to_color_map lists. - patterns = [] - patterns.extend( - [ - f"({re.escape(ss)})" - for ss in it.chain( - self.substrings_to_isolate, - self.tex_to_color_map.keys(), + return [string for string in tex_strings_validated_two if len(string) > 0] + + def _join_tex_strings_with_unique_deliminters( + self, tex_strings: list[str], substrings_to_isolate: Iterable[str] + ) -> str: + joined_string = "" + ssIdx = 0 + for idx, tex_string in enumerate(tex_strings): + string_part = rf"\special{{dvisvgm:raw }}" + self.matched_strings_and_ids.append((tex_string, f"unique{idx:03d}")) + + # Try to match with all substrings_to_isolate and apply the first match + # then match again (on the rest of the string) and continue until no + # characters are left in the string + unprocessed_string = str(tex_string) + processed_string = "" + while len(unprocessed_string) > 0: + first_match = self._locate_first_match( + substrings_to_isolate, unprocessed_string ) - ], + + if first_match: + processed, unprocessed_string = self._handle_match( + ssIdx, first_match + ) + processed_string = processed_string + processed + ssIdx += 1 + else: + processed_string = processed_string + unprocessed_string + unprocessed_string = "" + + string_part += processed_string + if idx < len(tex_strings) - 1: + string_part += self.arg_separator + string_part += r"\special{dvisvgm:raw }" + joined_string = joined_string + string_part + return joined_string + + def _locate_first_match( + self, substrings_to_isolate: Iterable[str], unprocessed_string: str + ) -> re.Match | None: + first_match_start = len(unprocessed_string) + first_match_length = 0 + first_match = None + for substring in substrings_to_isolate: + match = re.match(f"(.*?)({re.escape(substring)})(.*)", unprocessed_string) + if match and len(match.group(1)) < first_match_start: + first_match = match + first_match_start = len(match.group(1)) + first_match_length = len(match.group(2)) + elif match and len(match.group(1)) == first_match_start: + # Break ties by looking at length of matches. + if first_match_length < len(match.group(2)): + first_match = match + first_match_start = len(match.group(1)) + first_match_length = len(match.group(2)) + return first_match + + def _handle_match(self, ssIdx: int, first_match: re.Match) -> tuple[str, str]: + pre_match = first_match.group(1) + matched_string = first_match.group(2) + post_match = first_match.group(3) + pre_string = ( + rf"\special{{dvisvgm:raw }}" ) - pattern = "|".join(patterns) - if pattern: - pieces = [] - for s in tex_strings_combined: - pieces.extend(re.split(pattern, s)) - else: - pieces = tex_strings_combined - return [p for p in pieces if p] + post_string = r"\special{dvisvgm:raw }" + self.matched_strings_and_ids.append( + (matched_string, f"unique{ssIdx:03d}{MATHTEX_SUBSTRING}") + ) + processed_string = pre_match + pre_string + matched_string + post_string + unprocessed_string = post_match + return processed_string, unprocessed_string + + @property + def _substring_matches(self) -> list[tuple[str, str]]: + """Return only the 'ss' (substring_to_isolate) matches.""" + return [ + (tex, id_) + for tex, id_ in self.matched_strings_and_ids + if id_.endswith(MATHTEX_SUBSTRING) + ] + + @property + def _main_matches(self) -> list[tuple[str, str]]: + """Return only the main tex_string matches.""" + return [ + (tex, id_) + for tex, id_ in self.matched_strings_and_ids + if not id_.endswith(MATHTEX_SUBSTRING) + ] def _break_up_by_substrings(self) -> Self: """ @@ -339,51 +423,32 @@ def _break_up_by_substrings(self) -> Self: of tex_strings) """ new_submobjects: list[VMobject] = [] - curr_index = 0 - for tex_string in self.tex_strings: - sub_tex_mob = SingleStringMathTex( - tex_string, - tex_environment=self.tex_environment, - tex_template=self.tex_template, - ) - num_submobs = len(sub_tex_mob.submobjects) - new_index = ( - curr_index + num_submobs + len("".join(self.arg_separator.split())) + try: + for tex_string, tex_string_id in self._main_matches: + mtp = MathTexPart() + mtp.tex_string = tex_string + mtp.add(*self.id_to_vgroup_dict[tex_string_id].submobjects) + new_submobjects.append(mtp) + except KeyError: + logger.error( + f"MathTex: Could not find SVG group for tex part '{tex_string}' (id: {tex_string_id}). Using fallback to root group." ) - if num_submobs == 0: - last_submob_index = min(curr_index, len(self.submobjects) - 1) - sub_tex_mob.move_to(self.submobjects[last_submob_index], RIGHT) - else: - sub_tex_mob.submobjects = self.submobjects[curr_index:new_index] - new_submobjects.append(sub_tex_mob) - curr_index = new_index + new_submobjects.append(self.id_to_vgroup_dict["root"]) self.submobjects = new_submobjects return self - def get_parts_by_tex( - self, tex: str, substring: bool = True, case_sensitive: bool = True - ) -> VGroup: - def test(tex1: str, tex2: str) -> bool: - if not case_sensitive: - tex1 = tex1.lower() - tex2 = tex2.lower() - if substring: - return tex1 in tex2 - else: - return tex1 == tex2 - - return VGroup(*(m for m in self.submobjects if test(tex, m.get_tex_string()))) - - def get_part_by_tex(self, tex: str, **kwargs: Any) -> MathTex | None: - all_parts = self.get_parts_by_tex(tex, **kwargs) - return all_parts[0] if all_parts else None + def get_part_by_tex(self, tex: str, **kwargs: Any) -> VGroup | None: + for tex_str, match_id in self.matched_strings_and_ids: + if tex_str == tex: + return self.id_to_vgroup_dict[match_id] + return None def set_color_by_tex( self, tex: str, color: ParsableManimColor, **kwargs: Any ) -> Self: - parts_to_color = self.get_parts_by_tex(tex, **kwargs) - for part in parts_to_color: - part.set_color(color) + for tex_str, match_id in self.matched_strings_and_ids: + if tex_str == tex: + self.id_to_vgroup_dict[match_id].set_color(color) return self def set_opacity_by_tex( @@ -409,22 +474,18 @@ def set_opacity_by_tex( """ if remaining_opacity is not None: self.set_opacity(opacity=remaining_opacity) - for part in self.get_parts_by_tex(tex): - part.set_opacity(opacity) + for tex_str, match_id in self.matched_strings_and_ids: + if tex_str == tex: + self.id_to_vgroup_dict[match_id].set_opacity(opacity) return self def set_color_by_tex_to_color_map( self, texs_to_color_map: dict[str, ParsableManimColor], **kwargs: Any ) -> Self: for texs, color in list(texs_to_color_map.items()): - try: - # If the given key behaves like tex_strings - texs + "" - self.set_color_by_tex(texs, ManimColor(color), **kwargs) - except TypeError: - # If the given key is a tuple - for tex in texs: - self.set_color_by_tex(tex, ManimColor(color), **kwargs) + for match in self.matched_strings_and_ids: + if match[0] == texs: + self.id_to_vgroup_dict[match[1]].set_color(color) return self def index_of_part(self, part: MathTex) -> int: @@ -433,16 +494,17 @@ def index_of_part(self, part: MathTex) -> int: raise ValueError("Trying to get index of part not in MathTex") return split_self.index(part) - def index_of_part_by_tex(self, tex: str, **kwargs: Any) -> int: - part = self.get_part_by_tex(tex, **kwargs) - if part is None: - return -1 - return self.index_of_part(part) - def sort_alphabetically(self) -> None: self.submobjects.sort(key=lambda m: m.get_tex_string()) +class MathTexPart(VMobject, metaclass=ConvertToOpenGL): + tex_string: str + + def __repr__(self) -> str: + return f"{type(self).__name__}({repr(self.tex_string)})" + + class Tex(MathTex): r"""A string compiled with LaTeX in normal mode. diff --git a/tests/control_data/logs_data/bad_tex_scene_BadTex.txt b/tests/control_data/logs_data/bad_tex_scene_BadTex.txt index 02c8813969..06a36fb5a1 100644 --- a/tests/control_data/logs_data/bad_tex_scene_BadTex.txt +++ b/tests/control_data/logs_data/bad_tex_scene_BadTex.txt @@ -1,8 +1,8 @@ {"levelname": "INFO", "module": "logger_utils", "message": "Log file will be saved in <>"} {"levelname": "INFO", "module": "tex_file_writing", "message": "Writing <> to <>"} {"levelname": "ERROR", "module": "tex_file_writing", "message": "LaTeX compilation error: LaTeX Error: File `notapackage.sty' not found.\n"} -{"levelname": "ERROR", "module": "tex_file_writing", "message": "Context of error: \n\\documentclass[preview]{standalone}\n-> \\usepackage{notapackage}\n\\begin{document}\n\\begin{center}\n\\frac{1}{0}\n"} +{"levelname": "ERROR", "module": "tex_file_writing", "message": "Context of error: \n\\documentclass[preview]{standalone}\n-> \\usepackage{notapackage}\n\\begin{document}\n\\begin{center}\n\\special{dvisvgm:raw }\\frac{1}{0}\\special{dvisvgm:raw }\n"} {"levelname": "INFO", "module": "tex_file_writing", "message": "You do not have package notapackage.sty installed."} {"levelname": "INFO", "module": "tex_file_writing", "message": "Install notapackage.sty it using your LaTeX package manager, or check for typos."} {"levelname": "ERROR", "module": "tex_file_writing", "message": "LaTeX compilation error: Emergency stop.\n"} -{"levelname": "ERROR", "module": "tex_file_writing", "message": "Context of error: \n\\documentclass[preview]{standalone}\n-> \\usepackage{notapackage}\n\\begin{document}\n\\begin{center}\n\\frac{1}{0}\n"} +{"levelname": "ERROR", "module": "tex_file_writing", "message": "Context of error: \n\\documentclass[preview]{standalone}\n-> \\usepackage{notapackage}\n\\begin{document}\n\\begin{center}\n\\special{dvisvgm:raw }\\frac{1}{0}\\special{dvisvgm:raw }\n"} diff --git a/tests/module/mobject/text/test_texmobject.py b/tests/module/mobject/text/test_texmobject.py index 0206b07710..ed79633c08 100644 --- a/tests/module/mobject/text/test_texmobject.py +++ b/tests/module/mobject/text/test_texmobject.py @@ -10,7 +10,7 @@ def test_MathTex(config): MathTex("a^2 + b^2 = c^2") - assert Path(config.media_dir, "Tex", "e4be163a00cf424f.svg").exists() + assert Path(config.media_dir, "Tex", "05bb0a41ed575f00.svg").exists() def test_SingleStringMathTex(config): @@ -29,7 +29,7 @@ def test_double_braces_testing(text_input, length_sub): def test_tex(config): Tex("The horse does not eat cucumber salad.") - assert Path(config.media_dir, "Tex", "c3945e23e546c95a.svg").exists() + assert Path(config.media_dir, "Tex", "5384b41741a246bd.svg").exists() def test_tex_temp_directory(tmpdir, monkeypatch): @@ -42,12 +42,12 @@ def test_tex_temp_directory(tmpdir, monkeypatch): with tempconfig({"media_dir": "media"}): Tex("The horse does not eat cucumber salad.") assert Path("media", "Tex").exists() - assert Path("media", "Tex", "c3945e23e546c95a.svg").exists() + assert Path("media", "Tex", "5384b41741a246bd.svg").exists() def test_percent_char_rendering(config): Tex(r"\%") - assert Path(config.media_dir, "Tex", "4a583af4d19a3adf.tex").exists() + assert Path(config.media_dir, "Tex", "32509dd0ea993961.tex").exists() def test_tex_whitespace_arg(): @@ -218,11 +218,11 @@ def test_tex_garbage_collection(tmpdir, monkeypatch, config): Path(tmpdir, "media").mkdir(exist_ok=True) config.media_dir = "media" - tex_without_log = Tex("Hello World!") # d771330b76d29ffb.tex - assert Path("media", "Tex", "d771330b76d29ffb.tex").exists() - assert not Path("media", "Tex", "d771330b76d29ffb.log").exists() + tex_without_log = Tex("Hello World!") # 058a4e242c57db6d.tex + assert Path("media", "Tex", "058a4e242c57db6d.tex").exists() + assert not Path("media", "Tex", "058a4e242c57db6d.log").exists() config.no_latex_cleanup = True - tex_with_log = Tex("Hello World, again!") # da27670a37b08799.tex - assert Path("media", "Tex", "da27670a37b08799.log").exists() + tex_with_log = Tex("Hello World, again!") # 45b4e7819cc20cb1.tex + assert Path("media", "Tex", "45b4e7819cc20cb1.log").exists() diff --git a/tests/opengl/test_texmobject_opengl.py b/tests/opengl/test_texmobject_opengl.py index e9826f9d8f..618bdb4a4f 100644 --- a/tests/opengl/test_texmobject_opengl.py +++ b/tests/opengl/test_texmobject_opengl.py @@ -9,7 +9,7 @@ def test_MathTex(config, using_opengl_renderer): MathTex("a^2 + b^2 = c^2") - assert Path(config.media_dir, "Tex", "e4be163a00cf424f.svg").exists() + assert Path(config.media_dir, "Tex", "05bb0a41ed575f00.svg").exists() def test_SingleStringMathTex(config, using_opengl_renderer): @@ -28,7 +28,7 @@ def test_double_braces_testing(using_opengl_renderer, text_input, length_sub): def test_tex(config, using_opengl_renderer): Tex("The horse does not eat cucumber salad.") - assert Path(config.media_dir, "Tex", "c3945e23e546c95a.svg").exists() + assert Path(config.media_dir, "Tex", "5384b41741a246bd.svg").exists() def test_tex_whitespace_arg(using_opengl_renderer):