Small refactors on StringMobject and relevant classes

This commit is contained in:
YishiMichael
2022-05-28 21:43:37 +08:00
parent 59eba943e5
commit f0447d7739
9 changed files with 669 additions and 737 deletions

View File

@ -167,83 +167,95 @@ class TransformMatchingStrings(AnimationGroup):
digest_config(self, kwargs) digest_config(self, kwargs)
assert isinstance(source, StringMobject) assert isinstance(source, StringMobject)
assert isinstance(target, StringMobject) assert isinstance(target, StringMobject)
anims = []
source_indices = list(range(len(source.labels)))
target_indices = list(range(len(target.labels)))
def get_filtered_indices_lists(indices_lists, rest_indices): def get_matched_indices_lists(*part_items_list):
part_items_list_len = len(part_items_list)
indexed_part_items = sorted(it.chain(*[
[
(substr, items_index, indices_list)
for substr, indices_list in part_items
]
for items_index, part_items in enumerate(part_items_list)
]))
grouped_part_items = [
(substr, [
[indices_lists for _, _, indices_lists in grouper_2]
for _, grouper_2 in it.groupby(
grouper_1, key=lambda t: t[1]
)
])
for substr, grouper_1 in it.groupby(
indexed_part_items, key=lambda t: t[0]
)
]
return [
tuple(indices_lists_list)
for _, indices_lists_list in sorted(filter(
lambda t: t[0] and len(t[1]) == part_items_list_len,
grouped_part_items
), key=lambda t: len(t[0]), reverse=True)
]
def get_filtered_indices_lists(indices_lists, used_indices):
result = [] result = []
used = []
for indices_list in indices_lists: for indices_list in indices_lists:
if not indices_list: if not all(
continue index not in used_indices and index not in used
if not all(index in rest_indices for index in indices_list): for index in indices_list
):
continue continue
result.append(indices_list) result.append(indices_list)
for index in indices_list: used.extend(indices_list)
rest_indices.remove(index) return result, used
return result
def add_anims(anim_class, indices_lists_pairs): anim_class_items = [
for source_indices_lists, target_indices_lists in indices_lists_pairs: (ReplacementTransform, [
source_indices_lists = get_filtered_indices_lists(
source_indices_lists, source_indices
)
target_indices_lists = get_filtered_indices_lists(
target_indices_lists, target_indices
)
if not source_indices_lists or not target_indices_lists:
source_indices.extend(it.chain(*source_indices_lists))
target_indices.extend(it.chain(*target_indices_lists))
continue
anims.append(anim_class(
source.build_parts_from_indices_lists(source_indices_lists),
target.build_parts_from_indices_lists(target_indices_lists),
**kwargs
))
def get_substr_to_indices_lists_map(part_items):
result = {}
for substr, indices_list in part_items:
if substr not in result:
result[substr] = []
result[substr].append(indices_list)
return result
def add_anims_from(anim_class, func):
source_substr_map = get_substr_to_indices_lists_map(func(source))
target_substr_map = get_substr_to_indices_lists_map(func(target))
common_substrings = sorted([
s for s in source_substr_map if s and s in target_substr_map
], key=len, reverse=True)
add_anims(
anim_class,
[
(source_substr_map[substr], target_substr_map[substr])
for substr in common_substrings
]
)
add_anims(
ReplacementTransform,
[
( (
source.get_submob_indices_lists_by_selector(k), source.get_submob_indices_lists_by_selector(k),
target.get_submob_indices_lists_by_selector(v) target.get_submob_indices_lists_by_selector(v)
) )
for k, v in self.key_map.items() for k, v in self.key_map.items()
] ]),
) (FadeTransformPieces, get_matched_indices_lists(
add_anims_from( source.get_specified_part_items(),
FadeTransformPieces, target.get_specified_part_items()
StringMobject.get_specified_part_items )),
) (FadeTransformPieces, get_matched_indices_lists(
add_anims_from( source.get_group_part_items(),
FadeTransformPieces, target.get_group_part_items()
StringMobject.get_group_part_items ))
) ]
rest_source = VGroup(*[source[index] for index in source_indices]) anims = []
rest_target = VGroup(*[target[index] for index in target_indices]) source_used_indices = []
target_used_indices = []
for anim_class, pairs in anim_class_items:
for source_indices_lists, target_indices_lists in pairs:
source_filtered, source_used = get_filtered_indices_lists(
source_indices_lists, source_used_indices
)
target_filtered, target_used = get_filtered_indices_lists(
target_indices_lists, target_used_indices
)
if not source_filtered or not target_filtered:
continue
anims.append(anim_class(
source.build_parts_from_indices_lists(source_filtered),
target.build_parts_from_indices_lists(target_filtered),
**kwargs
))
source_used_indices.extend(source_used)
target_used_indices.extend(target_used)
rest_source = VGroup(*[
submob for index, submob in enumerate(source.submobjects)
if index not in source_used_indices
])
rest_target = VGroup(*[
submob for index, submob in enumerate(target.submobjects)
if index not in target_used_indices
])
if self.transform_mismatches: if self.transform_mismatches:
anims.append( anims.append(
ReplacementTransform(rest_source, rest_target, **kwargs) ReplacementTransform(rest_source, rest_target, **kwargs)

View File

@ -18,6 +18,8 @@ directories:
temporary_storage: "" temporary_storage: ""
universal_import_line: "from manimlib import *" universal_import_line: "from manimlib import *"
style: style:
# "latex" | "xelatex"
tex_compiler: "latex"
tex_font: "default" tex_font: "default"
font: "Consolas" font: "Consolas"
text_alignment: "LEFT" text_alignment: "LEFT"
@ -41,4 +43,4 @@ camera_resolutions:
high: "1920x1080" high: "1920x1080"
4k: "3840x2160" 4k: "3840x2160"
default_resolution: "high" default_resolution: "high"
fps: 30 fps: 30

View File

@ -40,6 +40,16 @@ class MTex(StringMobject):
"additional_preamble": "", "additional_preamble": "",
} }
CMD_PATTERN = r"\\(?:[a-zA-Z]+|.)|[_^{}]"
FLAG_DICT = {
r"{": 1,
r"}": -1
}
CONTENT_REPL = {}
MATCH_REPL = {
r"[_^{}]": ""
}
def __init__(self, tex_string: str, **kwargs): def __init__(self, tex_string: str, **kwargs):
# Prevent from passing an empty string. # Prevent from passing an empty string.
if not tex_string.strip(): if not tex_string.strip():
@ -75,44 +85,32 @@ class MTex(StringMobject):
# Parsing # Parsing
def get_cmd_spans(self) -> list[Span]: def get_internal_specified_items(
return self.find_spans(r"\\(?:[a-zA-Z]+|\s|\S)|[_^{}]")
def get_substr_flag(self, substr: str) -> int:
return {"{": 1, "}": -1}.get(substr, 0)
def get_repl_substr_for_content(self, substr: str) -> str:
return substr
def get_repl_substr_for_matching(self, substr: str) -> str:
return substr if substr.startswith("\\") else ""
def get_specified_items(
self, cmd_span_pairs: list[tuple[Span, Span]] self, cmd_span_pairs: list[tuple[Span, Span]]
) -> list[tuple[Span, dict[str, str]]]: ) -> list[tuple[Span, dict[str, str]]]:
cmd_content_spans = [ cmd_content_spans = [
(span_begin, span_end) (span_begin, span_end)
for (_, span_begin), (span_end, _) in cmd_span_pairs for (_, span_begin), (span_end, _) in cmd_span_pairs
] ]
specified_spans = [ return [
*[ (cmd_content_spans[range_begin], {})
cmd_content_spans[range_begin] for _, (range_begin, range_end) in self.group_neighbours([
for _, (range_begin, range_end) in self.compress_neighbours([ (span_begin + index, span_end - index)
(span_begin + index, span_end - index) for index, (span_begin, span_end) in enumerate(
for index, (span_begin, span_end) in enumerate( cmd_content_spans
cmd_content_spans )
) ])
]) if range_end - range_begin >= 2
if range_end - range_begin >= 2 ]
],
*[ def get_external_specified_items(
span self
for selector in self.tex_to_color_map ) -> list[tuple[Span, dict[str, str]]]:
for span in self.find_spans_by_selector(selector) return [
], (span, {})
*self.find_spans_by_selector(self.isolate) for selector in self.tex_to_color_map
for span in self.find_spans_by_selector(selector)
] ]
return [(span, {}) for span in specified_spans]
@staticmethod @staticmethod
def get_color_cmd_str(rgb_hex: str) -> str: def get_color_cmd_str(rgb_hex: str) -> str:
@ -156,8 +154,8 @@ class MTex(StringMobject):
def get_parts_by_tex(self, selector: Selector) -> VGroup: def get_parts_by_tex(self, selector: Selector) -> VGroup:
return self.select_parts(selector) return self.select_parts(selector)
def get_part_by_tex(self, selector: Selector) -> VGroup: def get_part_by_tex(self, selector: Selector, **kwargs) -> VGroup:
return self.select_part(selector) return self.select_part(selector, **kwargs)
def set_color_by_tex(self, selector: Selector, color: ManimColor): def set_color_by_tex(self, selector: Selector, color: ManimColor):
return self.set_parts_color(selector, color) return self.set_parts_color(selector, color)

