diff --git a/docs/source/getting_started/example_scenes.rst b/docs/source/getting_started/example_scenes.rst index 9d09e0c2..1caeb889 100644 --- a/docs/source/getting_started/example_scenes.rst +++ b/docs/source/getting_started/example_scenes.rst @@ -70,7 +70,7 @@ AnimatingMethods class AnimatingMethods(Scene): def construct(self): - grid = Tex(r"\pi").get_grid(10, 10, height=4) + grid = OldTex(r"\pi").get_grid(10, 10, height=4) self.add(grid) # You can animate the application of mobject methods with the @@ -192,16 +192,16 @@ TexTransformExample # each of these strings. For example, the Tex mobject # below will have 5 subjects, corresponding to the # expressions [A^2, +, B^2, =, C^2] - Tex("A^2", "+", "B^2", "=", "C^2"), + OldTex("A^2", "+", "B^2", "=", "C^2"), # Likewise here - Tex("A^2", "=", "C^2", "-", "B^2"), + OldTex("A^2", "=", "C^2", "-", "B^2"), # Alternatively, you can pass in the keyword argument # "isolate" with a list of strings that should be out as # their own submobject. So the line below is equivalent # to the commented out line below it. - Tex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]), - # Tex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"), - Tex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate]) + OldTex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]), + # OldTex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"), + OldTex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate]) ) lines.arrange(DOWN, buff=LARGE_BUFF) for line in lines: @@ -260,7 +260,7 @@ TexTransformExample # new_line2 and the "\sqrt" from the final line. By passing in, # transform_mismatches=True, it will transform this "^2" part into # the "\sqrt" part. - new_line2 = Tex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate]) + new_line2 = OldTex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate]) new_line2.replace(lines[2]) new_line2.match_style(lines[2]) @@ -700,7 +700,7 @@ OpeningManimExample moving_c_grid.prepare_for_nonlinear_transform() c_grid.set_stroke(BLUE_E, 1) c_grid.add_coordinate_labels(font_size=24) - complex_map_words = MTexText(""" + complex_map_words = TexText(""" Or thinking of the plane as $\\mathds{C}$,\\\\ this is the map $z \\rightarrow z^2$ """) diff --git a/example_scenes.py b/example_scenes.py index cce59dbb..a1ca9c69 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -47,7 +47,7 @@ class OpeningManimExample(Scene): moving_c_grid.prepare_for_nonlinear_transform() c_grid.set_stroke(BLUE_E, 1) c_grid.add_coordinate_labels(font_size=24) - complex_map_words = MTexText(""" + complex_map_words = TexText(""" Or thinking of the plane as $\\mathds{C}$,\\\\ this is the map $z \\rightarrow z^2$ """) @@ -70,7 +70,7 @@ class OpeningManimExample(Scene): class AnimatingMethods(Scene): def construct(self): - grid = Tex(r"\pi").get_grid(10, 10, height=4) + grid = Tex(R"\pi").get_grid(10, 10, height=4) self.add(grid) # You can animate the application of mobject methods with the @@ -165,16 +165,16 @@ class TexTransformExample(Scene): # each of these strings. For example, the Tex mobject # below will have 5 subjects, corresponding to the # expressions [A^2, +, B^2, =, C^2] - Tex("A^2", "+", "B^2", "=", "C^2"), + OldTex("A^2", "+", "B^2", "=", "C^2"), # Likewise here - Tex("A^2", "=", "C^2", "-", "B^2"), + OldTex("A^2", "=", "C^2", "-", "B^2"), # Alternatively, you can pass in the keyword argument # "isolate" with a list of strings that should be out as # their own submobject. So the line below is equivalent # to the commented out line below it. - Tex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]), - # Tex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"), - Tex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate]) + OldTex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]), + # OldTex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"), + OldTex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate]) ) lines.arrange(DOWN, buff=LARGE_BUFF) for line in lines: @@ -233,7 +233,7 @@ class TexTransformExample(Scene): # new_line2 and the "\sqrt" from the final line. By passing in, # transform_mismatches=True, it will transform this "^2" part into # the "\sqrt" part. - new_line2 = Tex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate]) + new_line2 = OldTex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate]) new_line2.replace(lines[2]) new_line2.match_style(lines[2]) diff --git a/logo/logo.py b/logo/logo.py index a79791ce..f44da45d 100644 --- a/logo/logo.py +++ b/logo/logo.py @@ -60,10 +60,10 @@ class Thumbnail(GraphScene): triangle.scale(0.1) # - x_label_p1 = MTex("a") - output_label_p1 = MTex("f(a)") - x_label_p2 = MTex("b") - output_label_p2 = MTex("f(b)") + x_label_p1 = Tex("a") + output_label_p1 = Tex("f(a)") + x_label_p2 = Tex("b") + output_label_p2 = Tex("f(b)") v_line_p1 = get_v_line(input_tracker_p1) v_line_p2 = get_v_line(input_tracker_p2) h_line_p1 = get_h_line(input_tracker_p1) @@ -170,7 +170,7 @@ class Thumbnail(GraphScene): # adding manim picture = Group(*self.mobjects) picture.scale(0.6).to_edge(LEFT, buff=SMALL_BUFF) - manim = MTexText("Manim").set_height(1.5) \ + manim = TexText("Manim").set_height(1.5) \ .next_to(picture, RIGHT) \ .shift(DOWN * 0.7) self.add(manim) diff --git a/manimlib/__init__.py b/manimlib/__init__.py index 520d4825..063cfa62 100644 --- a/manimlib/__init__.py +++ b/manimlib/__init__.py @@ -43,7 +43,7 @@ from manimlib.mobject.probability import * from manimlib.mobject.shape_matchers import * from manimlib.mobject.svg.brace import * from manimlib.mobject.svg.drawings import * -from manimlib.mobject.svg.mtex_mobject import * +from manimlib.mobject.svg.tex_mobject import * from manimlib.mobject.svg.string_mobject import * from manimlib.mobject.svg.svg_mobject import * from manimlib.mobject.svg.special_tex import * diff --git a/manimlib/animation/transform_matching_parts.py b/manimlib/animation/transform_matching_parts.py index 16b0534f..d82c8874 100644 --- a/manimlib/animation/transform_matching_parts.py +++ b/manimlib/animation/transform_matching_parts.py @@ -13,15 +13,14 @@ from manimlib.animation.transform import Transform from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Group from manimlib.mobject.svg.string_mobject import StringMobject -from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.old_tex_mobject import OldTex from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from typing import TYPE_CHECKING if TYPE_CHECKING: - from manimlib.mobject.svg.tex_mobject import SingleStringTex - from manimlib.mobject.svg.tex_mobject import Tex + from manimlib.mobject.svg.old_tex_mobject import SingleStringTex from manimlib.scene.scene import Scene @@ -140,15 +139,15 @@ class TransformMatchingShapes(TransformMatchingParts): class TransformMatchingTex(TransformMatchingParts): - mobject_type: type = Tex + mobject_type: type = OldTex group_type: type = VGroup @staticmethod - def get_mobject_parts(mobject: Tex) -> list[SingleStringTex]: + def get_mobject_parts(mobject: OldTex) -> list[SingleStringTex]: return mobject.submobjects @staticmethod - def get_mobject_key(mobject: Tex) -> str: + def get_mobject_key(mobject: OldTex) -> str: return mobject.get_tex() diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 49e5754d..b670d8ee 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -18,7 +18,7 @@ from manimlib.mobject.geometry import DashedLine from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Rectangle from manimlib.mobject.number_line import NumberLine -from manimlib.mobject.svg.mtex_mobject import MTex +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.types.dot_cloud import DotCloud from manimlib.mobject.types.surface import ParametricSurface from manimlib.mobject.types.vectorized_mobject import VGroup @@ -105,7 +105,7 @@ class CoordinateSystem(ABC): edge: Vect3 = RIGHT, direction: Vect3 = DL, **kwargs - ) -> MTex: + ) -> Tex: return self.get_axis_label( label_tex, self.get_x_axis(), edge, direction, **kwargs @@ -117,7 +117,7 @@ class CoordinateSystem(ABC): edge: Vect3 = UP, direction: Vect3 = DR, **kwargs - ) -> MTex: + ) -> Tex: return self.get_axis_label( label_tex, self.get_y_axis(), edge, direction, **kwargs @@ -130,8 +130,8 @@ class CoordinateSystem(ABC): edge: Vect3, direction: Vect3, buff: float = MED_SMALL_BUFF - ) -> MTex: - label = MTex(label_tex) + ) -> Tex: + label = Tex(label_tex) label.next_to( axis.get_edge_center(edge), direction, buff=buff @@ -268,9 +268,9 @@ class CoordinateSystem(ABC): direction: Vect3 = RIGHT, buff: float = MED_SMALL_BUFF, color: ManimColor | None = None - ) -> MTex | Mobject: + ) -> Tex | Mobject: if isinstance(label, str): - label = MTex(label) + label = Tex(label) if color is None: label.match_color(graph) if x is None: @@ -537,7 +537,7 @@ class ThreeDAxes(Axes): def add_axis_labels(self, x_tex="x", y_tex="y", z_tex="z", font_size=24, buff=0.2): x_label, y_label, z_label = labels = VGroup(*( - MTex(tex, font_size=font_size) + Tex(tex, font_size=font_size) for tex in [x_tex, y_tex, z_tex] )) z_label.rotate(PI / 2, RIGHT) diff --git a/manimlib/mobject/matrix.py b/manimlib/mobject/matrix.py index c827cf85..1cb132fd 100644 --- a/manimlib/mobject/matrix.py +++ b/manimlib/mobject/matrix.py @@ -10,8 +10,8 @@ from manimlib.constants import WHITE from manimlib.mobject.numbers import DecimalNumber from manimlib.mobject.numbers import Integer from manimlib.mobject.shape_matchers import BackgroundRectangle -from manimlib.mobject.svg.mtex_mobject import MTex -from manimlib.mobject.svg.mtex_mobject import MTexText +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject @@ -41,8 +41,8 @@ def matrix_to_tex_string(matrix: npt.ArrayLike) -> str: return prefix + R" \\ ".join(rows) + suffix -def matrix_to_mobject(matrix: npt.ArrayLike) -> MTex: - return MTex(matrix_to_tex_string(matrix)) +def matrix_to_mobject(matrix: npt.ArrayLike) -> Tex: + return Tex(matrix_to_tex_string(matrix)) def vector_coordinate_label( @@ -109,7 +109,7 @@ class Matrix(VMobject): def element_to_mobject(self, element: str | float | VMobject, **config) -> VMobject: if isinstance(element, VMobject): return element - return MTex(str(element), **config) + return Tex(str(element), **config) def matrix_to_mob_matrix( self, @@ -142,7 +142,7 @@ class Matrix(VMobject): def add_brackets(self, v_buff: float, h_buff: float): height = len(self.mob_matrix) - brackets = MTex("".join(( + brackets = Tex("".join(( R"\left[\begin{array}{c}", *height * [R"\quad \\"], R"\end{array}\right]", @@ -219,22 +219,22 @@ def get_det_text( background_rect: bool = False, initial_scale_factor: int = 2 ) -> VGroup: - parens = MTex("()") + parens = Tex("()") parens.scale(initial_scale_factor) parens.stretch_to_fit_height(matrix.get_height()) l_paren, r_paren = parens.split() l_paren.next_to(matrix, LEFT, buff=0.1) r_paren.next_to(matrix, RIGHT, buff=0.1) - det = MTexText("det") + det = TexText("det") det.scale(initial_scale_factor) det.next_to(l_paren, LEFT, buff=0.1) if background_rect: det.add_background_rectangle() det_text = VGroup(det, l_paren, r_paren) if determinant is not None: - eq = MTex("=") + eq = Tex("=") eq.next_to(r_paren, RIGHT, buff=0.1) - result = MTex(str(determinant)) + result = Tex(str(determinant)) result.next_to(eq, RIGHT, buff=0.2) det_text.add(eq, result) return det_text diff --git a/manimlib/mobject/numbers.py b/manimlib/mobject/numbers.py index 4c7f365c..7effabea 100644 --- a/manimlib/mobject/numbers.py +++ b/manimlib/mobject/numbers.py @@ -4,7 +4,7 @@ import numpy as np from manimlib.constants import DOWN, LEFT, RIGHT, UP from manimlib.constants import WHITE -from manimlib.mobject.svg.tex_mobject import SingleStringTex +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.types.vectorized_mobject import VMobject @@ -73,7 +73,7 @@ class DecimalNumber(VMobject): dots.arrange(RIGHT, buff=2 * dots[0].get_width()) self.add(dots) if self.unit is not None: - self.unit_sign = SingleStringTex(self.unit, font_size=self.get_font_size()) + self.unit_sign = Tex(self.unit, font_size=self.get_font_size()) self.add(self.unit_sign) self.arrange( diff --git a/manimlib/mobject/probability.py b/manimlib/mobject/probability.py index 26ee9200..44a3fbf5 100644 --- a/manimlib/mobject/probability.py +++ b/manimlib/mobject/probability.py @@ -9,8 +9,8 @@ from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Rectangle from manimlib.mobject.mobject import Mobject from manimlib.mobject.svg.brace import Brace -from manimlib.mobject.svg.mtex_mobject import MTex -from manimlib.mobject.svg.mtex_mobject import MTexText +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.utils.color import color_gradient from manimlib.utils.iterables import listify @@ -52,7 +52,7 @@ class SampleSpace(Rectangle): buff: float = MED_SMALL_BUFF ) -> None: # TODO, should this really exist in SampleSpaceScene - title_mob = MTexText(title) + title_mob = TexText(title) if title_mob.get_width() > self.get_width(): title_mob.set_width(self.get_width()) title_mob.next_to(self, UP, buff=buff) @@ -132,7 +132,7 @@ class SampleSpace(Rectangle): if isinstance(label, Mobject): label_mob = label else: - label_mob = MTex(label) + label_mob = Tex(label) label_mob.scale(self.default_label_scale_val) label_mob.next_to(brace, direction, buff) @@ -266,7 +266,7 @@ class BarChart(VGroup): if self.label_y_axis: labels = VGroup() for y_tick, value in zip(y_ticks, values): - label = MTex(str(np.round(value, 2))) + label = Tex(str(np.round(value, 2))) label.set_height(self.y_axis_label_height) label.next_to(y_tick, LEFT, SMALL_BUFF) labels.add(label) @@ -289,7 +289,7 @@ class BarChart(VGroup): bar_labels = VGroup() for bar, name in zip(bars, self.bar_names): - label = MTex(str(name)) + label = Tex(str(name)) label.scale(self.bar_label_scale_val) label.next_to(bar, DOWN, SMALL_BUFF) bar_labels.add(label) diff --git a/manimlib/mobject/svg/brace.py b/manimlib/mobject/svg/brace.py index 1ad27377..65cd3740 100644 --- a/manimlib/mobject/svg/brace.py +++ b/manimlib/mobject/svg/brace.py @@ -11,8 +11,7 @@ from manimlib.constants import PI from manimlib.animation.composition import AnimationGroup from manimlib.animation.fading import FadeIn from manimlib.animation.growing import GrowFromCenter -from manimlib.mobject.svg.tex_mobject import SingleStringTex -from manimlib.mobject.svg.mtex_mobject import MTex +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.types.vectorized_mobject import VGroup @@ -30,7 +29,7 @@ if TYPE_CHECKING: from manimlib.typing import Vect3 -class Brace(SingleStringTex): +class Brace(Tex): def __init__( self, mobject: Mobject, @@ -92,8 +91,8 @@ class Brace(SingleStringTex): self.put_at_tip(text_mob, buff=buff) return text_mob - def get_tex(self, *tex: str, **kwargs) -> MTex: - tex_mob = MTex(*tex) + def get_tex(self, *tex: str, **kwargs) -> Tex: + tex_mob = Tex(*tex) self.put_at_tip(tex_mob, **kwargs) return tex_mob @@ -109,7 +108,7 @@ class Brace(SingleStringTex): class BraceLabel(VMobject): - label_constructor: type = MTex + label_constructor: type = Tex def __init__( self, diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index ed7920a6..5a413ee5 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -44,8 +44,8 @@ from manimlib.mobject.geometry import Square from manimlib.mobject.mobject import Mobject from manimlib.mobject.numbers import Integer from manimlib.mobject.svg.svg_mobject import SVGMobject -from manimlib.mobject.svg.mtex_mobject import MTex -from manimlib.mobject.svg.mtex_mobject import MTexText +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.svg.special_tex import TexTextFromPresetString from manimlib.mobject.three_dimensions import Prismify from manimlib.mobject.three_dimensions import VCube @@ -427,7 +427,7 @@ class Bubble(SVGMobject): return self.content def write(self, *text): - self.add_content(MTexText(*text)) + self.add_content(TexText(*text)) return self def resize_to_content(self, buff=0.75): diff --git a/manimlib/mobject/svg/mtex_mobject.py b/manimlib/mobject/svg/mtex_mobject.py deleted file mode 100644 index 1860fa40..00000000 --- a/manimlib/mobject/svg/mtex_mobject.py +++ /dev/null @@ -1,228 +0,0 @@ -from __future__ import annotations - -import re - -from manimlib.mobject.svg.string_mobject import StringMobject -from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.mobject.types.vectorized_mobject import VMobject -from manimlib.utils.color import color_to_hex -from manimlib.utils.color import hex_to_int -from manimlib.utils.tex_file_writing import tex_content_to_svg_file -from manimlib.utils.tex import num_tex_symbols -from manimlib.logger import log - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from manimlib.typing import ManimColor, Span, Selector - - -SCALE_FACTOR_PER_FONT_POINT = 0.001 - - -class MTex(StringMobject): - tex_environment: str = "align*" - - def __init__( - self, - *tex_strings: str, - font_size: int = 48, - alignment: str = R"\centering", - template: str = "", - additional_preamble: str = "", - tex_to_color_map: dict = dict(), - t2c: dict = dict(), - isolate: Selector = [], - use_labelled_svg: bool = True, - **kwargs - ): - # Combine multi-string arg, but mark them to isolate - if len(tex_strings) > 1: - if isinstance(isolate, (str, re.Pattern, tuple)): - isolate = [isolate] - isolate = [*isolate, *tex_strings] - - tex_string = " ".join(tex_strings) - - # Prevent from passing an empty string. - if not tex_string.strip(): - tex_string = R"\\" - - self.tex_string = tex_string - self.alignment = alignment - self.template = template - self.additional_preamble = additional_preamble - self.tex_to_color_map = dict(**t2c, **tex_to_color_map) - - super().__init__( - tex_string, - use_labelled_svg=use_labelled_svg, - isolate=isolate, - **kwargs - ) - - self.set_color_by_tex_to_color_map(self.tex_to_color_map) - self.scale(SCALE_FACTOR_PER_FONT_POINT * font_size) - - @property - def hash_seed(self) -> tuple: - return ( - self.__class__.__name__, - self.svg_default, - self.path_string_config, - self.base_color, - self.isolate, - self.protect, - self.tex_string, - self.alignment, - self.tex_environment, - self.tex_to_color_map, - self.template, - self.additional_preamble - ) - - def get_file_path_by_content(self, content: str) -> str: - return tex_content_to_svg_file( - content, self.template, self.additional_preamble, self.tex_string - ) - - # Parsing - - @staticmethod - def get_command_matches(string: str) -> list[re.Match]: - # Lump together adjacent brace pairs - pattern = re.compile(r""" - (?P\\(?:[a-zA-Z]+|.)) - |(?P{+) - |(?P}+) - """, flags=re.X | re.S) - result = [] - open_stack = [] - for match_obj in pattern.finditer(string): - if match_obj.group("open"): - open_stack.append((match_obj.span(), len(result))) - elif match_obj.group("close"): - close_start, close_end = match_obj.span() - while True: - if not open_stack: - raise ValueError("Missing '{' inserted") - (open_start, open_end), index = open_stack.pop() - n = min(open_end - open_start, close_end - close_start) - result.insert(index, pattern.fullmatch( - string, pos=open_end - n, endpos=open_end - )) - result.append(pattern.fullmatch( - string, pos=close_start, endpos=close_start + n - )) - close_start += n - if close_start < close_end: - continue - open_end -= n - if open_start < open_end: - open_stack.append(((open_start, open_end), index)) - break - else: - result.append(match_obj) - if open_stack: - raise ValueError("Missing '}' inserted") - return result - - @staticmethod - def get_command_flag(match_obj: re.Match) -> int: - if match_obj.group("open"): - return 1 - if match_obj.group("close"): - return -1 - return 0 - - @staticmethod - def replace_for_content(match_obj: re.Match) -> str: - return match_obj.group() - - @staticmethod - def replace_for_matching(match_obj: re.Match) -> str: - if match_obj.group("command"): - return match_obj.group() - return "" - - @staticmethod - def get_attr_dict_from_command_pair( - open_command: re.Match, close_command: re.Match - ) -> dict[str, str] | None: - if len(open_command.group()) >= 2: - return {} - return None - - def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]: - return [ - (span, {}) - for selector in self.tex_to_color_map - for span in self.find_spans_by_selector(selector) - ] - - @staticmethod - def get_color_command(rgb_hex: str) -> str: - rgb = hex_to_int(rgb_hex) - rg, b = divmod(rgb, 256) - r, g = divmod(rg, 256) - return f"\\color[RGB]{{{r}, {g}, {b}}}" - - @staticmethod - def get_command_string( - attr_dict: dict[str, str], is_end: bool, label_hex: str | None - ) -> str: - if label_hex is None: - return "" - if is_end: - return "}}" - return "{{" + MTex.get_color_command(label_hex) - - def get_content_prefix_and_suffix( - self, is_labelled: bool - ) -> tuple[str, str]: - prefix_lines = [] - suffix_lines = [] - if not is_labelled: - prefix_lines.append(self.get_color_command( - color_to_hex(self.base_color) - )) - if self.alignment: - prefix_lines.append(self.alignment) - if self.tex_environment: - prefix_lines.append(f"\\begin{{{self.tex_environment}}}") - suffix_lines.append(f"\\end{{{self.tex_environment}}}") - return ( - "".join([line + "\n" for line in prefix_lines]), - "".join(["\n" + line for line in suffix_lines]) - ) - - # Method alias - - def get_parts_by_tex(self, selector: Selector) -> VGroup: - return self.select_parts(selector) - - def get_part_by_tex(self, selector: Selector, index: int = 0) -> VMobject: - return self.select_part(selector, index) - - def set_color_by_tex(self, selector: Selector, color: ManimColor): - return self.set_parts_color(selector, color) - - def set_color_by_tex_to_color_map( - self, color_map: dict[Selector, ManimColor] - ): - return self.set_parts_color_by_dict(color_map) - - def substr_to_path_count(self, substr: str) -> int: - tex = self.get_tex() - if len(self) != num_tex_symbols(tex): - log.warning( - f"Estimated size of {tex} does not match true size", - ) - return num_tex_symbols(substr) - - def get_tex(self) -> str: - return self.get_string() - - -class MTexText(MTex): - tex_environment: str = "" diff --git a/manimlib/mobject/svg/old_tex_mobject.py b/manimlib/mobject/svg/old_tex_mobject.py new file mode 100644 index 00000000..d0b46f88 --- /dev/null +++ b/manimlib/mobject/svg/old_tex_mobject.py @@ -0,0 +1,339 @@ +from __future__ import annotations + +from functools import reduce +import operator as op +import re + +from manimlib.constants import BLACK, WHITE +from manimlib.mobject.svg.svg_mobject import SVGMobject +from manimlib.mobject.types.vectorized_mobject import VGroup +from manimlib.utils.tex_file_writing import tex_content_to_svg_file + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Iterable, List, Dict + from manimlib.typing import ManimColor + + +SCALE_FACTOR_PER_FONT_POINT = 0.001 + + +class SingleStringTex(SVGMobject): + height: float | None = None + + def __init__( + self, + tex_string: str, + height: float | None = None, + fill_color: ManimColor = WHITE, + fill_opacity: float = 1.0, + stroke_width: float = 0, + svg_default: dict = dict(fill_color=WHITE), + path_string_config: dict = dict( + should_subdivide_sharp_curves=True, + should_remove_null_curves=True, + ), + font_size: int = 48, + alignment: str = R"\centering", + math_mode: bool = True, + organize_left_to_right: bool = False, + template: str = "", + additional_preamble: str = "", + **kwargs + ): + self.tex_string = tex_string + self.svg_default = dict(svg_default) + self.path_string_config = dict(path_string_config) + self.font_size = font_size + self.alignment = alignment + self.math_mode = math_mode + self.organize_left_to_right = organize_left_to_right + self.template = template + self.additional_preamble = additional_preamble + + super().__init__( + height=height, + fill_color=fill_color, + fill_opacity=fill_opacity, + stroke_width=stroke_width, + path_string_config=path_string_config, + **kwargs + ) + + if self.height is None: + self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size) + if self.organize_left_to_right: + self.organize_submobjects_left_to_right() + + @property + def hash_seed(self) -> tuple: + return ( + self.__class__.__name__, + self.svg_default, + self.path_string_config, + self.tex_string, + self.alignment, + self.math_mode, + self.template, + self.additional_preamble + ) + + def get_file_path(self) -> str: + content = self.get_tex_file_body(self.tex_string) + file_path = tex_content_to_svg_file( + content, self.template, self.additional_preamble, self.tex_string + ) + return file_path + + def get_tex_file_body(self, tex_string: str) -> str: + new_tex = self.get_modified_expression(tex_string) + if self.math_mode: + new_tex = "\\begin{align*}\n" + new_tex + "\n\\end{align*}" + return self.alignment + "\n" + new_tex + + def get_modified_expression(self, tex_string: str) -> str: + return self.modify_special_strings(tex_string.strip()) + + def modify_special_strings(self, tex: str) -> str: + tex = tex.strip() + should_add_filler = reduce(op.or_, [ + # Fraction line needs something to be over + tex == "\\over", + tex == "\\overline", + # Makesure sqrt has overbar + tex == "\\sqrt", + tex == "\\sqrt{", + # Need to add blank subscript or superscript + tex.endswith("_"), + tex.endswith("^"), + tex.endswith("dot"), + ]) + if should_add_filler: + filler = "{\\quad}" + tex += filler + + should_add_double_filler = reduce(op.or_, [ + tex == "\\overset", + # TODO: these can't be used since they change + # the latex draw order. + # tex == "\\frac", # you can use \\over as a alternative + # tex == "\\dfrac", + # tex == "\\binom", + ]) + if should_add_double_filler: + filler = "{\\quad}{\\quad}" + tex += filler + + if tex == "\\substack": + tex = "\\quad" + + if tex == "": + tex = "\\quad" + + # To keep files from starting with a line break + if tex.startswith("\\\\"): + tex = tex.replace("\\\\", "\\quad\\\\") + + tex = self.balance_braces(tex) + + # Handle imbalanced \left and \right + num_lefts, num_rights = [ + len([ + s for s in tex.split(substr)[1:] + if s and s[0] in "(){}[]|.\\" + ]) + for substr in ("\\left", "\\right") + ] + if num_lefts != num_rights: + tex = tex.replace("\\left", "\\big") + tex = tex.replace("\\right", "\\big") + + for context in ["array"]: + begin_in = ("\\begin{%s}" % context) in tex + end_in = ("\\end{%s}" % context) in tex + if begin_in ^ end_in: + # Just turn this into a blank string, + # which means caller should leave a + # stray \\begin{...} with other symbols + tex = "" + return tex + + def balance_braces(self, tex: str) -> str: + """ + Makes Tex resiliant to unmatched braces + """ + num_unclosed_brackets = 0 + for i in range(len(tex)): + if i > 0 and tex[i - 1] == "\\": + # So as to not count '\{' type expressions + continue + char = tex[i] + if char == "{": + num_unclosed_brackets += 1 + elif char == "}": + if num_unclosed_brackets == 0: + tex = "{" + tex + else: + num_unclosed_brackets -= 1 + tex += num_unclosed_brackets * "}" + return tex + + def get_tex(self) -> str: + return self.tex_string + + def organize_submobjects_left_to_right(self): + self.sort(lambda p: p[0]) + return self + + +class OldTex(SingleStringTex): + def __init__( + self, + *tex_strings: str, + arg_separator: str = "", + isolate: List[str] = [], + tex_to_color_map: Dict[str, ManimColor] = {}, + **kwargs + ): + self.tex_strings = self.break_up_tex_strings( + tex_strings, + substrings_to_isolate=[*isolate, *tex_to_color_map.keys()] + ) + full_string = arg_separator.join(self.tex_strings) + + super().__init__(full_string, **kwargs) + self.break_up_by_substrings(self.tex_strings) + self.set_color_by_tex_to_color_map(tex_to_color_map) + + if self.organize_left_to_right: + self.organize_submobjects_left_to_right() + + def break_up_tex_strings(self, tex_strings: Iterable[str], substrings_to_isolate: List[str] = []) -> Iterable[str]: + # Separate out any strings specified in the isolate + # or tex_to_color_map lists. + if len(substrings_to_isolate) == 0: + return tex_strings + patterns = ( + "({})".format(re.escape(ss)) + for ss in substrings_to_isolate + ) + pattern = "|".join(patterns) + pieces = [] + for s in tex_strings: + if pattern: + pieces.extend(re.split(pattern, s)) + else: + pieces.append(s) + return list(filter(lambda s: s, pieces)) + + def break_up_by_substrings(self, tex_strings: Iterable[str]): + """ + Reorganize existing submojects one layer + deeper based on the structure of tex_strings (as a list + of tex_strings) + """ + if len(list(tex_strings)) == 1: + submob = self.copy() + self.set_submobjects([submob]) + return self + new_submobjects = [] + curr_index = 0 + for tex_string in tex_strings: + tex_string = tex_string.strip() + if len(tex_string) == 0: + continue + sub_tex_mob = SingleStringTex(tex_string, math_mode=self.math_mode) + num_submobs = len(sub_tex_mob) + if num_submobs == 0: + continue + new_index = curr_index + num_submobs + sub_tex_mob.set_submobjects(self.submobjects[curr_index:new_index]) + new_submobjects.append(sub_tex_mob) + curr_index = new_index + self.set_submobjects(new_submobjects) + return self + + def get_parts_by_tex( + self, + tex: str, + substring: bool = True, + case_sensitive: bool = True + ) -> VGroup: + def test(tex1, tex2): + if not case_sensitive: + tex1 = tex1.lower() + tex2 = tex2.lower() + if substring: + return tex1 in tex2 + else: + return tex1 == tex2 + + return VGroup(*filter( + lambda m: isinstance(m, SingleStringTex) and test(tex, m.get_tex()), + self.submobjects + )) + + def get_part_by_tex(self, tex: str, **kwargs) -> SingleStringTex | None: + all_parts = self.get_parts_by_tex(tex, **kwargs) + return all_parts[0] if all_parts else None + + def set_color_by_tex(self, tex: str, color: ManimColor, **kwargs): + self.get_parts_by_tex(tex, **kwargs).set_color(color) + return self + + def set_color_by_tex_to_color_map( + self, + tex_to_color_map: dict[str, ManimColor], + **kwargs + ): + for tex, color in list(tex_to_color_map.items()): + self.set_color_by_tex(tex, color, **kwargs) + return self + + def index_of_part(self, part: SingleStringTex, start: int = 0) -> int: + return self.submobjects.index(part, start) + + def index_of_part_by_tex(self, tex: str, start: int = 0, **kwargs) -> int: + part = self.get_part_by_tex(tex, **kwargs) + return self.index_of_part(part, start) + + def slice_by_tex( + self, + start_tex: str | None = None, + stop_tex: str | None = None, + **kwargs + ) -> VGroup: + if start_tex is None: + start_index = 0 + else: + start_index = self.index_of_part_by_tex(start_tex, **kwargs) + + if stop_tex is None: + return self[start_index:] + else: + stop_index = self.index_of_part_by_tex(stop_tex, start=start_index, **kwargs) + return self[start_index:stop_index] + + def sort_alphabetically(self) -> None: + self.submobjects.sort(key=lambda m: m.get_tex()) + + def set_bstroke(self, color: ManimColor = BLACK, width: float = 4): + self.set_stroke(color, width, background=True) + return self + + +class OldTexText(OldTex): + def __init__( + self, + *tex_strings: str, + math_mode: bool = False, + arg_separator: str = "", + **kwargs + ): + super().__init__( + *tex_strings, + math_mode=math_mode, + arg_separator=arg_separator, + **kwargs + ) diff --git a/manimlib/mobject/svg/special_tex.py b/manimlib/mobject/svg/special_tex.py index 9929b589..578c00e2 100644 --- a/manimlib/mobject/svg/special_tex.py +++ b/manimlib/mobject/svg/special_tex.py @@ -6,7 +6,7 @@ from manimlib.constants import FRAME_WIDTH from manimlib.constants import MED_LARGE_BUFF, SMALL_BUFF from manimlib.mobject.geometry import Line from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.mobject.svg.mtex_mobject import MTexText +from manimlib.mobject.svg.tex_mobject import TexText from typing import TYPE_CHECKING @@ -30,7 +30,7 @@ class BulletedList(VGroup): *labelled_content, R"\end{itemize}" ]) - tex_text = MTexText(tex_string, isolate=labelled_content, **kwargs) + tex_text = TexText(tex_string, isolate=labelled_content, **kwargs) lines = (tex_text.select_part(part) for part in labelled_content) super().__init__(*lines) @@ -42,7 +42,7 @@ class BulletedList(VGroup): part.set_fill(opacity=(1.0 if i == index else opacity)) -class TexTextFromPresetString(MTexText): +class TexTextFromPresetString(TexText): tex: str = "" default_color: ManimColor = WHITE @@ -54,7 +54,7 @@ class TexTextFromPresetString(MTexText): ) -class Title(MTexText): +class Title(TexText): def __init__( self, *text_parts: str, diff --git a/manimlib/mobject/svg/string_mobject.py b/manimlib/mobject/svg/string_mobject.py index 9aa71cf0..71f2b6b9 100644 --- a/manimlib/mobject/svg/string_mobject.py +++ b/manimlib/mobject/svg/string_mobject.py @@ -24,7 +24,7 @@ if TYPE_CHECKING: class StringMobject(SVGMobject, ABC): """ - An abstract base class for `MTex` and `MarkupText` + An abstract base class for `Tex` and `MarkupText` This class aims to optimize the logic of "slicing submobjects via substrings". This could be much clearer and more user-friendly diff --git a/manimlib/mobject/svg/tex_mobject.py b/manimlib/mobject/svg/tex_mobject.py index cfcd1c7b..77a438b4 100644 --- a/manimlib/mobject/svg/tex_mobject.py +++ b/manimlib/mobject/svg/tex_mobject.py @@ -1,70 +1,68 @@ from __future__ import annotations -from functools import reduce -import operator as op import re -from manimlib.constants import BLACK, WHITE -from manimlib.mobject.svg.svg_mobject import SVGMobject +from manimlib.mobject.svg.string_mobject import StringMobject from manimlib.mobject.types.vectorized_mobject import VGroup +from manimlib.mobject.types.vectorized_mobject import VMobject +from manimlib.utils.color import color_to_hex +from manimlib.utils.color import hex_to_int from manimlib.utils.tex_file_writing import tex_content_to_svg_file +from manimlib.utils.tex import num_tex_symbols +from manimlib.logger import log from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Iterable, List, Dict - from manimlib.typing import ManimColor + from manimlib.typing import ManimColor, Span, Selector SCALE_FACTOR_PER_FONT_POINT = 0.001 -class SingleStringTex(SVGMobject): - height: float | None = None +class Tex(StringMobject): + tex_environment: str = "align*" def __init__( self, - tex_string: str, - height: float | None = None, - fill_color: ManimColor = WHITE, - fill_opacity: float = 1.0, - stroke_width: float = 0, - svg_default: dict = dict(fill_color=WHITE), - path_string_config: dict = dict( - should_subdivide_sharp_curves=True, - should_remove_null_curves=True, - ), + *tex_strings: str, font_size: int = 48, alignment: str = R"\centering", - math_mode: bool = True, - organize_left_to_right: bool = False, template: str = "", additional_preamble: str = "", + tex_to_color_map: dict = dict(), + t2c: dict = dict(), + isolate: Selector = [], + use_labelled_svg: bool = True, **kwargs ): + # Combine multi-string arg, but mark them to isolate + if len(tex_strings) > 1: + if isinstance(isolate, (str, re.Pattern, tuple)): + isolate = [isolate] + isolate = [*isolate, *tex_strings] + + tex_string = " ".join(tex_strings) + + # Prevent from passing an empty string. + if not tex_string.strip(): + tex_string = R"\\" + self.tex_string = tex_string - self.svg_default = dict(svg_default) - self.path_string_config = dict(path_string_config) - self.font_size = font_size self.alignment = alignment - self.math_mode = math_mode - self.organize_left_to_right = organize_left_to_right self.template = template self.additional_preamble = additional_preamble + self.tex_to_color_map = dict(**t2c, **tex_to_color_map) super().__init__( - height=height, - fill_color=fill_color, - fill_opacity=fill_opacity, - stroke_width=stroke_width, - path_string_config=path_string_config, + tex_string, + use_labelled_svg=use_labelled_svg, + isolate=isolate, **kwargs ) - if self.height is None: - self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size) - if self.organize_left_to_right: - self.organize_submobjects_left_to_right() + self.set_color_by_tex_to_color_map(self.tex_to_color_map) + self.scale(SCALE_FACTOR_PER_FONT_POINT * font_size) @property def hash_seed(self) -> tuple: @@ -72,268 +70,159 @@ class SingleStringTex(SVGMobject): self.__class__.__name__, self.svg_default, self.path_string_config, + self.base_color, + self.isolate, + self.protect, self.tex_string, self.alignment, - self.math_mode, + self.tex_environment, + self.tex_to_color_map, self.template, self.additional_preamble ) - def get_file_path(self) -> str: - content = self.get_tex_file_body(self.tex_string) - file_path = tex_content_to_svg_file( + def get_file_path_by_content(self, content: str) -> str: + return tex_content_to_svg_file( content, self.template, self.additional_preamble, self.tex_string ) - return file_path - def get_tex_file_body(self, tex_string: str) -> str: - new_tex = self.get_modified_expression(tex_string) - if self.math_mode: - new_tex = "\\begin{align*}\n" + new_tex + "\n\\end{align*}" - return self.alignment + "\n" + new_tex + # Parsing - def get_modified_expression(self, tex_string: str) -> str: - return self.modify_special_strings(tex_string.strip()) + @staticmethod + def get_command_matches(string: str) -> list[re.Match]: + # Lump together adjacent brace pairs + pattern = re.compile(r""" + (?P\\(?:[a-zA-Z]+|.)) + |(?P{+) + |(?P}+) + """, flags=re.X | re.S) + result = [] + open_stack = [] + for match_obj in pattern.finditer(string): + if match_obj.group("open"): + open_stack.append((match_obj.span(), len(result))) + elif match_obj.group("close"): + close_start, close_end = match_obj.span() + while True: + if not open_stack: + raise ValueError("Missing '{' inserted") + (open_start, open_end), index = open_stack.pop() + n = min(open_end - open_start, close_end - close_start) + result.insert(index, pattern.fullmatch( + string, pos=open_end - n, endpos=open_end + )) + result.append(pattern.fullmatch( + string, pos=close_start, endpos=close_start + n + )) + close_start += n + if close_start < close_end: + continue + open_end -= n + if open_start < open_end: + open_stack.append(((open_start, open_end), index)) + break + else: + result.append(match_obj) + if open_stack: + raise ValueError("Missing '}' inserted") + return result - def modify_special_strings(self, tex: str) -> str: - tex = tex.strip() - should_add_filler = reduce(op.or_, [ - # Fraction line needs something to be over - tex == "\\over", - tex == "\\overline", - # Makesure sqrt has overbar - tex == "\\sqrt", - tex == "\\sqrt{", - # Need to add blank subscript or superscript - tex.endswith("_"), - tex.endswith("^"), - tex.endswith("dot"), - ]) - if should_add_filler: - filler = "{\\quad}" - tex += filler + @staticmethod + def get_command_flag(match_obj: re.Match) -> int: + if match_obj.group("open"): + return 1 + if match_obj.group("close"): + return -1 + return 0 - should_add_double_filler = reduce(op.or_, [ - tex == "\\overset", - # TODO: these can't be used since they change - # the latex draw order. - # tex == "\\frac", # you can use \\over as a alternative - # tex == "\\dfrac", - # tex == "\\binom", - ]) - if should_add_double_filler: - filler = "{\\quad}{\\quad}" - tex += filler + @staticmethod + def replace_for_content(match_obj: re.Match) -> str: + return match_obj.group() - if tex == "\\substack": - tex = "\\quad" + @staticmethod + def replace_for_matching(match_obj: re.Match) -> str: + if match_obj.group("command"): + return match_obj.group() + return "" - if tex == "": - tex = "\\quad" + @staticmethod + def get_attr_dict_from_command_pair( + open_command: re.Match, close_command: re.Match + ) -> dict[str, str] | None: + if len(open_command.group()) >= 2: + return {} + return None - # To keep files from starting with a line break - if tex.startswith("\\\\"): - tex = tex.replace("\\\\", "\\quad\\\\") - - tex = self.balance_braces(tex) - - # Handle imbalanced \left and \right - num_lefts, num_rights = [ - len([ - s for s in tex.split(substr)[1:] - if s and s[0] in "(){}[]|.\\" - ]) - for substr in ("\\left", "\\right") + def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]: + return [ + (span, {}) + for selector in self.tex_to_color_map + for span in self.find_spans_by_selector(selector) ] - if num_lefts != num_rights: - tex = tex.replace("\\left", "\\big") - tex = tex.replace("\\right", "\\big") - for context in ["array"]: - begin_in = ("\\begin{%s}" % context) in tex - end_in = ("\\end{%s}" % context) in tex - if begin_in ^ end_in: - # Just turn this into a blank string, - # which means caller should leave a - # stray \\begin{...} with other symbols - tex = "" - return tex + @staticmethod + def get_color_command(rgb_hex: str) -> str: + rgb = hex_to_int(rgb_hex) + rg, b = divmod(rgb, 256) + r, g = divmod(rg, 256) + return f"\\color[RGB]{{{r}, {g}, {b}}}" - def balance_braces(self, tex: str) -> str: - """ - Makes Tex resiliant to unmatched braces - """ - num_unclosed_brackets = 0 - for i in range(len(tex)): - if i > 0 and tex[i - 1] == "\\": - # So as to not count '\{' type expressions - continue - char = tex[i] - if char == "{": - num_unclosed_brackets += 1 - elif char == "}": - if num_unclosed_brackets == 0: - tex = "{" + tex - else: - num_unclosed_brackets -= 1 - tex += num_unclosed_brackets * "}" - return tex + @staticmethod + def get_command_string( + attr_dict: dict[str, str], is_end: bool, label_hex: str | None + ) -> str: + if label_hex is None: + return "" + if is_end: + return "}}" + return "{{" + Tex.get_color_command(label_hex) - def get_tex(self) -> str: - return self.tex_string - - def organize_submobjects_left_to_right(self): - self.sort(lambda p: p[0]) - return self - - -class Tex(SingleStringTex): - def __init__( - self, - *tex_strings: str, - arg_separator: str = "", - isolate: List[str] = [], - tex_to_color_map: Dict[str, ManimColor] = {}, - **kwargs - ): - self.tex_strings = self.break_up_tex_strings( - tex_strings, - substrings_to_isolate=[*isolate, *tex_to_color_map.keys()] + def get_content_prefix_and_suffix( + self, is_labelled: bool + ) -> tuple[str, str]: + prefix_lines = [] + suffix_lines = [] + if not is_labelled: + prefix_lines.append(self.get_color_command( + color_to_hex(self.base_color) + )) + if self.alignment: + prefix_lines.append(self.alignment) + if self.tex_environment: + prefix_lines.append(f"\\begin{{{self.tex_environment}}}") + suffix_lines.append(f"\\end{{{self.tex_environment}}}") + return ( + "".join([line + "\n" for line in prefix_lines]), + "".join(["\n" + line for line in suffix_lines]) ) - full_string = arg_separator.join(self.tex_strings) - super().__init__(full_string, **kwargs) - self.break_up_by_substrings(self.tex_strings) - self.set_color_by_tex_to_color_map(tex_to_color_map) + # Method alias - if self.organize_left_to_right: - self.organize_submobjects_left_to_right() + def get_parts_by_tex(self, selector: Selector) -> VGroup: + return self.select_parts(selector) - def break_up_tex_strings(self, tex_strings: Iterable[str], substrings_to_isolate: List[str] = []) -> Iterable[str]: - # Separate out any strings specified in the isolate - # or tex_to_color_map lists. - if len(substrings_to_isolate) == 0: - return tex_strings - patterns = ( - "({})".format(re.escape(ss)) - for ss in substrings_to_isolate - ) - pattern = "|".join(patterns) - pieces = [] - for s in tex_strings: - if pattern: - pieces.extend(re.split(pattern, s)) - else: - pieces.append(s) - return list(filter(lambda s: s, pieces)) + def get_part_by_tex(self, selector: Selector, index: int = 0) -> VMobject: + return self.select_part(selector, index) - def break_up_by_substrings(self, tex_strings: Iterable[str]): - """ - Reorganize existing submojects one layer - deeper based on the structure of tex_strings (as a list - of tex_strings) - """ - if len(list(tex_strings)) == 1: - submob = self.copy() - self.set_submobjects([submob]) - return self - new_submobjects = [] - curr_index = 0 - for tex_string in tex_strings: - tex_string = tex_string.strip() - if len(tex_string) == 0: - continue - sub_tex_mob = SingleStringTex(tex_string, math_mode=self.math_mode) - num_submobs = len(sub_tex_mob) - if num_submobs == 0: - continue - new_index = curr_index + num_submobs - sub_tex_mob.set_submobjects(self.submobjects[curr_index:new_index]) - new_submobjects.append(sub_tex_mob) - curr_index = new_index - self.set_submobjects(new_submobjects) - return self - - def get_parts_by_tex( - self, - tex: str, - substring: bool = True, - case_sensitive: bool = True - ) -> VGroup: - def test(tex1, tex2): - if not case_sensitive: - tex1 = tex1.lower() - tex2 = tex2.lower() - if substring: - return tex1 in tex2 - else: - return tex1 == tex2 - - return VGroup(*filter( - lambda m: isinstance(m, SingleStringTex) and test(tex, m.get_tex()), - self.submobjects - )) - - def get_part_by_tex(self, tex: str, **kwargs) -> SingleStringTex | None: - all_parts = self.get_parts_by_tex(tex, **kwargs) - return all_parts[0] if all_parts else None - - def set_color_by_tex(self, tex: str, color: ManimColor, **kwargs): - self.get_parts_by_tex(tex, **kwargs).set_color(color) - return self + def set_color_by_tex(self, selector: Selector, color: ManimColor): + return self.set_parts_color(selector, color) def set_color_by_tex_to_color_map( - self, - tex_to_color_map: dict[str, ManimColor], - **kwargs + self, color_map: dict[Selector, ManimColor] ): - for tex, color in list(tex_to_color_map.items()): - self.set_color_by_tex(tex, color, **kwargs) - return self + return self.set_parts_color_by_dict(color_map) - def index_of_part(self, part: SingleStringTex, start: int = 0) -> int: - return self.submobjects.index(part, start) + def substr_to_path_count(self, substr: str) -> int: + tex = self.get_tex() + if len(self) != num_tex_symbols(tex): + log.warning( + f"Estimated size of {tex} does not match true size", + ) + return num_tex_symbols(substr) - def index_of_part_by_tex(self, tex: str, start: int = 0, **kwargs) -> int: - part = self.get_part_by_tex(tex, **kwargs) - return self.index_of_part(part, start) - - def slice_by_tex( - self, - start_tex: str | None = None, - stop_tex: str | None = None, - **kwargs - ) -> VGroup: - if start_tex is None: - start_index = 0 - else: - start_index = self.index_of_part_by_tex(start_tex, **kwargs) - - if stop_tex is None: - return self[start_index:] - else: - stop_index = self.index_of_part_by_tex(stop_tex, start=start_index, **kwargs) - return self[start_index:stop_index] - - def sort_alphabetically(self) -> None: - self.submobjects.sort(key=lambda m: m.get_tex()) - - def set_bstroke(self, color: ManimColor = BLACK, width: float = 4): - self.set_stroke(color, width, background=True) - return self + def get_tex(self) -> str: + return self.get_string() class TexText(Tex): - def __init__( - self, - *tex_strings: str, - math_mode: bool = False, - arg_separator: str = "", - **kwargs - ): - super().__init__( - *tex_strings, - math_mode=math_mode, - arg_separator=arg_separator, - **kwargs - ) + tex_environment: str = "" diff --git a/manimlib/scene/interactive_scene.py b/manimlib/scene/interactive_scene.py index f9a29c1e..b3eb0fb2 100644 --- a/manimlib/scene/interactive_scene.py +++ b/manimlib/scene/interactive_scene.py @@ -17,7 +17,7 @@ from manimlib.mobject.geometry import Square from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Mobject from manimlib.mobject.numbers import DecimalNumber -from manimlib.mobject.svg.mtex_mobject import MTex +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.types.dot_cloud import DotCloud from manimlib.mobject.types.vectorized_mobject import VGroup @@ -61,7 +61,7 @@ class InteractiveScene(Scene): Command + 'c' copies the ids of selections to clipboard Command + 'v' will paste either: - The copied mobject - - A MTex mobject based on copied LaTeX + - A Tex mobject based on copied LaTeX - A Text mobject based on copied Text Command + 'z' restores selection back to its original state Command + 's' saves the selected mobjects to file @@ -358,7 +358,7 @@ class InteractiveScene(Scene): # Otherwise, treat as tex or text if set("\\^=+").intersection(clipboard_str): # Proxy to text for LaTeX try: - new_mob = MTex(clipboard_str) + new_mob = Tex(clipboard_str) except LatexError: return else: