Some refactors

This commit is contained in:
YishiMichael 2022-04-11 23:44:33 +08:00
parent 36d62ae1a3
commit 12bfe88f40
No known key found for this signature in database
GPG key ID: EC615C0C5A86BC80
3 changed files with 142 additions and 164 deletions

View file

@ -4,12 +4,14 @@ import re
import colour
import itertools as it
from typing import Iterable, Union, Sequence
from abc import abstractmethod
from abc import ABC, abstractmethod
from manimlib.constants import WHITE
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.color import color_to_int_rgb
from manimlib.utils.color import color_to_rgb
from manimlib.utils.color import rgb_to_hex
from manimlib.utils.config_ops import digest_config
from manimlib.utils.iterables import remove_list_redundancies
@ -34,36 +36,39 @@ class _StringSVG(SVGMobject):
}
class LabelledString(_StringSVG):
class LabelledString(_StringSVG, ABC):
"""
An abstract base class for `MTex` and `MarkupText`
"""
CONFIG = {
"base_color": None,
"base_color": WHITE,
"use_plain_file": False,
"isolate": [],
}
def __init__(self, string: str, **kwargs):
self.string = string
reserved_svg_default = kwargs.pop("svg_default", {})
digest_config(self, kwargs)
self.reserved_svg_default = reserved_svg_default
self.base_color = self.base_color \
or reserved_svg_default.get("color", None) \
or reserved_svg_default.get("fill_color", None) \
# Convert `base_color` to hex code.
self.base_color = rgb_to_hex(color_to_rgb(
self.base_color \
or self.svg_default.get("color", None) \
or self.svg_default.get("fill_color", None) \
or WHITE
))
self.svg_default["fill_color"] = BLACK
self.pre_parse()
self.parse()
super().__init__(**kwargs)
super().__init__()
self.post_parse()
def get_file_path(self) -> str:
return self.get_file_path_(use_plain_file=False)
def get_file_path_(self, use_plain_file: bool) -> str:
content = self.get_decorated_string(use_plain_file=use_plain_file)
content = self.get_content(use_plain_file)
return self.get_file_path_by_content(content)
@abstractmethod
@ -77,15 +82,11 @@ class LabelledString(_StringSVG):
self.color_to_label(submob.get_fill_color())
for submob in self.submobjects
]
if any([
self.use_plain_file,
self.reserved_svg_default,
self.has_predefined_colors
]):
if self.use_plain_file or self.has_predefined_local_colors:
file_path = self.get_file_path_(use_plain_file=True)
plain_svg = _StringSVG(
file_path,
svg_default=self.reserved_svg_default,
svg_default=self.svg_default,
path_string_config=self.path_string_config
)
self.set_submobjects(plain_svg.submobjects)
@ -101,7 +102,9 @@ class LabelledString(_StringSVG):
def parse(self) -> None:
self.command_repl_items = self.get_command_repl_items()
self.command_spans = self.get_command_spans()
self.ignored_spans = self.get_ignored_spans()
self.extra_entity_spans = self.get_extra_entity_spans()
self.entity_spans = self.get_entity_spans()
self.extra_ignored_spans = self.get_extra_ignored_spans()
self.skipped_spans = self.get_skipped_spans()
self.internal_specified_spans = self.get_internal_specified_spans()
self.external_specified_spans = self.get_external_specified_spans()
@ -216,7 +219,7 @@ class LabelledString(_StringSVG):
return sorted_seq[index + index_shift]
@staticmethod
def get_span_replacement_dict(
def generate_span_repl_dict(
inserted_string_pairs: list[tuple[Span, tuple[str, str]]],
other_repl_items: list[tuple[Span, str]]
) -> dict[Span, str]:
@ -290,20 +293,20 @@ class LabelledString(_StringSVG):
r, g = divmod(rg, 256)
return r, g, b
@staticmethod
def int_to_hex(rgb_int: int) -> str:
return "#{:06x}".format(rgb_int).upper()
@staticmethod
def hex_to_int(rgb_hex: str) -> int:
return int(rgb_hex[1:], 16)
@staticmethod
def color_to_label(color: ManimColor) -> int:
rgb_tuple = color_to_int_rgb(color)
rgb = LabelledString.rgb_to_int(rgb_tuple)
return rgb - 1
@abstractmethod
def get_begin_color_command_str(int_rgb: int) -> str:
return ""
@abstractmethod
def get_end_color_command_str() -> str:
return ""
# Parsing
@abstractmethod
@ -313,14 +316,25 @@ class LabelledString(_StringSVG):
def get_command_spans(self) -> list[Span]:
return [cmd_span for cmd_span, _ in self.command_repl_items]
def get_ignored_spans(self) -> list[int]:
@abstractmethod
def get_extra_entity_spans(self) -> list[Span]:
return []
def get_entity_spans(self) -> list[Span]:
return list(it.chain(
self.command_spans,
self.extra_entity_spans
))
@abstractmethod
def get_extra_ignored_spans(self) -> list[int]:
return []
def get_skipped_spans(self) -> list[Span]:
return list(it.chain(
self.find_spans(r"\s"),
self.command_spans,
self.ignored_spans
self.extra_ignored_spans
))
def shrink_span(self, span: Span) -> Span:
@ -344,7 +358,11 @@ class LabelledString(_StringSVG):
self.find_substrs(self.isolate)
))
shrinked_spans = list(filter(
lambda span: span[0] < span[1],
lambda span: span[0] < span[1] and not any([
entity_span[0] < index < entity_span[1]
for index in span
for entity_span in self.entity_spans
]),
[self.shrink_span(span) for span in spans]
))
return remove_list_redundancies(shrinked_spans)
@ -363,36 +381,11 @@ class LabelledString(_StringSVG):
)
@abstractmethod
def get_inserted_string_pairs(
self, use_plain_file: bool
) -> list[tuple[Span, tuple[str, str]]]:
return []
def get_content(self, use_plain_file: bool) -> str:
return ""
@abstractmethod
def get_other_repl_items(
self, use_plain_file: bool
) -> list[tuple[Span, str]]:
return []
def get_decorated_string(self, use_plain_file: bool) -> str:
span_repl_dict = self.get_span_replacement_dict(
self.get_inserted_string_pairs(use_plain_file),
self.get_other_repl_items(use_plain_file)
)
result = self.get_replaced_substr(self.full_span, span_repl_dict)
if not use_plain_file:
return result
return "".join([
self.get_begin_color_command_str(
self.rgb_to_int(color_to_int_rgb(self.base_color))
),
result,
self.get_end_color_command_str()
])
@abstractmethod
def has_predefined_colors(self) -> bool:
def has_predefined_local_colors(self) -> bool:
return False
# Post-parsing

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import itertools as it
import colour
from typing import Union, Sequence
@ -32,7 +33,7 @@ class MTex(LabelledString):
def __init__(self, tex_string: str, **kwargs):
# Prevent from passing an empty string.
if not tex_string:
tex_string = "\\quad"
tex_string = "\\\\"
self.tex_string = tex_string
super().__init__(tex_string, **kwargs)
@ -55,30 +56,14 @@ class MTex(LabelledString):
)
def get_file_path_by_content(self, content: str) -> str:
full_tex = self.get_tex_file_body(content)
with display_during_execution(f"Writing \"{self.tex_string}\""):
file_path = self.tex_to_svg_file_path(full_tex)
return file_path
def get_tex_file_body(self, content: str) -> str:
if self.tex_environment:
content = "\n".join([
f"\\begin{{{self.tex_environment}}}",
content,
f"\\end{{{self.tex_environment}}}"
])
if self.alignment:
content = "\n".join([self.alignment, content])
tex_config = get_tex_config()
return tex_config["tex_body"].replace(
full_tex = tex_config["tex_body"].replace(
tex_config["text_to_replace"],
content
)
@staticmethod
def tex_to_svg_file_path(tex_file_content: str) -> str:
return tex_to_svg_file(tex_file_content)
with display_during_execution(f"Writing \"{self.tex_string}\""):
file_path = tex_to_svg_file(full_tex)
return file_path
def pre_parse(self) -> None:
super().pre_parse()
@ -91,29 +76,23 @@ class MTex(LabelledString):
# Toolkits
@staticmethod
def get_begin_color_command_str(rgb_int: int) -> str:
def get_color_command_str(rgb_int: int) -> str:
rgb_tuple = MTex.int_to_rgb(rgb_int)
return "".join([
"{{",
"\\color[RGB]",
"{",
",".join(map(str, rgb_tuple)),
"}"
])
@staticmethod
def get_end_color_command_str() -> str:
return "}}"
# Pre-parsing
def get_backslash_indices(self) -> list[int]:
# Newlines (`\\`) don't count.
return [
span[1] - 1
# The latter of `\\` doesn't count.
return list(it.chain(*[
range(span[0], span[1], 2)
for span in self.find_spans(r"\\+")
if (span[1] - span[0]) % 2 == 1
]
]))
def get_unescaped_char_spans(self, chars: str):
return sorted(filter(
@ -215,7 +194,13 @@ class MTex(LabelledString):
result.append(((span_begin, span_end), repl_str))
return result
def get_ignored_spans(self) -> list[int]:
def get_extra_entity_spans(self) -> list[Span]:
return [
self.match(r"\\([a-zA-Z]+|.)", pos=index).span()
for index in self.backslash_indices
]
def get_extra_ignored_spans(self) -> list[int]:
return self.script_char_spans.copy()
def get_internal_specified_spans(self) -> list[Span]:
@ -256,35 +241,46 @@ class MTex(LabelledString):
result.append(shrinked_span)
return result
def get_inserted_string_pairs(
self, use_plain_file: bool
) -> list[tuple[Span, tuple[str, str]]]:
def get_content(self, use_plain_file: bool) -> str:
if use_plain_file:
return []
span_repl_dict = {}
else:
extended_label_span_list = [
span
if span in self.script_content_spans
else (span[0], self.rslide(span[1], self.script_spans))
for span in self.label_span_list
]
inserted_string_pairs = [
(span, (
"{{" + self.get_color_command_str(label + 1),
"}}"
))
for label, span in enumerate(extended_label_span_list)
]
span_repl_dict = self.generate_span_repl_dict(
inserted_string_pairs,
self.command_repl_items
)
result = self.get_replaced_substr(self.full_span, span_repl_dict)
extended_label_span_list = [
span
if span in self.script_content_spans
else (span[0], self.rslide(span[1], self.script_spans))
for span in self.label_span_list
]
return [
(span, (
self.get_begin_color_command_str(label + 1),
self.get_end_color_command_str()
))
for label, span in enumerate(extended_label_span_list)
]
def get_other_repl_items(
self, use_plain_file: bool
) -> list[tuple[Span, str]]:
if self.tex_environment:
result = "\n".join([
f"\\begin{{{self.tex_environment}}}",
result,
f"\\end{{{self.tex_environment}}}"
])
if self.alignment:
result = "\n".join([self.alignment, result])
if use_plain_file:
return []
return self.command_repl_items.copy()
result = "\n".join([
self.get_color_command_str(self.hex_to_int(self.base_color)),
result
])
return result
@property
def has_predefined_colors(self) -> bool:
def has_predefined_local_colors(self) -> bool:
return bool(self.command_repl_items)
# Post-parsing

View file

@ -254,27 +254,6 @@ class MarkupText(LabelledString):
for key, val in attr_dict.items()
])
@staticmethod
def get_begin_tag_str(attr_dict: dict[str, str]) -> str:
return f"<span {MarkupText.get_attr_dict_str(attr_dict)}>"
@staticmethod
def get_end_tag_str() -> str:
return "</span>"
@staticmethod
def rgb_int_to_hex(rgb_int: int) -> str:
return "#{:06x}".format(rgb_int).upper()
@staticmethod
def get_begin_color_command_str(rgb_int: int):
color_hex = MarkupText.rgb_int_to_hex(rgb_int)
return MarkupText.get_begin_tag_str({"foreground": color_hex})
@staticmethod
def get_end_color_command_str() -> str:
return MarkupText.get_end_tag_str()
@staticmethod
def merge_attr_dicts(
attr_dict_items: list[Span, str, typing.Any]
@ -452,6 +431,14 @@ class MarkupText(LabelledString):
]
return result
def get_extra_entity_spans(self) -> list[Span]:
if not self.is_markup:
return []
return self.find_spans(r"&.*?;")
def get_extra_ignored_spans(self) -> list[int]:
return []
def get_internal_specified_spans(self) -> list[Span]:
return [span for span, _ in self.local_dicts_from_markup]
@ -464,13 +451,10 @@ class MarkupText(LabelledString):
self.find_spans(r"\b"),
self.specified_spans
))))
entity_spans = self.command_spans.copy()
if self.is_markup:
entity_spans += self.find_spans(r"&.*?;")
breakup_indices = sorted(filter(
lambda index: not any([
span[0] < index < span[1]
for span in entity_spans
for span in self.entity_spans
]),
breakup_indices
))
@ -479,40 +463,45 @@ class MarkupText(LabelledString):
self.get_neighbouring_pairs(breakup_indices)
))
def get_inserted_string_pairs(
self, use_plain_file: bool
) -> list[tuple[Span, tuple[str, str]]]:
if not use_plain_file:
def get_content(self, use_plain_file: bool) -> str:
if use_plain_file:
attr_dict_items = [
(span, {
key: BLACK if key in COLOR_RELATED_KEYS else val
for key, val in attr_dict.items()
})
for span, attr_dict in self.predefined_attr_dicts
] + [
(span, {"foreground": self.rgb_int_to_hex(label + 1)})
for label, span in enumerate(self.label_span_list)
(self.full_span, {"foreground": self.base_color}),
*self.predefined_attr_dicts,
*[
(span, {})
for span in self.label_span_list
]
]
else:
attr_dict_items = self.predefined_attr_dicts + [
(span, {})
for span in self.label_span_list
attr_dict_items = [
(self.full_span, {"foreground": BLACK}),
*[
(span, {
key: BLACK if key in COLOR_RELATED_KEYS else val
for key, val in attr_dict.items()
})
for span, attr_dict in self.predefined_attr_dicts
],
*[
(span, {"foreground": self.int_to_hex(label + 1)})
for label, span in enumerate(self.label_span_list)
]
]
return [
inserted_string_pairs = [
(span, (
self.get_begin_tag_str(attr_dict),
self.get_end_tag_str()
f"<span {self.get_attr_dict_str(attr_dict)}>",
"</span>"
))
for span, attr_dict in self.merge_attr_dicts(attr_dict_items)
]
def get_other_repl_items(
self, use_plain_file: bool
) -> list[tuple[Span, str]]:
return self.command_repl_items.copy()
span_repl_dict = self.generate_span_repl_dict(
inserted_string_pairs, self.command_repl_items
)
return self.get_replaced_substr(self.full_span, span_repl_dict)
@property
def has_predefined_colors(self) -> bool:
def has_predefined_local_colors(self) -> bool:
return any([
key in COLOR_RELATED_KEYS
for _, attr_dict in self.predefined_attr_dicts