View File

@ -18,7 +18,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from colour import Color from colour import Color
from typing import Iterable, Sequence, TypeVar, Union from typing import Callable, Iterable, TypeVar, Union
ManimColor = Union[str, Color] ManimColor = Union[str, Color]
Span = tuple[int, int] Span = tuple[int, int]
@ -66,6 +66,11 @@ class StringMobject(SVGMobject, ABC):
"isolate": (), "isolate": (),
} }
CMD_PATTERN: str | None = None
FLAG_DICT: dict[str, int] = {}
CONTENT_REPL: dict[str, str | Callable[[re.Match], str]] = {}
MATCH_REPL: dict[str, str | Callable[[re.Match], str]] = {}
def __init__(self, string: str, **kwargs): def __init__(self, string: str, **kwargs):
self.string = string self.string = string
digest_config(self, kwargs) digest_config(self, kwargs)
@ -153,21 +158,18 @@ class StringMobject(SVGMobject, ABC):
# Toolkits # Toolkits
def get_substr(self, span: Span) -> str:
return self.string[slice(*span)]
def find_spans(self, pattern: str | re.Pattern) -> list[Span]:
return [
match_obj.span()
for match_obj in re.finditer(pattern, self.string)
]
def find_spans_by_selector(self, selector: Selector) -> list[Span]: def find_spans_by_selector(self, selector: Selector) -> list[Span]:
def find_spans_by_single_selector(sel): def find_spans_by_single_selector(sel):
if isinstance(sel, str): if isinstance(sel, str):
return self.find_spans(re.escape(sel)) return [
match_obj.span()
for match_obj in re.finditer(re.escape(sel), self.string)
]
if isinstance(sel, re.Pattern): if isinstance(sel, re.Pattern):
return self.find_spans(sel) return [
match_obj.span()
for match_obj in sel.finditer(self.string)
]
if isinstance(sel, tuple) and len(sel) == 2 and all( if isinstance(sel, tuple) and len(sel) == 2 and all(
isinstance(index, int) or index is None isinstance(index, int) or index is None
for index in sel for index in sel
@ -191,24 +193,59 @@ class StringMobject(SVGMobject, ABC):
result.extend(spans) result.extend(spans)
return result return result
@staticmethod def get_substr(self, span: Span) -> str:
def get_neighbouring_pairs(vals: Sequence[T]) -> list[tuple[T, T]]: return self.string[slice(*span)]
return list(zip(vals[:-1], vals[1:]))
@staticmethod @staticmethod
def compress_neighbours(vals: Sequence[T]) -> list[tuple[T, Span]]: def get_substr_matched_obj(
substr: str, match_dict: dict[str, T]
) -> tuple[re.Match, T] | None:
for pattern, val in match_dict.items():
match_obj = re.fullmatch(pattern, substr, re.S)
if match_obj is None:
continue
return match_obj, val
return None
@staticmethod
def get_substr_matched_val(
substr: str, match_dict: dict[str, T], default: T
) -> T:
obj = StringMobject.get_substr_matched_obj(substr, match_dict)
if obj is None:
return default
_, val = obj
return val
@staticmethod
def get_substr_matched_str(
substr: str, match_dict: dict[str, str | Callable[[re.Match], str]]
) -> str:
obj = StringMobject.get_substr_matched_obj(substr, match_dict)
if obj is None:
return substr
match_obj, val = obj
if isinstance(val, str):
return val
return val(match_obj)
@staticmethod
def get_neighbouring_pairs(vals: Iterable[T]) -> list[tuple[T, T]]:
val_list = list(vals)
return list(zip(val_list[:-1], val_list[1:]))
@staticmethod
def group_neighbours(vals: Iterable[T]) -> list[tuple[T, Span]]:
if not vals: if not vals:
return [] return []
unique_vals = [vals[0]] unique_vals, range_lens = zip(*(
indices = [0] (val, len(list(grouper)))
for index, val in enumerate(vals): for val, grouper in it.groupby(vals)
if val == unique_vals[-1]: ))
continue val_ranges = StringMobject.get_neighbouring_pairs(
unique_vals.append(val) [0, *it.accumulate(range_lens)]
indices.append(index) )
indices.append(len(vals))
val_ranges = StringMobject.get_neighbouring_pairs(indices)
return list(zip(unique_vals, val_ranges)) return list(zip(unique_vals, val_ranges))
@staticmethod @staticmethod
@ -228,18 +265,6 @@ class StringMobject(SVGMobject, ABC):
(*span_ends, universal_span[1]) (*span_ends, universal_span[1])
)) ))
def replace_substr(self, span: Span, repl_items: list[Span, str]):
if not repl_items:
return self.get_substr(span)
repl_spans, repl_strs = zip(*sorted(repl_items, key=lambda t: t[0]))
pieces = [
self.get_substr(piece_span)
for piece_span in self.get_complement_spans(span, repl_spans)
]
repl_strs = [*repl_strs, ""]
return "".join(it.chain(*zip(pieces, repl_strs)))
@staticmethod @staticmethod
def color_to_hex(color: ManimColor) -> str: def color_to_hex(color: ManimColor) -> str:
return rgb_to_hex(color_to_rgb(color)) return rgb_to_hex(color_to_rgb(color))
@ -255,12 +280,26 @@ class StringMobject(SVGMobject, ABC):
# Parsing # Parsing
def parse(self) -> None: def parse(self) -> None:
cmd_spans = self.get_cmd_spans() pattern = self.CMD_PATTERN
cmd_spans = [] if pattern is None else [
match_obj.span()
for match_obj in re.finditer(pattern, self.string, re.S)
]
cmd_substrs = [self.get_substr(span) for span in cmd_spans] cmd_substrs = [self.get_substr(span) for span in cmd_spans]
flags = [self.get_substr_flag(substr) for substr in cmd_substrs] flags = [
specified_items = self.get_specified_items( self.get_substr_matched_val(substr, self.FLAG_DICT, 0)
self.get_cmd_span_pairs(cmd_spans, flags) for substr in cmd_substrs
) ]
specified_items = [
*self.get_internal_specified_items(
self.get_cmd_span_pairs(cmd_spans, flags)
),
*self.get_external_specified_items(),
*[
(span, {})
for span in self.find_spans_by_selector(self.isolate)
]
]
split_items = [ split_items = [
(span, attr_dict) (span, attr_dict)
for specified_span, attr_dict in specified_items for specified_span, attr_dict in specified_items
@ -273,31 +312,15 @@ class StringMobject(SVGMobject, ABC):
self.split_items = split_items self.split_items = split_items
self.labelled_spans = [span for span, _ in split_items] self.labelled_spans = [span for span, _ in split_items]
self.cmd_repl_items_for_content = [ self.cmd_repl_items_for_content = [
(span, self.get_repl_substr_for_content(substr)) (span, self.get_substr_matched_str(substr, self.CONTENT_REPL))
for span, substr in zip(cmd_spans, cmd_substrs) for span, substr in zip(cmd_spans, cmd_substrs)
] ]
self.cmd_repl_items_for_matching = [ self.cmd_repl_items_for_matching = [
(span, self.get_repl_substr_for_matching(substr)) (span, self.get_substr_matched_str(substr, self.MATCH_REPL))
for span, substr in zip(cmd_spans, cmd_substrs) for span, substr in zip(cmd_spans, cmd_substrs)
] ]
self.check_overlapping() self.check_overlapping()
@abstractmethod
def get_cmd_spans(self) -> list[Span]:
return []
@abstractmethod
def get_substr_flag(self, substr: str) -> int:
return 0
@abstractmethod
def get_repl_substr_for_content(self, substr: str) -> str:
return ""
@abstractmethod
def get_repl_substr_for_matching(self, substr: str) -> str:
return ""
@staticmethod @staticmethod
def get_cmd_span_pairs( def get_cmd_span_pairs(
cmd_spans: list[Span], flags: list[int] cmd_spans: list[Span], flags: list[int]
@ -317,11 +340,17 @@ class StringMobject(SVGMobject, ABC):
return result return result
@abstractmethod @abstractmethod
def get_specified_items( def get_internal_specified_items(
self, cmd_span_pairs: list[tuple[Span, Span]] self, cmd_span_pairs: list[tuple[Span, Span]]
) -> list[tuple[Span, dict[str, str]]]: ) -> list[tuple[Span, dict[str, str]]]:
return [] return []
@abstractmethod
def get_external_specified_items(
self
) -> list[tuple[Span, dict[str, str]]]:
return []
def split_span_by_levels( def split_span_by_levels(
self, arbitrary_span: Span, cmd_spans: list[Span], flags: list[int] self, arbitrary_span: Span, cmd_spans: list[Span], flags: list[int]
) -> list[Span]: ) -> list[Span]:
@ -387,6 +416,18 @@ class StringMobject(SVGMobject, ABC):
) -> tuple[str, str]: ) -> tuple[str, str]:
return "", "" return "", ""
def replace_substr(self, span: Span, repl_items: list[Span, str]):
if not repl_items:
return self.get_substr(span)
repl_spans, repl_strs = zip(*sorted(repl_items, key=lambda t: t[0]))
pieces = [
self.get_substr(piece_span)
for piece_span in self.get_complement_spans(span, repl_spans)
]
repl_strs = [*repl_strs, ""]
return "".join(it.chain(*zip(pieces, repl_strs)))
def get_content(self, is_labelled: bool) -> str: def get_content(self, is_labelled: bool) -> str:
inserted_str_pairs = [ inserted_str_pairs = [
(span, self.get_cmd_str_pair( (span, self.get_cmd_str_pair(
@ -446,7 +487,7 @@ class StringMobject(SVGMobject, ABC):
return [] return []
group_labels, labelled_submob_ranges = zip( group_labels, labelled_submob_ranges = zip(
*self.compress_neighbours(self.labels) *self.group_neighbours(self.labels)
) )
ordered_spans = [ ordered_spans = [
self.labelled_spans[label] if label != -1 else self.full_span self.labelled_spans[label] if label != -1 else self.full_span

View File

@ -122,7 +122,8 @@ class SVGMobject(VMobject):
if k in style_keys if k in style_keys
} }
new_root = ET.Element("svg", {}) # TODO: width, height # Ignore other attributes in case that svgelements cannot parse them
new_root = ET.Element("svg", {})
config_style_node = ET.SubElement(new_root, "g", config_style_attrs) config_style_node = ET.SubElement(new_root, "g", config_style_attrs)
root_style_node = ET.SubElement(config_style_node, "g", style_attrs) root_style_node = ET.SubElement(config_style_node, "g", style_attrs)
root_style_node.extend(root) root_style_node.extend(root)

View File

@ -61,9 +61,8 @@ class _Alignment:
self.value = _Alignment.VAL_DICT[s.upper()] self.value = _Alignment.VAL_DICT[s.upper()]
class MarkupText(StringMobject): class Text(StringMobject):
CONFIG = { CONFIG = {
"is_markup": True,
"font_size": 48, "font_size": 48,
"lsh": None, "lsh": None,
"justify": False, "justify": False,
@ -85,28 +84,16 @@ class MarkupText(StringMobject):
"isolate": (re.compile(r"[a-zA-Z]+"), re.compile(r"\S+")), "isolate": (re.compile(r"[a-zA-Z]+"), re.compile(r"\S+")),
} }
# See https://docs.gtk.org/Pango/pango_markup.html CMD_PATTERN = r"""[<>&"']"""
MARKUP_COLOR_KEYS = { FLAG_DICT = {}
"foreground": False, CONTENT_REPL = {
"fgcolor": False, r"<": "&lt;",
"color": False, r">": "&gt;",
"background": True, r"&": "&amp;",
"bgcolor": True, r"\"": "&quot;",
"underline_color": True, r"'": "&apos;"
"overline_color": True,
"strikethrough_color": True,
}
MARKUP_TAGS = {
"b": {"font_weight": "bold"},
"big": {"font_size": "larger"},
"i": {"font_style": "italic"},
"s": {"strikethrough": "true"},
"sub": {"baseline_shift": "subscript", "font_scale": "subscript"},
"sup": {"baseline_shift": "superscript", "font_scale": "superscript"},
"small": {"font_size": "smaller"},
"tt": {"font_family": "monospace"},
"u": {"underline": "single"},
} }
MATCH_REPL = {}
def __init__(self, text: str, **kwargs): def __init__(self, text: str, **kwargs):
self.full2short(kwargs) self.full2short(kwargs)
@ -116,8 +103,6 @@ class MarkupText(StringMobject):
self.font = get_customization()["style"]["font"] self.font = get_customization()["style"]["font"]
if not self.alignment: if not self.alignment:
self.alignment = get_customization()["style"]["text_alignment"] self.alignment = get_customization()["style"]["text_alignment"]
if self.is_markup:
self.validate_markup_string(text)
self.text = text self.text = text
super().__init__(text, **kwargs) super().__init__(text, **kwargs)
@ -141,7 +126,6 @@ class MarkupText(StringMobject):
self.base_color, self.base_color,
self.isolate, self.isolate,
self.text, self.text,
self.is_markup,
self.font_size, self.font_size,
self.lsh, self.lsh,
self.justify, self.justify,
@ -231,73 +215,15 @@ class MarkupText(StringMobject):
# Parsing # Parsing
def get_cmd_spans(self) -> list[Span]: def get_internal_specified_items(
if not self.is_markup:
return self.find_spans(r"""[<>&"']""")
# Unsupported passthroughs:
# "<?...?>", "<!--...-->", "<![CDATA[...]]>", "<!DOCTYPE...>"
# See https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gmarkup.c
return self.find_spans(
r"""&[\s\S]*?;|[>"']|</?\w+(?:\s*\w+\s*\=\s*(["'])[\s\S]*?\1)*/?>"""
)
def get_substr_flag(self, substr: str) -> int:
if re.fullmatch(r"<\w[\s\S]*[^/]>", substr):
return 1
if substr.startswith("</"):
return -1
return 0
def get_repl_substr_for_content(self, substr: str) -> str:
if substr.startswith("<") and substr.endswith(">"):
return ""
return {
"<": "&lt;",
">": "&gt;",
"&": "&amp;",
"\"": "&quot;",
"'": "&apos;"
}.get(substr, substr)
def get_repl_substr_for_matching(self, substr: str) -> str:
if substr.startswith("<") and substr.endswith(">"):
return ""
if substr.startswith("&#") and substr.endswith(";"):
if substr.startswith("&#x"):
char_reference = int(substr[3:-1], 16)
else:
char_reference = int(substr[2:-1], 10)
return chr(char_reference)
return {
"&lt;": "<",
"&gt;": ">",
"&amp;": "&",
"&quot;": "\"",
"&apos;": "'"
}.get(substr, substr)
def get_specified_items(
self, cmd_span_pairs: list[tuple[Span, Span]] self, cmd_span_pairs: list[tuple[Span, Span]]
) -> list[tuple[Span, dict[str, str]]]: ) -> list[tuple[Span, dict[str, str]]]:
attr_pattern = r"""(\w+)\s*\=\s*(["'])([\s\S]*?)\2""" return []
internal_items = []
for begin_cmd_span, end_cmd_span in cmd_span_pairs:
begin_tag = self.get_substr(begin_cmd_span)
tag_name = re.match(r"<(\w+)", begin_tag).group(1)
if tag_name == "span":
attr_dict = {
attr_match_obj.group(1): attr_match_obj.group(3)
for attr_match_obj in re.finditer(attr_pattern, begin_tag)
}
else:
attr_dict = MarkupText.MARKUP_TAGS.get(tag_name, {})
internal_items.append(
((begin_cmd_span[1], end_cmd_span[0]), attr_dict)
)
def get_external_specified_items(
self
) -> list[tuple[Span, dict[str, str]]]:
return [ return [
*internal_items,
*[ *[
(span, {key: val}) (span, {key: val})
for t2x_dict, key in ( for t2x_dict, key in (
@ -313,10 +239,6 @@ class MarkupText(StringMobject):
(span, local_config) (span, local_config)
for selector, local_config in self.local_configs.items() for selector, local_config in self.local_configs.items()
for span in self.find_spans_by_selector(selector) for span in self.find_spans_by_selector(selector)
],
*[
(span, {})
for span in self.find_spans_by_selector(self.isolate)
] ]
] ]
@ -327,11 +249,13 @@ class MarkupText(StringMobject):
if label_hex is not None: if label_hex is not None:
converted_attr_dict = {"foreground": label_hex} converted_attr_dict = {"foreground": label_hex}
for key, val in attr_dict.items(): for key, val in attr_dict.items():
substitute_key = MarkupText.MARKUP_COLOR_KEYS.get(key, None) if key in (
if substitute_key is None: "background", "bgcolor",
converted_attr_dict[key] = val "underline_color", "overline_color", "strikethrough_color"
elif substitute_key: ):
converted_attr_dict[key] = "black" converted_attr_dict[key] = "black"
elif key not in ("foreground", "fgcolor", "color"):
converted_attr_dict[key] = val
else: else:
converted_attr_dict = attr_dict.copy() converted_attr_dict = attr_dict.copy()
attrs_str = " ".join([ attrs_str = " ".join([
@ -376,8 +300,8 @@ class MarkupText(StringMobject):
def get_parts_by_text(self, selector: Selector) -> VGroup: def get_parts_by_text(self, selector: Selector) -> VGroup:
return self.select_parts(selector) return self.select_parts(selector)
def get_part_by_text(self, selector: Selector) -> VGroup: def get_part_by_text(self, selector: Selector, **kwargs) -> VGroup:
return self.select_part(selector) return self.select_part(selector, **kwargs)
def set_color_by_text(self, selector: Selector, color: ManimColor): def set_color_by_text(self, selector: Selector, color: ManimColor):
return self.set_parts_color(selector, color) return self.set_parts_color(selector, color)
@ -391,10 +315,70 @@ class MarkupText(StringMobject):
return self.get_string() return self.get_string()
class Text(MarkupText): class MarkupText(Text):
CONFIG = { # Unsupported passthroughs:
"is_markup": False, # "<?...?>", "<!--...-->", "<![CDATA[...]]>", "<!DOCTYPE...>"
# See https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gmarkup.c
CMD_PATTERN = r"""</?\w+(?:\s*\w+\s*\=\s*(["']).*?\1)*/?>|&.*?;|[>"']"""
FLAG_DICT = {
r"</.*>": -1,
r"<.*/>": 0,
r"<.*>": 1
} }
CONTENT_REPL = {
r">": "&gt;",
r"\"": "&quot;",
r"'": "&apos;"
}
MATCH_REPL = {
r"<.*>": "",
r"&#x(.*);": lambda m: chr(int(m.group(1), 16)),
r"&#(.*);": lambda m: chr(int(m.group(1), 10)),
r"&lt;": "<",
r"&gt;": ">",
r"&amp;": "&",
r"&quot;": "\"",
r"&apos;": "'"
}
# See https://docs.gtk.org/Pango/pango_markup.html
MARKUP_TAGS = {
"b": {"font_weight": "bold"},
"big": {"font_size": "larger"},
"i": {"font_style": "italic"},
"s": {"strikethrough": "true"},
"sub": {"baseline_shift": "subscript", "font_scale": "subscript"},
"sup": {"baseline_shift": "superscript", "font_scale": "superscript"},
"small": {"font_size": "smaller"},
"tt": {"font_family": "monospace"},
"u": {"underline": "single"},
}
def __init__(self, text: str, **kwargs):
self.validate_markup_string(text)
super().__init__(text, **kwargs)
def get_internal_specified_items(
self, cmd_span_pairs: list[tuple[Span, Span]]
) -> list[tuple[Span, dict[str, str]]]:
attr_pattern = r"""(\w+)\s*\=\s*(["'])(.*?)\2"""
result = []
for begin_cmd_span, end_cmd_span in cmd_span_pairs:
begin_tag = self.get_substr(begin_cmd_span)
tag_name = re.search(r"\w+", begin_tag).group()
if tag_name == "span":
attr_dict = {
attr_match_obj.group(1): attr_match_obj.group(3)
for attr_match_obj in re.finditer(
attr_pattern, begin_tag, re.S
)
}
else:
attr_dict = self.MARKUP_TAGS.get(tag_name, {})
result.append(
((begin_cmd_span[1], end_cmd_span[0]), attr_dict)
)
return result
class Code(MarkupText): class Code(MarkupText):

View File

@ -1,552 +1,440 @@
default: default: |-
compiler: latex \usepackage[english]{babel}
preamble: |- \usepackage[utf8]{inputenc}
\usepackage[english]{babel} \usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc} \usepackage{amsmath}
\usepackage[T1]{fontenc} \usepackage{amssymb}
\usepackage{amsmath} \usepackage{dsfont}
\usepackage{amssymb} \usepackage{setspace}
\usepackage{dsfont} \usepackage{tipa}
\usepackage{setspace} \usepackage{relsize}
\usepackage{tipa} \usepackage{textcomp}
\usepackage{relsize} \usepackage{mathrsfs}
\usepackage{textcomp} \usepackage{calligra}
\usepackage{mathrsfs} \usepackage{wasysym}
\usepackage{calligra} \usepackage{ragged2e}
\usepackage{wasysym} \usepackage{physics}
\usepackage{ragged2e} \usepackage{xcolor}
\usepackage{physics} \usepackage{microtype}
\usepackage{xcolor} \usepackage{pifont}
\usepackage{microtype} \DisableLigatures{encoding = *, family = * }
\usepackage{pifont} \linespread{1}
\DisableLigatures{encoding = *, family = * }
\linespread{1}
ctex: ctex: |-
compiler: xelatex \usepackage[UTF8]{ctex}
preamble: |- \usepackage[english]{babel}
\usepackage[UTF8]{ctex} \usepackage{amsmath}
\usepackage[english]{babel} \usepackage{amssymb}
\usepackage{amsmath} \usepackage{dsfont}
\usepackage{amssymb} \usepackage{setspace}
\usepackage{dsfont} \usepackage{tipa}
\usepackage{setspace} \usepackage{relsize}
\usepackage{tipa} \usepackage{textcomp}
\usepackage{relsize} \usepackage{mathrsfs}
\usepackage{textcomp} \usepackage{calligra}
\usepackage{mathrsfs} \usepackage{wasysym}
\usepackage{calligra} \usepackage{ragged2e}
\usepackage{wasysym} \usepackage{physics}
\usepackage{ragged2e} \usepackage{xcolor}
\usepackage{physics} \usepackage{microtype}
\usepackage{xcolor} \linespread{1}
\usepackage{microtype}
\linespread{1}
blank: basic: |-
compiler: latex \usepackage[english]{babel}
preamble: |- \usepackage{amsmath}
\usepackage{xcolor} \usepackage{amssymb}
\usepackage{xcolor}
basic: # A collection of TeX templates for the fonts described at
compiler: latex # http://jf.burnol.free.fr/showcase.html
preamble: |-
\usepackage[english]{babel}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{xcolor}
# American Typewriter # American Typewriter
american_typewriter: american_typewriter: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{American Typewriter}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{American Typewriter}
\usepackage[defaultmathsizes]{mathastext}
# Antykwa Poltawskiego (TX Fonts for Greek and math symbols) # Antykwa Poltawskiego (TX Fonts for Greek and math symbols)
antykwa: antykwa: |-
compiler: latex \usepackage[OT4,OT1]{fontenc}
preamble: |- \usepackage{txfonts}
\usepackage[OT4,OT1]{fontenc} \usepackage[upright]{txgreeks}
\usepackage{txfonts} \usepackage{antpolt}
\usepackage[upright]{txgreeks} \usepackage[defaultmathsizes,nolessnomore]{mathastext}
\usepackage{antpolt}
\usepackage[defaultmathsizes,nolessnomore]{mathastext}
# Apple Chancery # Apple Chancery
apple_chancery: apple_chancery: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Apple Chancery}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{Apple Chancery}
\usepackage[defaultmathsizes]{mathastext}
# Auriocus Kalligraphicus (Symbol Greek) # Auriocus Kalligraphicus (Symbol Greek)
auriocus_kalligraphicus: auriocus_kalligraphicus: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{aurical}
\usepackage[T1]{fontenc} \renewcommand{\rmdefault}{AuriocusKalligraphicus}
\usepackage{aurical} \usepackage[symbolgreek]{mathastext}
\renewcommand{\rmdefault}{AuriocusKalligraphicus}
\usepackage[symbolgreek]{mathastext}
# Baskervald ADF with Fourier # Baskervald ADF with Fourier
baskervald_adf_fourier: baskervald_adf_fourier: |-
compiler: latex \usepackage[upright]{fourier}
preamble: |- \usepackage{baskervald}
\usepackage[upright]{fourier} \usepackage[defaultmathsizes,noasterisk]{mathastext}
\usepackage{baskervald}
\usepackage[defaultmathsizes,noasterisk]{mathastext}
# Baskerville (Italic) # Baskerville (Italic)
baskerville_it: baskerville_it: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Baskerville}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes,italic]{mathastext}
\setmainfont[Mapping=tex-text]{Baskerville}
\usepackage[defaultmathsizes,italic]{mathastext}
# Biolinum # Biolinum
biolinum: biolinum: |-
compiler: latex \usepackage{txfonts}
preamble: |- \usepackage[upright]{txgreeks}
\usepackage{txfonts} \usepackage[no-math]{fontspec}
\usepackage[upright]{txgreeks} \setmainfont[Mapping=tex-text]{Minion Pro}
\usepackage[no-math]{fontspec} \setsansfont[Mapping=tex-text,Scale=MatchUppercase]{Myriad Pro}
\setmainfont[Mapping=tex-text]{Minion Pro} \renewcommand\familydefault\sfdefault
\setsansfont[Mapping=tex-text,Scale=MatchUppercase]{Myriad Pro} \usepackage[defaultmathsizes]{mathastext}
\renewcommand\familydefault\sfdefault \renewcommand\familydefault\rmdefault
\usepackage[defaultmathsizes]{mathastext}
\renewcommand\familydefault\rmdefault
# BrushScriptX-Italic (PX math and Greek) # BrushScriptX-Italic (PX math and Greek)
brushscriptx: brushscriptx: |-
compiler: xelatex \usepackage[T1]{fontenc}
preamble: |- \usepackage{pxfonts}
\usepackage[T1]{fontenc} \renewcommand{\rmdefault}{pbsi}
\usepackage{pxfonts} \renewcommand{\mddefault}{xl}
\renewcommand{\rmdefault}{pbsi} \renewcommand{\bfdefault}{xl}
\renewcommand{\mddefault}{xl} \usepackage[defaultmathsizes,noasterisk]{mathastext}
\renewcommand{\bfdefault}{xl} \boldmath
\usepackage[defaultmathsizes,noasterisk]{mathastext}
\boldmath
# Chalkboard SE # Chalkboard SE
chalkboard_se: chalkboard_se: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Chalkboard SE}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{Chalkboard SE}
\usepackage[defaultmathsizes]{mathastext}
# Chalkduster # Chalkduster
chalkduster: chalkduster: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Chalkduster}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{Chalkduster}
\usepackage[defaultmathsizes]{mathastext}
# Comfortaa # Comfortaa
comfortaa: comfortaa: |-
compiler: latex \usepackage[default]{comfortaa}
preamble: |- \usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext}
\usepackage[default]{comfortaa} \let\varphi\phi
\usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext} \linespread{1.06}
\let\varphi\phi
\linespread{1.06}
# Comic Sans MS # Comic Sans MS
comic_sans: comic_sans: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Comic Sans MS}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{Comic Sans MS}
\usepackage[defaultmathsizes]{mathastext}
# Droid Sans # Droid Sans
droid_sans: droid_sans: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[default]{droidsans}
\usepackage[T1]{fontenc} \usepackage[LGRgreek]{mathastext}
\usepackage[default]{droidsans} \let\varepsilon\epsilon
\usepackage[LGRgreek]{mathastext}
\let\varepsilon\epsilon
# Droid Sans (Italic) # Droid Sans (Italic)
droid_sans_it: droid_sans_it: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[default]{droidsans}
\usepackage[T1]{fontenc} \usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
\usepackage[default]{droidsans} \let\varphi\phi
\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
\let\varphi\phi
# Droid Serif # Droid Serif
droid_serif: droid_serif: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[default]{droidserif}
\usepackage[T1]{fontenc} \usepackage[LGRgreek]{mathastext}
\usepackage[default]{droidserif} \let\varepsilon\epsilon
\usepackage[LGRgreek]{mathastext}
\let\varepsilon\epsilon
# Droid Serif (PX math symbols) (Italic) # Droid Serif (PX math symbols) (Italic)
droid_serif_px_it: droid_serif_px_it: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{pxfonts}
\usepackage[T1]{fontenc} \usepackage[default]{droidserif}
\usepackage{pxfonts} \usepackage[LGRgreek,defaultmathsizes,italic,basic]{mathastext}
\usepackage[default]{droidserif} \let\varphi\phi
\usepackage[LGRgreek,defaultmathsizes,italic,basic]{mathastext}
\let\varphi\phi
# ECF Augie (Euler Greek) # ECF Augie (Euler Greek)
ecf_augie: ecf_augie: |-
compiler: latex \renewcommand\familydefault{fau}
preamble: |- \usepackage[defaultmathsizes,eulergreek]{mathastext}
\renewcommand\familydefault{fau}
\usepackage[defaultmathsizes,eulergreek]{mathastext}
# ECF JD (with TX fonts) # ECF JD (with TX fonts)
ecf_jd: ecf_jd: |-
compiler: latex \usepackage{txfonts}
preamble: |- \usepackage[upright]{txgreeks}
\usepackage{txfonts} \renewcommand\familydefault{fjd}
\usepackage[upright]{txgreeks} \usepackage{mathastext}
\renewcommand\familydefault{fjd} \mathversion{bold}
\usepackage{mathastext}
\mathversion{bold}
# ECF Skeetch (CM Greek) # ECF Skeetch (CM Greek)
ecf_skeetch: ecf_skeetch: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \DeclareFontFamily{T1}{fsk}{}
\usepackage[T1]{fontenc} \DeclareFontShape{T1}{fsk}{m}{n}{<->s*[1.315] fskmw8t}{}
\DeclareFontFamily{T1}{fsk}{} \renewcommand\rmdefault{fsk}
\DeclareFontShape{T1}{fsk}{m}{n}{<->s*[1.315] fskmw8t}{} \usepackage[noendash,defaultmathsizes,nohbar,defaultimath]{mathastext}
\renewcommand\rmdefault{fsk}
\usepackage[noendash,defaultmathsizes,nohbar,defaultimath]{mathastext}
# ECF Tall Paul (with Symbol font) # ECF Tall Paul (with Symbol font)
ecf_tall_paul: ecf_tall_paul: |-
compiler: latex \DeclareFontFamily{T1}{ftp}{}
preamble: |- \DeclareFontShape{T1}{ftp}{m}{n}{<->s*[1.4] ftpmw8t}{}
\DeclareFontFamily{T1}{ftp}{} \renewcommand\familydefault{ftp}
\DeclareFontShape{T1}{ftp}{m}{n}{<->s*[1.4] ftpmw8t}{} \usepackage[symbol]{mathastext}
\renewcommand\familydefault{ftp} \let\infty\inftypsy
\usepackage[symbol]{mathastext}
\let\infty\inftypsy
# ECF Webster (with TX fonts) # ECF Webster (with TX fonts)
ecf_webster: ecf_webster: |-
compiler: xelatex \usepackage{txfonts}
preamble: |- \usepackage[upright]{txgreeks}
\usepackage{txfonts} \renewcommand\familydefault{fwb}
\usepackage[upright]{txgreeks} \usepackage{mathastext}
\renewcommand\familydefault{fwb} \renewcommand{\int}{\intop\limits}
\usepackage{mathastext} \linespread{1.5}
\renewcommand{\int}{\intop\limits} \mathversion{bold}
\linespread{1.5}
\mathversion{bold}
# Electrum ADF (CM Greek) # Electrum ADF (CM Greek)
electrum_adf: electrum_adf: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[LGRgreek,basic,defaultmathsizes]{mathastext}
\usepackage[T1]{fontenc} \usepackage[lf]{electrum}
\usepackage[LGRgreek,basic,defaultmathsizes]{mathastext} \Mathastext
\usepackage[lf]{electrum} \let\varphi\phi
\Mathastext
\let\varphi\phi
# Epigrafica # Epigrafica
epigrafica: epigrafica: |-
compiler: latex \usepackage[LGR,OT1]{fontenc}
preamble: |- \usepackage{epigrafica}
\usepackage[LGR,OT1]{fontenc} \usepackage[basic,LGRgreek,defaultmathsizes]{mathastext}
\usepackage{epigrafica} \let\varphi\phi
\usepackage[basic,LGRgreek,defaultmathsizes]{mathastext} \linespread{1.2}
\let\varphi\phi
\linespread{1.2}
# Fourier Utopia (Fourier upright Greek) # Fourier Utopia (Fourier upright Greek)
fourier_utopia: fourier_utopia: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[upright]{fourier}
\usepackage[T1]{fontenc} \usepackage{mathastext}
\usepackage[upright]{fourier}
\usepackage{mathastext}
# French Cursive (Euler Greek) # French Cursive (Euler Greek)
french_cursive: french_cursive: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[default]{frcursive}
\usepackage[T1]{fontenc} \usepackage[eulergreek,noplusnominus,noequal,nohbar,nolessnomore,noasterisk]{mathastext}
\usepackage[default]{frcursive}
\usepackage[eulergreek,noplusnominus,noequal,nohbar,nolessnomore,noasterisk]{mathastext}
# GFS Bodoni # GFS Bodoni
gfs_bodoni: gfs_bodoni: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \renewcommand{\rmdefault}{bodoni}
\usepackage[T1]{fontenc} \usepackage[LGRgreek]{mathastext}
\renewcommand{\rmdefault}{bodoni} \let\varphi\phi
\usepackage[LGRgreek]{mathastext} \linespread{1.06}
\let\varphi\phi
\linespread{1.06}
# GFS Didot (Italic) # GFS Didot (Italic)
gfs_didot: gfs_didot: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \renewcommand\rmdefault{udidot}
\usepackage[T1]{fontenc} \usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
\renewcommand\rmdefault{udidot} \let\varphi\phi
\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
\let\varphi\phi
# GFS NeoHellenic # GFS NeoHellenic
gfs_neoHellenic: gfs_neoHellenic: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \renewcommand{\rmdefault}{neohellenic}
\usepackage[T1]{fontenc} \usepackage[LGRgreek]{mathastext}
\renewcommand{\rmdefault}{neohellenic} \let\varphi\phi
\usepackage[LGRgreek]{mathastext} \linespread{1.06}
\let\varphi\phi
\linespread{1.06}
# GNU FreeSerif (and TX fonts symbols) # GNU FreeSerif (and TX fonts symbols)
gnu_freesans_tx: gnu_freesans_tx: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \usepackage{txfonts}
\usepackage[no-math]{fontspec} \setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}
\usepackage{txfonts} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}
\usepackage[defaultmathsizes]{mathastext}
# GNU FreeSerif and FreeSans # GNU FreeSerif and FreeSans
gnu_freeserif_freesans: gnu_freeserif_freesans: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}
\usepackage[no-math]{fontspec} \setsansfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSansBold,ItalicFont=FreeSansOblique,BoldItalicFont=FreeSansBoldOblique,Scale=MatchLowercase]{FreeSans}
\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif} \renewcommand{\familydefault}{lmss}
\setsansfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSansBold,ItalicFont=FreeSansOblique,BoldItalicFont=FreeSansBoldOblique,Scale=MatchLowercase]{FreeSans} \usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext}
\renewcommand{\familydefault}{lmss} \renewcommand{\familydefault}{\sfdefault}
\usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext} \Mathastext
\renewcommand{\familydefault}{\sfdefault} \let\varphi\phi
\Mathastext \renewcommand{\familydefault}{\rmdefault}
\let\varphi\phi
\renewcommand{\familydefault}{\rmdefault}
# Helvetica with Fourier (Italic) # Helvetica with Fourier (Italic)
helvetica_fourier_it: helvetica_fourier_it: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[scaled]{helvet}
\usepackage[T1]{fontenc} \usepackage{fourier}
\usepackage[scaled]{helvet} \renewcommand{\rmdefault}{phv}
\usepackage{fourier} \usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
\renewcommand{\rmdefault}{phv}
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
# Latin Modern Typewriter Proportional # Latin Modern Typewriter Proportional
latin_modern_tw: latin_modern_tw: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[variablett]{lmodern}
\usepackage[T1]{fontenc} \renewcommand{\rmdefault}{\ttdefault}
\usepackage[variablett]{lmodern} \usepackage[LGRgreek]{mathastext}
\renewcommand{\rmdefault}{\ttdefault} \MTgreekfont{lmtt}
\usepackage[LGRgreek]{mathastext} \Mathastext
\MTgreekfont{lmtt} \let\varepsilon\epsilon
\Mathastext
\let\varepsilon\epsilon
# Latin Modern Typewriter Proportional (CM Greek) (Italic) # Latin Modern Typewriter Proportional (CM Greek) (Italic)
latin_modern_tw_it: latin_modern_tw_it: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[variablett,nomath]{lmodern}
\usepackage[T1]{fontenc} \renewcommand{\familydefault}{\ttdefault}
\usepackage[variablett,nomath]{lmodern} \usepackage[frenchmath]{mathastext}
\renewcommand{\familydefault}{\ttdefault} \linespread{1.08}
\usepackage[frenchmath]{mathastext}
\linespread{1.08}
# Libertine # Libertine
libertine: libertine: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{libertine}
\usepackage[T1]{fontenc} \usepackage[greek=n]{libgreek}
\usepackage{libertine} \usepackage[noasterisk,defaultmathsizes]{mathastext}
\usepackage[greek=n]{libgreek}
\usepackage[noasterisk,defaultmathsizes]{mathastext}
# Libris ADF with Fourier # Libris ADF with Fourier
libris_adf_fourier: libris_adf_fourier: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage[upright]{fourier}
\usepackage[T1]{fontenc} \usepackage{libris}
\usepackage[upright]{fourier} \renewcommand{\familydefault}{\sfdefault}
\usepackage{libris} \usepackage[noasterisk]{mathastext}
\renewcommand{\familydefault}{\sfdefault}
\usepackage[noasterisk]{mathastext}
# Minion Pro and Myriad Pro (and TX fonts symbols) # Minion Pro and Myriad Pro (and TX fonts symbols)
minion_pro_myriad_pro: minion_pro_myriad_pro: |-
compiler: xelatex \usepackage[T1]{fontenc}
preamble: |- \usepackage[default]{droidserif}
\usepackage[T1]{fontenc} \usepackage[LGRgreek]{mathastext}
\usepackage[default]{droidserif} \let\varepsilon\epsilon
\usepackage[LGRgreek]{mathastext}
\let\varepsilon\epsilon
# Minion Pro (and TX fonts symbols) # Minion Pro (and TX fonts symbols)
minion_pro_tx: minion_pro_tx: |-
compiler: xelatex \usepackage{txfonts}
preamble: |- \usepackage[no-math]{fontspec}
\usepackage{txfonts} \setmainfont[Mapping=tex-text]{Minion Pro}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{Minion Pro}
\usepackage[defaultmathsizes]{mathastext}
# New Century Schoolbook (Symbol Greek) # New Century Schoolbook (Symbol Greek)
new_century_schoolbook: new_century_schoolbook: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{newcent}
\usepackage[T1]{fontenc} \usepackage[symbolgreek]{mathastext}
\usepackage{newcent} \linespread{1.1}
\usepackage[symbolgreek]{mathastext}
\linespread{1.1}
# New Century Schoolbook (Symbol Greek, PX math symbols) # New Century Schoolbook (Symbol Greek, PX math symbols)
new_century_schoolbook_px: new_century_schoolbook_px: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{pxfonts}
\usepackage[T1]{fontenc} \usepackage{newcent}
\usepackage{pxfonts} \usepackage[symbolgreek,defaultmathsizes]{mathastext}
\usepackage{newcent} \linespread{1.06}
\usepackage[symbolgreek,defaultmathsizes]{mathastext}
\linespread{1.06}
# Noteworthy Light # Noteworthy Light
noteworthy_light: noteworthy_light: |-
compiler: latex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Noteworthy Light}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{Noteworthy Light}
\usepackage[defaultmathsizes]{mathastext}
# Palatino (Symbol Greek) # Palatino (Symbol Greek)
palatino: palatino: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{palatino}
\usepackage[T1]{fontenc} \usepackage[symbolmax,defaultmathsizes]{mathastext}
\usepackage{palatino}
\usepackage[symbolmax,defaultmathsizes]{mathastext}
# Papyrus # Papyrus
papyrus: papyrus: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Papyrus}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes]{mathastext}
\setmainfont[Mapping=tex-text]{Papyrus}
\usepackage[defaultmathsizes]{mathastext}
# Romande ADF with Fourier (Italic) # Romande ADF with Fourier (Italic)
romande_adf_fourier_it: romande_adf_fourier_it: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{fourier}
\usepackage[T1]{fontenc} \usepackage{romande}
\usepackage{fourier} \usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
\usepackage{romande} \renewcommand{\itshape}{\swashstyle}
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
\renewcommand{\itshape}{\swashstyle}
# SliTeX (Euler Greek) # SliTeX (Euler Greek)
slitex: slitex: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{tpslifonts}
\usepackage[T1]{fontenc} \usepackage[eulergreek,defaultmathsizes]{mathastext}
\usepackage{tpslifonts} \MTEulerScale{1.06}
\usepackage[eulergreek,defaultmathsizes]{mathastext} \linespread{1.2}
\MTEulerScale{1.06}
\linespread{1.2}
# Times with Fourier (Italic) # Times with Fourier (Italic)
times_fourier_it: times_fourier_it: |-
compiler: latex \usepackage{fourier}
preamble: |- \renewcommand{\rmdefault}{ptm}
\usepackage{fourier} \usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
\renewcommand{\rmdefault}{ptm}
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
# URW Avant Garde (Symbol Greek) # URW Avant Garde (Symbol Greek)
urw_avant_garde: urw_avant_garde: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{avant}
\usepackage[T1]{fontenc} \renewcommand{\familydefault}{\sfdefault}
\usepackage{avant} \usepackage[symbolgreek,defaultmathsizes]{mathastext}
\renewcommand{\familydefault}{\sfdefault}
\usepackage[symbolgreek,defaultmathsizes]{mathastext}
# URW Zapf Chancery (CM Greek) # URW Zapf Chancery (CM Greek)
urw_zapf_chancery: urw_zapf_chancery: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \DeclareFontFamily{T1}{pzc}{}
\usepackage[T1]{fontenc} \DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}
\DeclareFontFamily{T1}{pzc}{} \DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{}
\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{} \DeclareFontShape{T1}{pzc}{mb}{sl}{<->ssub * pzc/mb/it}{}
\DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{} \DeclareFontShape{T1}{pzc}{m}{sl}{<->ssub * pzc/mb/sl}{}
\DeclareFontShape{T1}{pzc}{mb}{sl}{<->ssub * pzc/mb/it}{} \DeclareFontShape{T1}{pzc}{m}{n}{<->ssub * pzc/mb/it}{}
\DeclareFontShape{T1}{pzc}{m}{sl}{<->ssub * pzc/mb/sl}{} \usepackage{chancery}
\DeclareFontShape{T1}{pzc}{m}{n}{<->ssub * pzc/mb/it}{} \usepackage{mathastext}
\usepackage{chancery} \linespread{1.05}
\usepackage{mathastext} \boldmath
\linespread{1.05}
\boldmath
# Venturis ADF with Fourier (Italic) # Venturis ADF with Fourier (Italic)
venturis_adf_fourier_it: venturis_adf_fourier_it: |-
compiler: latex \usepackage{fourier}
preamble: |- \usepackage[lf]{venturis}
\usepackage{fourier} \usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
\usepackage[lf]{venturis}
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
# Verdana (Italic) # Verdana (Italic)
verdana_it: verdana_it: |-
compiler: xelatex \usepackage[no-math]{fontspec}
preamble: |- \setmainfont[Mapping=tex-text]{Verdana}
\usepackage[no-math]{fontspec} \usepackage[defaultmathsizes,italic]{mathastext}
\setmainfont[Mapping=tex-text]{Verdana}
\usepackage[defaultmathsizes,italic]{mathastext}
# Vollkorn (TX fonts for Greek and math symbols) # Vollkorn (TX fonts for Greek and math symbols)
vollkorn: vollkorn: |-
compiler: latex \usepackage[T1]{fontenc}
preamble: |- \usepackage{txfonts}
\usepackage[T1]{fontenc} \usepackage[upright]{txgreeks}
\usepackage{txfonts} \usepackage{vollkorn}
\usepackage[upright]{txgreeks} \usepackage[defaultmathsizes]{mathastext}
\usepackage{vollkorn}
\usepackage[defaultmathsizes]{mathastext}
# Vollkorn with Fourier (Italic) # Vollkorn with Fourier (Italic)
vollkorn_fourier_it: vollkorn_fourier_it: |-
compiler: latex \usepackage{fourier}
preamble: |- \usepackage{vollkorn}
\usepackage{fourier} \usepackage[italic,nohbar]{mathastext}
\usepackage{vollkorn}
\usepackage[italic,nohbar]{mathastext}
# Zapf Chancery # Zapf Chancery
zapf_chancery: zapf_chancery: |-
compiler: latex \DeclareFontFamily{T1}{pzc}{}
preamble: |- \DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}
\DeclareFontFamily{T1}{pzc}{} \DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{}
\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{} \usepackage{chancery}
\DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{} \renewcommand\shapedefault\itdefault
\usepackage{chancery} \renewcommand\bfdefault\mddefault
\renewcommand\shapedefault\itdefault \usepackage[defaultmathsizes]{mathastext}
\renewcommand\bfdefault\mddefault \linespread{1.05}
\usepackage[defaultmathsizes]{mathastext}
\linespread{1.05}

