From 831b7d455c10e98269668ac7dbb4fc03a008d2fc Mon Sep 17 00:00:00 2001 From: YishiMichael Date: Thu, 27 Jan 2022 23:09:05 +0800 Subject: [PATCH 01/17] Handle explicit color-related commands --- manimlib/mobject/mobject.py | 4 +- manimlib/mobject/svg/mtex_mobject.py | 232 +++++++++++++++++---------- 2 files changed, 151 insertions(+), 85 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index ce0807a2..b1621c4f 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -109,8 +109,8 @@ class Mobject(object): "reflectiveness": self.reflectiveness, } - def init_colors(self, override=True): - self.set_color(self.color, self.opacity, override) + def init_colors(self): + self.set_color(self.color, self.opacity) def init_points(self): # Typically implemented in subclass, unlpess purposefully left blank diff --git a/manimlib/mobject/svg/mtex_mobject.py b/manimlib/mobject/svg/mtex_mobject.py index b7854dd3..b1f34147 100644 --- a/manimlib/mobject/svg/mtex_mobject.py +++ b/manimlib/mobject/svg/mtex_mobject.py @@ -49,23 +49,9 @@ class _LabelledTex(_PlainTex): if len(color_str) == 4: # "#RGB" => "#RRGGBB" color_str = "#" + "".join([c * 2 for c in color_str[1:]]) - - return int(color_str[1:], 16) - 1 - - def get_mobjects_from(self, element, style): - result = super().get_mobjects_from(element, style) - for mob in result: - if not hasattr(mob, "glyph_label"): - mob.glyph_label = -1 - try: - color_str = element.getAttribute("fill") - if color_str: - glyph_label = _LabelledTex.color_str_to_label(color_str) - for mob in result: - mob.glyph_label = glyph_label - except: - pass - return result + if color_str == "#ffffff": + return 0 + return int(color_str[1:], 16) class _TexSpan(object): @@ -87,16 +73,16 @@ class _TexParser(object): def __init__(self, tex_string, additional_substrings): self.tex_string = tex_string self.tex_spans_dict = {} - self.specified_substrings = [] - self.current_label = 0 + self.current_label = -1 self.brace_index_pairs = self.get_brace_index_pairs() + self.existing_color_command_spans = self.get_existing_color_command_spans() + self.has_existing_color_commands = any(self.existing_color_command_spans.values()) self.add_tex_span((0, len(tex_string))) self.break_up_by_double_braces() self.break_up_by_scripts() self.break_up_by_additional_substrings(additional_substrings) self.check_if_overlap() self.analyse_containing_labels() - self.specified_substrings = remove_list_redundancies(self.specified_substrings) @staticmethod def label_to_color_tuple(rgb): @@ -112,16 +98,12 @@ class _TexParser(object): if script_type == 0: # Should be additionally labelled. - label = self.current_label self.current_label += 1 + label = self.current_label tex_span = _TexSpan(script_type, label) self.tex_spans_dict[span_tuple] = tex_span - def add_specified_substring(self, span_tuple): - substring = self.tex_string[slice(*span_tuple)] - self.specified_substrings.append(substring) - def get_brace_index_pairs(self): result = [] left_brace_indices = [] @@ -140,6 +122,34 @@ class _TexParser(object): self.raise_tex_parsing_error("unmatched braces") return result + def get_existing_color_command_spans(self): + tex_string = self.tex_string + color_related_commands_dict = _TexParser.get_color_related_commands_dict() + commands = color_related_commands_dict.keys() + result = { + command_name: [] + for command_name in commands + } + brace_index_pairs = self.brace_index_pairs + pattern = "|".join([ + re.escape(command_name) + for command_name in commands + ]) + for match_obj in re.finditer(pattern, tex_string): + span_tuple = match_obj.span() + command_begin_index = span_tuple[0] + command_name = match_obj.group() + n_braces = color_related_commands_dict[command_name] + for _ in range(n_braces): + span_tuple = min(filter( + lambda t: t[0] >= span_tuple[1], + brace_index_pairs + )) + result[command_name].append( + (command_begin_index, span_tuple[1]) + ) + return result + def break_up_by_double_braces(self): # Match paired double braces (`{{...}}`). skip_pair = False @@ -154,7 +164,6 @@ class _TexParser(object): span_tuple[1] == prev_span_tuple[1] + 1 ]): self.add_tex_span(span_tuple) - self.add_specified_substring(span_tuple) skip_pair = True def break_up_by_scripts(self): @@ -201,9 +210,7 @@ class _TexParser(object): span_end = script_spans_dict[span_end] if span_begin >= span_end: continue - span_tuple = (span_begin, span_end) - self.add_tex_span(span_tuple) - self.add_specified_substring(span_tuple) + self.add_tex_span((span_begin, span_end)) def check_if_overlap(self): span_tuples = sorted( @@ -237,7 +244,7 @@ class _TexParser(object): def get_labelled_expression(self): tex_string = self.tex_string - if not self.tex_spans_dict: + if self.current_label == 0 and not self.has_existing_color_commands: return tex_string # Remove the span of extire tex string. @@ -248,6 +255,9 @@ class _TexParser(object): for i in range(2) ], key=lambda t: (t[0], -t[1], -t[2]))[1:] + # Prevent from "\\color[RGB]" being replaced. + # Hopefully tex string doesn't contain such a substring... + color_command_placeholder = "{{\\iffalse \\fi}}" result = tex_string[: indices_with_labels[0][0]] for index_with_label, next_index_with_label in _get_neighbouring_pairs( indices_with_labels @@ -259,7 +269,7 @@ class _TexParser(object): color_tuple = _TexParser.label_to_color_tuple(label) result += "".join([ "{{", - "\\color[RGB]", + color_command_placeholder, "{", ",".join(map(str, color_tuple)), "}" @@ -267,11 +277,35 @@ class _TexParser(object): else: result += "}}" result += tex_string[index : next_index] - return result + + color_related_commands_dict = _TexParser.get_color_related_commands_dict() + for command_name, command_spans in self.existing_color_command_spans.items(): + if not command_spans: + continue + n_braces = color_related_commands_dict[command_name] + command_to_replace = command_name + n_braces * "{black}" + commands = { + tex_string[slice(*span_tuple)] + for span_tuple in command_spans + } + for command in commands: + result = result.replace(command, command_to_replace) + + return result.replace(color_command_placeholder, "\\color[RGB]") def raise_tex_parsing_error(self, message): raise ValueError(f"Failed to parse tex ({message}): \"{self.tex_string}\"") + @staticmethod + def get_color_related_commands_dict(): + return { + "\\color": 1, + "\\textcolor": 1, + "\\pagecolor": 1, + "\\colorbox": 1, + "\\fcolorbox": 2, + } + class MTex(VMobject): CONFIG = { @@ -282,7 +316,7 @@ class MTex(VMobject): "tex_environment": "align*", "isolate": [], "tex_to_color_map": {}, - "generate_plain_tex_file": False, + "use_plain_tex_file": False, } def __init__(self, tex_string, **kwargs): @@ -293,9 +327,8 @@ class MTex(VMobject): tex_string = "\\quad" self.tex_string = tex_string - self.generate_mobject() + self.generate_tex() - self.init_colors() self.set_color_by_tex_to_color_map(self.tex_to_color_map) self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size) @@ -310,43 +343,67 @@ class MTex(VMobject): def get_parser(self): return _TexParser(self.tex_string, self.get_additional_substrings_to_break_up()) - def generate_mobject(self): + def generate_tex(self): tex_string = self.tex_string tex_parser = self.get_parser() self.tex_spans_dict = tex_parser.tex_spans_dict - self.specified_substrings = tex_parser.specified_substrings - plain_full_tex = self.get_tex_file_body(tex_string) - plain_hash_val = hash(plain_full_tex) - if plain_hash_val in TEX_HASH_TO_MOB_MAP: - self.add(*TEX_HASH_TO_MOB_MAP[plain_hash_val].copy()) - return self + fill_color = self.get_fill_color() + stroke_width = self.get_stroke_width() - labelled_expression = tex_parser.get_labelled_expression() - full_tex = self.get_tex_file_body(labelled_expression) - hash_val = hash(full_tex) - if hash_val in TEX_HASH_TO_MOB_MAP and not self.generate_plain_tex_file: + # Cannot simultaneously be false, so at least one file is generated. + require_labelled_tex_file = tex_parser.current_label != 0 + require_plain_tex_file = any([ + self.use_plain_tex_file, + tex_parser.has_existing_color_commands, + fill_color != "#ffffff", + tex_parser.current_label == 0 + ]) + + if require_labelled_tex_file: + labelled_full_tex = self.get_tex_file_body(tex_parser.get_labelled_expression()) + labelled_hash_val = hash((labelled_full_tex, stroke_width)) + if labelled_hash_val in TEX_HASH_TO_MOB_MAP: + if not require_plain_tex_file: + self.add(*TEX_HASH_TO_MOB_MAP[labelled_hash_val].copy()) + return self + else: + with display_during_execution(f"Writing \"{tex_string}\""): + filename = tex_to_svg_file(labelled_full_tex) + labelled_svg_glyphs = _LabelledTex( + filename, + stroke_width=stroke_width + ) + self.add(*labelled_svg_glyphs) + self.build_submobjects() + TEX_HASH_TO_MOB_MAP[labelled_hash_val] = self.copy() + if not require_plain_tex_file: + return self + + # require_plain_tex_file == True + self.set_submobjects([]) + full_tex = self.get_tex_file_body(tex_string) + hash_val = hash((full_tex, fill_color, stroke_width)) + if hash_val in TEX_HASH_TO_MOB_MAP: self.add(*TEX_HASH_TO_MOB_MAP[hash_val].copy()) return self - with display_during_execution(f"Writing \"{tex_string}\""): filename = tex_to_svg_file(full_tex) - svg_mob = _LabelledTex(filename) - self.add(*svg_mob.copy()) + svg_glyphs = _PlainTex( + filename, + fill_color=fill_color, + stroke_width=stroke_width + ) + if require_labelled_tex_file: + labelled_svg_mob = TEX_HASH_TO_MOB_MAP[labelled_hash_val] + for glyph, labelled_glyph in zip(svg_glyphs, it.chain(*labelled_svg_mob)): + glyph.glyph_label = labelled_glyph.glyph_label + else: + for glyph in svg_glyphs: + glyph.glyph_label = 0 + self.add(*svg_glyphs) self.build_submobjects() - TEX_HASH_TO_MOB_MAP[hash_val] = self - if not self.generate_plain_tex_file: - return self - - with display_during_execution(f"Writing \"{tex_string}\""): - filename = tex_to_svg_file(plain_full_tex) - plain_svg_mob = _PlainTex(filename) - svg_mob = TEX_HASH_TO_MOB_MAP[hash_val] - for plain_submob, submob in zip(plain_svg_mob, svg_mob): - plain_submob.glyph_label = submob.glyph_label - self.add(*plain_svg_mob.copy()) - self.build_submobjects() - TEX_HASH_TO_MOB_MAP[plain_hash_val] = self + TEX_HASH_TO_MOB_MAP[hash_val] = self.copy() return self def get_tex_file_body(self, new_tex): @@ -368,6 +425,9 @@ class MTex(VMobject): def build_submobjects(self): if not self.submobjects: return + self.init_colors() + for glyph in self.submobjects: + glyph.set_fill(glyph.fill_color) self.group_submobjects() self.sort_scripts_in_tex_order() self.assign_submob_tex_strings() @@ -383,13 +443,13 @@ class MTex(VMobject): new_glyphs = [] current_glyph_label = 0 - for submob in self.submobjects: - if submob.glyph_label == current_glyph_label: - new_glyphs.append(submob) + for glyph in self.submobjects: + if glyph.glyph_label == current_glyph_label: + new_glyphs.append(glyph) else: append_new_submobject(new_glyphs) - new_glyphs = [submob] - current_glyph_label = submob.glyph_label + new_glyphs = [glyph] + current_glyph_label = glyph.glyph_label append_new_submobject(new_glyphs) self.set_submobjects(new_submobjects) @@ -425,7 +485,7 @@ class MTex(VMobject): ) switch_range_pairs.append((submob_range_0, submob_range_1)) - switch_range_pairs.sort(key=lambda pair: (pair[0].stop, -pair[0].start)) + switch_range_pairs.sort(key=lambda t: (t[0].stop, -t[0].start)) indices = list(range(len(self.submobjects))) for submob_range_0, submob_range_1 in switch_range_pairs: indices = [ @@ -486,22 +546,28 @@ class MTex(VMobject): self.submobjects )) - def find_span_components_of_custom_span(self, custom_span_tuple, partial_result=[]): + def find_span_components_of_custom_span(self, custom_span_tuple): + tex_string = self.tex_string + span_choices = sorted(filter( + lambda t: _contains(custom_span_tuple, t), + self.tex_spans_dict.keys() + )) + # Filter out spans that reach the farthest. + span_choices_dict = dict(span_choices) + span_begin, span_end = custom_span_tuple - if span_begin == span_end: - return partial_result - next_begin_choices = sorted([ - span_tuple[1] - for span_tuple in self.tex_spans_dict.keys() - if span_tuple[0] == span_begin and span_tuple[1] <= span_end - ], reverse=True) - for next_begin in next_begin_choices: - result = self.find_span_components_of_custom_span( - (next_begin, span_end), [*partial_result, (span_begin, next_begin)] - ) - if result is not None: - return result - return None + result = [] + while span_begin != span_end: + if span_begin not in span_choices_dict: + if tex_string[span_begin].strip(): + return None + # Whitespaces may occur between spans. + span_begin += 1 + continue + next_begin = span_choices_dict[span_begin] + result.append((span_begin, next_begin)) + span_begin = next_begin + return result def get_part_by_custom_span_tuple(self, custom_span_tuple): span_tuples = self.find_span_components_of_custom_span(custom_span_tuple) From 2bd25a55fae214e1e257f1d7943b59715da158c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B9=A4=E7=BF=94=E4=B8=87=E9=87=8C?= Date: Fri, 28 Jan 2022 00:20:13 +0800 Subject: [PATCH 02/17] add back override parameter to init_colors --- manimlib/mobject/mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index b1621c4f..ce0807a2 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -109,8 +109,8 @@ class Mobject(object): "reflectiveness": self.reflectiveness, } - def init_colors(self): - self.set_color(self.color, self.opacity) + def init_colors(self, override=True): + self.set_color(self.color, self.opacity, override) def init_points(self): # Typically implemented in subclass, unlpess purposefully left blank From 6c39cac62b7a04e71da1e241b47ce5798c847c2a Mon Sep 17 00:00:00 2001 From: YishiMichael <50232075+YishiMichael@users.noreply.github.com> Date: Fri, 28 Jan 2022 01:19:02 +0800 Subject: [PATCH 03/17] Remove redundant attribute --- manimlib/mobject/svg/mtex_mobject.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/manimlib/mobject/svg/mtex_mobject.py b/manimlib/mobject/svg/mtex_mobject.py index b1f34147..77cc9400 100644 --- a/manimlib/mobject/svg/mtex_mobject.py +++ b/manimlib/mobject/svg/mtex_mobject.py @@ -349,8 +349,6 @@ class MTex(VMobject): self.tex_spans_dict = tex_parser.tex_spans_dict fill_color = self.get_fill_color() - stroke_width = self.get_stroke_width() - # Cannot simultaneously be false, so at least one file is generated. require_labelled_tex_file = tex_parser.current_label != 0 require_plain_tex_file = any([ @@ -362,7 +360,7 @@ class MTex(VMobject): if require_labelled_tex_file: labelled_full_tex = self.get_tex_file_body(tex_parser.get_labelled_expression()) - labelled_hash_val = hash((labelled_full_tex, stroke_width)) + labelled_hash_val = hash(labelled_full_tex) if labelled_hash_val in TEX_HASH_TO_MOB_MAP: if not require_plain_tex_file: self.add(*TEX_HASH_TO_MOB_MAP[labelled_hash_val].copy()) @@ -370,10 +368,7 @@ class MTex(VMobject): else: with display_during_execution(f"Writing \"{tex_string}\""): filename = tex_to_svg_file(labelled_full_tex) - labelled_svg_glyphs = _LabelledTex( - filename, - stroke_width=stroke_width - ) + labelled_svg_glyphs = _LabelledTex(filename) self.add(*labelled_svg_glyphs) self.build_submobjects() TEX_HASH_TO_MOB_MAP[labelled_hash_val] = self.copy() @@ -383,17 +378,13 @@ class MTex(VMobject): # require_plain_tex_file == True self.set_submobjects([]) full_tex = self.get_tex_file_body(tex_string) - hash_val = hash((full_tex, fill_color, stroke_width)) + hash_val = hash((full_tex, fill_color)) if hash_val in TEX_HASH_TO_MOB_MAP: self.add(*TEX_HASH_TO_MOB_MAP[hash_val].copy()) return self with display_during_execution(f"Writing \"{tex_string}\""): filename = tex_to_svg_file(full_tex) - svg_glyphs = _PlainTex( - filename, - fill_color=fill_color, - stroke_width=stroke_width - ) + svg_glyphs = _PlainTex(filename, fill_color=fill_color) if require_labelled_tex_file: labelled_svg_mob = TEX_HASH_TO_MOB_MAP[labelled_hash_val] for glyph, labelled_glyph in zip(svg_glyphs, it.chain(*labelled_svg_mob)): From 5d2dcec307c8bf816ab3390cda5fcfb8a6953059 Mon Sep 17 00:00:00 2001 From: YishiMichael Date: Sat, 29 Jan 2022 14:05:52 +0800 Subject: [PATCH 04/17] Fix color-related bugs --- manimlib/mobject/svg/mtex_mobject.py | 107 ++++++++++++++++----------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/manimlib/mobject/svg/mtex_mobject.py b/manimlib/mobject/svg/mtex_mobject.py index 77cc9400..2b399e48 100644 --- a/manimlib/mobject/svg/mtex_mobject.py +++ b/manimlib/mobject/svg/mtex_mobject.py @@ -6,6 +6,7 @@ from types import MethodType from manimlib.mobject.svg.svg_mobject import SVGMobject from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VGroup +from manimlib.utils.color import color_to_int_rgb from manimlib.utils.iterables import adjacent_pairs from manimlib.utils.iterables import remove_list_redundancies from manimlib.utils.tex_file_writing import tex_to_svg_file @@ -28,7 +29,7 @@ def _get_neighbouring_pairs(iterable): return list(adjacent_pairs(iterable))[:-1] -class _PlainTex(SVGMobject): +class _TexSVG(SVGMobject): CONFIG = { "height": None, "path_string_config": { @@ -37,13 +38,6 @@ class _PlainTex(SVGMobject): }, } - -class _LabelledTex(_PlainTex): - def __init__(self, file_name=None, **kwargs): - super().__init__(file_name, **kwargs) - for glyph in self: - glyph.glyph_label = _LabelledTex.color_str_to_label(glyph.fill_color) - @staticmethod def color_str_to_label(color_str): if len(color_str) == 4: @@ -53,6 +47,11 @@ class _LabelledTex(_PlainTex): return 0 return int(color_str[1:], 16) + def parse_labels(self): + for glyph in self: + glyph.glyph_label = _TexSVG.color_str_to_label(glyph.fill_color) + return self + class _TexSpan(object): def __init__(self, script_type, label): @@ -77,10 +76,15 @@ class _TexParser(object): self.brace_index_pairs = self.get_brace_index_pairs() self.existing_color_command_spans = self.get_existing_color_command_spans() self.has_existing_color_commands = any(self.existing_color_command_spans.values()) + self.specified_substring_spans = [] self.add_tex_span((0, len(tex_string))) self.break_up_by_double_braces() self.break_up_by_scripts() self.break_up_by_additional_substrings(additional_substrings) + self.specified_substrings = remove_list_redundancies([ + tex_string[slice(*span_tuple)] + for span_tuple in self.specified_substring_spans + ]) self.check_if_overlap() self.analyse_containing_labels() @@ -164,6 +168,7 @@ class _TexParser(object): span_tuple[1] == prev_span_tuple[1] + 1 ]): self.add_tex_span(span_tuple) + self.specified_substring_spans.append(span_tuple) skip_pair = True def break_up_by_scripts(self): @@ -210,7 +215,9 @@ class _TexParser(object): span_end = script_spans_dict[span_end] if span_begin >= span_end: continue - self.add_tex_span((span_begin, span_end)) + span_tuple = (span_begin, span_end) + self.add_tex_span(span_tuple) + self.specified_substring_spans.append(span_tuple) def check_if_overlap(self): span_tuples = sorted( @@ -242,7 +249,7 @@ class _TexParser(object): if _contains(span_1, span_0): tex_span_1.containing_labels.append(tex_span_0.label) - def get_labelled_expression(self): + def get_labelled_tex_string(self): tex_string = self.tex_string if self.current_label == 0 and not self.has_existing_color_commands: return tex_string @@ -294,7 +301,8 @@ class _TexParser(object): return result.replace(color_command_placeholder, "\\color[RGB]") def raise_tex_parsing_error(self, message): - raise ValueError(f"Failed to parse tex ({message}): \"{self.tex_string}\"") + log.error(f"Failed to parse tex ({message}): \"{self.tex_string}\"") + sys.exit(2) @staticmethod def get_color_related_commands_dict(): @@ -327,7 +335,7 @@ class MTex(VMobject): tex_string = "\\quad" self.tex_string = tex_string - self.generate_tex() + self.generate_mobject() self.set_color_by_tex_to_color_map(self.tex_to_color_map) self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size) @@ -343,61 +351,60 @@ class MTex(VMobject): def get_parser(self): return _TexParser(self.tex_string, self.get_additional_substrings_to_break_up()) - def generate_tex(self): + def generate_mobject(self): tex_string = self.tex_string tex_parser = self.get_parser() self.tex_spans_dict = tex_parser.tex_spans_dict - + self.specified_substrings = tex_parser.specified_substrings fill_color = self.get_fill_color() + # Cannot simultaneously be false, so at least one file is generated. require_labelled_tex_file = tex_parser.current_label != 0 require_plain_tex_file = any([ self.use_plain_tex_file, tex_parser.has_existing_color_commands, - fill_color != "#ffffff", tex_parser.current_label == 0 ]) if require_labelled_tex_file: - labelled_full_tex = self.get_tex_file_body(tex_parser.get_labelled_expression()) + labelled_full_tex = self.get_tex_file_body(tex_parser.get_labelled_tex_string()) labelled_hash_val = hash(labelled_full_tex) if labelled_hash_val in TEX_HASH_TO_MOB_MAP: - if not require_plain_tex_file: - self.add(*TEX_HASH_TO_MOB_MAP[labelled_hash_val].copy()) - return self + self.add(*TEX_HASH_TO_MOB_MAP[labelled_hash_val].copy()) else: with display_during_execution(f"Writing \"{tex_string}\""): filename = tex_to_svg_file(labelled_full_tex) - labelled_svg_glyphs = _LabelledTex(filename) + labelled_svg_glyphs = _TexSVG(filename).parse_labels() self.add(*labelled_svg_glyphs) self.build_submobjects() - TEX_HASH_TO_MOB_MAP[labelled_hash_val] = self.copy() + TEX_HASH_TO_MOB_MAP[labelled_hash_val] = self.copy() if not require_plain_tex_file: + self.set_fill(color=fill_color) return self # require_plain_tex_file == True self.set_submobjects([]) - full_tex = self.get_tex_file_body(tex_string) - hash_val = hash((full_tex, fill_color)) + full_tex = self.get_tex_file_body(tex_string, fill_color=fill_color) + hash_val = hash(full_tex) if hash_val in TEX_HASH_TO_MOB_MAP: self.add(*TEX_HASH_TO_MOB_MAP[hash_val].copy()) - return self - with display_during_execution(f"Writing \"{tex_string}\""): - filename = tex_to_svg_file(full_tex) - svg_glyphs = _PlainTex(filename, fill_color=fill_color) - if require_labelled_tex_file: - labelled_svg_mob = TEX_HASH_TO_MOB_MAP[labelled_hash_val] - for glyph, labelled_glyph in zip(svg_glyphs, it.chain(*labelled_svg_mob)): - glyph.glyph_label = labelled_glyph.glyph_label - else: - for glyph in svg_glyphs: - glyph.glyph_label = 0 - self.add(*svg_glyphs) - self.build_submobjects() - TEX_HASH_TO_MOB_MAP[hash_val] = self.copy() + else: + with display_during_execution(f"Writing \"{tex_string}\""): + filename = tex_to_svg_file(full_tex) + svg_glyphs = _TexSVG(filename) + if require_labelled_tex_file: + labelled_svg_mob = TEX_HASH_TO_MOB_MAP[labelled_hash_val] + for glyph, labelled_glyph in zip(svg_glyphs, it.chain(*labelled_svg_mob)): + glyph.glyph_label = labelled_glyph.glyph_label + else: + for glyph in svg_glyphs: + glyph.glyph_label = 0 + self.add(*svg_glyphs) + self.build_submobjects() + TEX_HASH_TO_MOB_MAP[hash_val] = self.copy() return self - def get_tex_file_body(self, new_tex): + def get_tex_file_body(self, new_tex, fill_color=None): if self.tex_environment: new_tex = "\n".join([ f"\\begin{{{self.tex_environment}}}", @@ -406,6 +413,17 @@ class MTex(VMobject): ]) if self.alignment: new_tex = "\n".join([self.alignment, new_tex]) + if fill_color: + int_rgb = color_to_int_rgb(fill_color) + color_command = "".join([ + "\\color[RGB]", + "{", + ",".join(map(str, int_rgb)), + "}" + ]) + new_tex = "\n".join( + [color_command, new_tex] + ) tex_config = get_tex_config() return tex_config["tex_body"].replace( @@ -564,7 +582,8 @@ class MTex(VMobject): span_tuples = self.find_span_components_of_custom_span(custom_span_tuple) if span_tuples is None: tex = self.tex_string[slice(*custom_span_tuple)] - raise ValueError(f"Failed to get span of tex: \"{tex}\"") + log.error(f"Failed to get span of tex: \"{tex}\"") + sys.exit(2) return self.get_part_by_span_tuples(span_tuples) def get_parts_by_tex(self, tex): @@ -595,7 +614,8 @@ class MTex(VMobject): if submob in part ] if not indices: - raise ValueError("Failed to find part in tex") + log.error("Failed to find part in tex") + sys.exit(2) return indices def indices_of_part_by_tex(self, tex, index=0): @@ -633,11 +653,8 @@ class MTex(VMobject): for span_tuple in self.tex_spans_dict.keys() ]) - def list_tex_strings_of_submobjects(self): - # Work with `index_labels()`. - log.debug(f"Submobjects of \"{self.get_tex()}\":") - for i, submob in enumerate(self.submobjects): - log.debug(f"{i}: \"{submob.get_tex()}\"") + def get_specified_substrings(self): + return self.specified_substrings class MTexText(MTex): From a6675eb043acb62fd3c438e4312fa246c320807a Mon Sep 17 00:00:00 2001 From: YishiMichael Date: Sat, 29 Jan 2022 14:35:27 +0800 Subject: [PATCH 05/17] Some small refactors --- manimlib/mobject/svg/mtex_mobject.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/manimlib/mobject/svg/mtex_mobject.py b/manimlib/mobject/svg/mtex_mobject.py index 2b399e48..14f24d96 100644 --- a/manimlib/mobject/svg/mtex_mobject.py +++ b/manimlib/mobject/svg/mtex_mobject.py @@ -3,6 +3,7 @@ import re import sys from types import MethodType +from manimlib.constants import BLACK from manimlib.mobject.svg.svg_mobject import SVGMobject from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VGroup @@ -31,6 +32,7 @@ def _get_neighbouring_pairs(iterable): class _TexSVG(SVGMobject): CONFIG = { + "color": BLACK, "height": None, "path_string_config": { "should_subdivide_sharp_curves": True, @@ -39,17 +41,14 @@ class _TexSVG(SVGMobject): } @staticmethod - def color_str_to_label(color_str): - if len(color_str) == 4: - # "#RGB" => "#RRGGBB" - color_str = "#" + "".join([c * 2 for c in color_str[1:]]) - if color_str == "#ffffff": - return 0 - return int(color_str[1:], 16) + def color_to_label(fill_color): + r, g, b = color_to_int_rgb(fill_color) + rg = r * 256 + g + return rg * 256 + b def parse_labels(self): for glyph in self: - glyph.glyph_label = _TexSVG.color_str_to_label(glyph.fill_color) + glyph.glyph_label = _TexSVG.color_to_label(glyph.fill_color) return self @@ -90,8 +89,7 @@ class _TexParser(object): @staticmethod def label_to_color_tuple(rgb): - # Get a unique color different from black, - # or the svg file will not include the color information. + # Get a unique color different from black. rg, b = divmod(rgb, 256) r, g = divmod(rg, 256) return r, g, b @@ -306,6 +304,7 @@ class _TexParser(object): @staticmethod def get_color_related_commands_dict(): + # Only list a few commands that are commonly used. return { "\\color": 1, "\\textcolor": 1, From 725155409b133a0330c994ee7021046673eb4aa3 Mon Sep 17 00:00:00 2001 From: YishiMichael Date: Sat, 29 Jan 2022 21:06:54 +0800 Subject: [PATCH 06/17] Some small refactors --- manimlib/mobject/svg/mtex_mobject.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/manimlib/mobject/svg/mtex_mobject.py b/manimlib/mobject/svg/mtex_mobject.py index 14f24d96..fa30c2d3 100644 --- a/manimlib/mobject/svg/mtex_mobject.py +++ b/manimlib/mobject/svg/mtex_mobject.py @@ -237,7 +237,7 @@ class _TexParser(object): f"\"{tex_string[slice(*span_tuple)]}\"" for span_tuple in span_tuple_pair )) - sys.exit(2) + raise ValueError def analyse_containing_labels(self): for span_0, tex_span_0 in self.tex_spans_dict.items(): @@ -299,8 +299,7 @@ class _TexParser(object): return result.replace(color_command_placeholder, "\\color[RGB]") def raise_tex_parsing_error(self, message): - log.error(f"Failed to parse tex ({message}): \"{self.tex_string}\"") - sys.exit(2) + raise ValueError(f"Failed to parse tex ({message}): \"{self.tex_string}\"") @staticmethod def get_color_related_commands_dict(): @@ -581,8 +580,7 @@ class MTex(VMobject): span_tuples = self.find_span_components_of_custom_span(custom_span_tuple) if span_tuples is None: tex = self.tex_string[slice(*custom_span_tuple)] - log.error(f"Failed to get span of tex: \"{tex}\"") - sys.exit(2) + raise ValueError(f"Failed to get span of tex: \"{tex}\"") return self.get_part_by_span_tuples(span_tuples) def get_parts_by_tex(self, tex): @@ -613,20 +611,13 @@ class MTex(VMobject): if submob in part ] if not indices: - log.error("Failed to find part in tex") - sys.exit(2) + raise ValueError("Failed to find part in tex") return indices def indices_of_part_by_tex(self, tex, index=0): part = self.get_part_by_tex(tex, index=index) return self.indices_of_part(part) - def indices_of_all_parts_by_tex(self, tex, index=0): - all_parts = self.get_parts_by_tex(tex) - return list(it.chain(*[ - self.indices_of_part(part) for part in all_parts - ])) - def range_of_part(self, part): indices = self.indices_of_part(part) return range(indices[0], indices[-1] + 1) From 7093f7d02d821961c17e03f651b6ddb326d0e9aa Mon Sep 17 00:00:00 2001 From: YishiMichael <50232075+YishiMichael@users.noreply.github.com> Date: Sun, 30 Jan 2022 12:42:35 +0800 Subject: [PATCH 07/17] Some small refactors to MTex (#1723) --- manimlib/mobject/svg/mtex_mobject.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/manimlib/mobject/svg/mtex_mobject.py b/manimlib/mobject/svg/mtex_mobject.py index fa30c2d3..da09297b 100644 --- a/manimlib/mobject/svg/mtex_mobject.py +++ b/manimlib/mobject/svg/mtex_mobject.py @@ -371,8 +371,8 @@ class MTex(VMobject): self.add(*TEX_HASH_TO_MOB_MAP[labelled_hash_val].copy()) else: with display_during_execution(f"Writing \"{tex_string}\""): - filename = tex_to_svg_file(labelled_full_tex) - labelled_svg_glyphs = _TexSVG(filename).parse_labels() + labelled_svg_glyphs = MTex.get_svg_glyphs(labelled_full_tex) + labelled_svg_glyphs.parse_labels() self.add(*labelled_svg_glyphs) self.build_submobjects() TEX_HASH_TO_MOB_MAP[labelled_hash_val] = self.copy() @@ -388,8 +388,7 @@ class MTex(VMobject): self.add(*TEX_HASH_TO_MOB_MAP[hash_val].copy()) else: with display_during_execution(f"Writing \"{tex_string}\""): - filename = tex_to_svg_file(full_tex) - svg_glyphs = _TexSVG(filename) + svg_glyphs = MTex.get_svg_glyphs(full_tex) if require_labelled_tex_file: labelled_svg_mob = TEX_HASH_TO_MOB_MAP[labelled_hash_val] for glyph, labelled_glyph in zip(svg_glyphs, it.chain(*labelled_svg_mob)): @@ -429,6 +428,11 @@ class MTex(VMobject): new_tex ) + @staticmethod + def get_svg_glyphs(full_tex): + filename = tex_to_svg_file(full_tex) + return _TexSVG(filename) + def build_submobjects(self): if not self.submobjects: return From aa6335cd901390c9b11ccb73fba35d65179b2bed Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Sun, 30 Jan 2022 13:00:57 +0800 Subject: [PATCH 08/17] docs: update changelog for #1719 #1720 #1721 and #1723 --- docs/source/development/changelog.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/source/development/changelog.rst b/docs/source/development/changelog.rst index cf18e9e6..2c9c1cd9 100644 --- a/docs/source/development/changelog.rst +++ b/docs/source/development/changelog.rst @@ -9,6 +9,7 @@ Fixed bugs - `f1996f8 `__: Temporarily fixed ``Lightbulb`` - `#1712 `__: Fixed some bugs of ``SVGMobject`` - `#1717 `__: Fixed some bugs of SVG path string parser +- `#1720 `__: Fixed some bugs of ``MTex`` New Features ^^^^^^^^^^^^ @@ -16,6 +17,8 @@ New Features - `#1704 `__: Added ``lable_buff`` config parameter for ``Brace`` - `#1712 `__: Added support for ``rotate skewX skewY`` transform in SVG - `#1717 `__: Added style support to ``SVGMobject`` +- `#1719 `__: Added parser to