View File

@ -44,8 +44,9 @@ def init_customization() -> None:
}, },
"universal_import_line": "from manimlib import *", "universal_import_line": "from manimlib import *",
"style": { "style": {
"tex_compiler": "",
"tex_font": "", "tex_font": "",
"font": "", "font": "Consolas",
"background_color": "", "background_color": "",
}, },
"window_position": "UR", "window_position": "UR",
@ -57,7 +58,7 @@ def init_customization() -> None:
"medium": "1280x720", "medium": "1280x720",
"high": "1920x1080", "high": "1920x1080",
"4k": "3840x2160", "4k": "3840x2160",
"default_resolution": "high", "default_resolution": "",
}, },
"fps": 30, "fps": 30,
} }
@ -106,14 +107,16 @@ def init_customization() -> None:
console.print("[bold]Styles:[/bold]") console.print("[bold]Styles:[/bold]")
style_config = configuration["style"] style_config = configuration["style"]
style_config["tex_font"] = Prompt.ask( compiler = Prompt.ask(
" Which [bold]font[/bold] for LaTeX do you want", " Select an executable program to use to compile a LaTeX source file",
default="default" choices=["latex", "xelatex"],
) default="latex"
style_config["font"] = Prompt.ask(
" Which [bold]font[/bold] for non-LaTeX text do you want",
default="Consolas"
) )
style_config["tex_compiler"] = compiler
if compiler == "latex":
style_config["tex_font"] = "default"
else:
style_config["tex_font"] = "ctex"
style_config["background_color"] = Prompt.ask( style_config["background_color"] = Prompt.ask(
" Which [bold]background color[/bold] do you want [italic](hex code)", " Which [bold]background color[/bold] do you want [italic](hex code)",
default="#333333" default="#333333"
@ -127,7 +130,7 @@ def init_customization() -> None:
) )
table.add_row("480p15", "720p30", "1080p60", "2160p60") table.add_row("480p15", "720p30", "1080p60", "2160p60")
console.print(table) console.print(table)
configuration["camera_qualities"]["default_quality"] = Prompt.ask( configuration["camera_resolutions"]["default_resolution"] = Prompt.ask(
" Which one to choose as the default rendering quality", " Which one to choose as the default rendering quality",
choices=["low", "medium", "high", "ultra_high"], choices=["low", "medium", "high", "ultra_high"],
default="high" default="high"

View File

@ -15,14 +15,7 @@ from manimlib.utils.simple_functions import hash_string
SAVED_TEX_CONFIG = {} SAVED_TEX_CONFIG = {}
def get_tex_font_config(tex_font: str) -> str: def get_tex_font_preamble(tex_font: str) -> str:
"""
Returns a dict which should look something like this:
{
"compiler": "latex",
"preamble": "..."
}
"""
name = re.sub(r"[^a-zA-Z]", "_", tex_font).lower() name = re.sub(r"[^a-zA-Z]", "_", tex_font).lower()
with open(os.path.join( with open(os.path.join(
get_manim_dir(), "manimlib", "tex_fonts.yml" get_manim_dir(), "manimlib", "tex_fonts.yml"
@ -35,30 +28,40 @@ def get_tex_font_config(tex_font: str) -> str:
) )
name = "default" name = "default"
result = templates_dict[name] result = templates_dict[name]
if name not in ("default", "ctex", "blank", "basic"): if name not in ("default", "ctex", "basic"):
result["preamble"] = "\n".join(( result = templates_dict["basic"] + "\n" + result
templates_dict["basic"]["preamble"], result["preamble"]
))
return result return result
def get_tex_config() -> dict[str, str]: def get_tex_config() -> dict[str, str]:
"""
Returns a dict which should look something like this:
{
"compiler": "latex",
"font": "default",
"preamble": "..."
}
"""
# Only load once, then save thereafter # Only load once, then save thereafter
if not SAVED_TEX_CONFIG: if not SAVED_TEX_CONFIG:
tex_font = get_custom_config()["style"]["tex_font"] style_config = get_custom_config()["style"]
SAVED_TEX_CONFIG.update(get_tex_font_config(tex_font)) SAVED_TEX_CONFIG.update({
"compiler": style_config["tex_compiler"],
"font": style_config["tex_font"],
"preamble": get_tex_font_preamble(style_config["tex_font"])
})
return SAVED_TEX_CONFIG return SAVED_TEX_CONFIG
def tex_content_to_svg_file( def tex_content_to_svg_file(
content: str, tex_font: str, additional_preamble: str content: str, tex_font: str, additional_preamble: str
) -> str: ) -> str:
if not tex_font: tex_config = get_tex_config()
tex_config = get_tex_config() if not tex_font or tex_font == tex_config["font"]:
preamble = tex_config["preamble"]
else: else:
tex_config = get_tex_font_config(tex_font) preamble = get_tex_font_preamble(tex_font)
preamble = tex_config["preamble"]
if additional_preamble: if additional_preamble:
preamble += "\n" + additional_preamble preamble += "\n" + additional_preamble
full_tex = "\n\n".join(( full_tex = "\n\n".join((