mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 05:52:34 +08:00
Small refactors on StringMobject and relevant classes
This commit is contained in:
@ -167,83 +167,95 @@ class TransformMatchingStrings(AnimationGroup):
|
||||
digest_config(self, kwargs)
|
||||
assert isinstance(source, 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 = []
|
||||
used = []
|
||||
for indices_list in indices_lists:
|
||||
if not indices_list:
|
||||
continue
|
||||
if not all(index in rest_indices for index in indices_list):
|
||||
if not all(
|
||||
index not in used_indices and index not in used
|
||||
for index in indices_list
|
||||
):
|
||||
continue
|
||||
result.append(indices_list)
|
||||
for index in indices_list:
|
||||
rest_indices.remove(index)
|
||||
return result
|
||||
used.extend(indices_list)
|
||||
return result, used
|
||||
|
||||
def add_anims(anim_class, indices_lists_pairs):
|
||||
for source_indices_lists, target_indices_lists in indices_lists_pairs:
|
||||
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,
|
||||
[
|
||||
anim_class_items = [
|
||||
(ReplacementTransform, [
|
||||
(
|
||||
source.get_submob_indices_lists_by_selector(k),
|
||||
target.get_submob_indices_lists_by_selector(v)
|
||||
)
|
||||
for k, v in self.key_map.items()
|
||||
]),
|
||||
(FadeTransformPieces, get_matched_indices_lists(
|
||||
source.get_specified_part_items(),
|
||||
target.get_specified_part_items()
|
||||
)),
|
||||
(FadeTransformPieces, get_matched_indices_lists(
|
||||
source.get_group_part_items(),
|
||||
target.get_group_part_items()
|
||||
))
|
||||
]
|
||||
)
|
||||
add_anims_from(
|
||||
FadeTransformPieces,
|
||||
StringMobject.get_specified_part_items
|
||||
)
|
||||
add_anims_from(
|
||||
FadeTransformPieces,
|
||||
StringMobject.get_group_part_items
|
||||
)
|
||||
|
||||
rest_source = VGroup(*[source[index] for index in source_indices])
|
||||
rest_target = VGroup(*[target[index] for index in target_indices])
|
||||
anims = []
|
||||
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:
|
||||
anims.append(
|
||||
ReplacementTransform(rest_source, rest_target, **kwargs)
|
||||
|
@ -18,6 +18,8 @@ directories:
|
||||
temporary_storage: ""
|
||||
universal_import_line: "from manimlib import *"
|
||||
style:
|
||||
# "latex" | "xelatex"
|
||||
tex_compiler: "latex"
|
||||
tex_font: "default"
|
||||
font: "Consolas"
|
||||
text_alignment: "LEFT"
|
||||
|
@ -40,6 +40,16 @@ class MTex(StringMobject):
|
||||
"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):
|
||||
# Prevent from passing an empty string.
|
||||
if not tex_string.strip():
|
||||
@ -75,44 +85,32 @@ class MTex(StringMobject):
|
||||
|
||||
# Parsing
|
||||
|
||||
def get_cmd_spans(self) -> list[Span]:
|
||||
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(
|
||||
def get_internal_specified_items(
|
||||
self, cmd_span_pairs: list[tuple[Span, Span]]
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
cmd_content_spans = [
|
||||
(span_begin, span_end)
|
||||
for (_, span_begin), (span_end, _) in cmd_span_pairs
|
||||
]
|
||||
specified_spans = [
|
||||
*[
|
||||
cmd_content_spans[range_begin]
|
||||
for _, (range_begin, range_end) in self.compress_neighbours([
|
||||
return [
|
||||
(cmd_content_spans[range_begin], {})
|
||||
for _, (range_begin, range_end) in self.group_neighbours([
|
||||
(span_begin + index, span_end - index)
|
||||
for index, (span_begin, span_end) in enumerate(
|
||||
cmd_content_spans
|
||||
)
|
||||
])
|
||||
if range_end - range_begin >= 2
|
||||
],
|
||||
*[
|
||||
span
|
||||
]
|
||||
|
||||
def get_external_specified_items(
|
||||
self
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
return [
|
||||
(span, {})
|
||||
for selector in self.tex_to_color_map
|
||||
for span in self.find_spans_by_selector(selector)
|
||||
],
|
||||
*self.find_spans_by_selector(self.isolate)
|
||||
]
|
||||
return [(span, {}) for span in specified_spans]
|
||||
|
||||
@staticmethod
|
||||
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:
|
||||
return self.select_parts(selector)
|
||||
|
||||
def get_part_by_tex(self, selector: Selector) -> VGroup:
|
||||
return self.select_part(selector)
|
||||
def get_part_by_tex(self, selector: Selector, **kwargs) -> VGroup:
|
||||
return self.select_part(selector, **kwargs)
|
||||
|
||||
def set_color_by_tex(self, selector: Selector, color: ManimColor):
|
||||
return self.set_parts_color(selector, color)
|
||||
|
@ -18,7 +18,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from colour import Color
|
||||
from typing import Iterable, Sequence, TypeVar, Union
|
||||
from typing import Callable, Iterable, TypeVar, Union
|
||||
|
||||
ManimColor = Union[str, Color]
|
||||
Span = tuple[int, int]
|
||||
@ -66,6 +66,11 @@ class StringMobject(SVGMobject, ABC):
|
||||
"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):
|
||||
self.string = string
|
||||
digest_config(self, kwargs)
|
||||
@ -153,21 +158,18 @@ class StringMobject(SVGMobject, ABC):
|
||||
|
||||
# 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_single_selector(sel):
|
||||
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):
|
||||
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(
|
||||
isinstance(index, int) or index is None
|
||||
for index in sel
|
||||
@ -191,24 +193,59 @@ class StringMobject(SVGMobject, ABC):
|
||||
result.extend(spans)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_neighbouring_pairs(vals: Sequence[T]) -> list[tuple[T, T]]:
|
||||
return list(zip(vals[:-1], vals[1:]))
|
||||
def get_substr(self, span: Span) -> str:
|
||||
return self.string[slice(*span)]
|
||||
|
||||
@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:
|
||||
return []
|
||||
|
||||
unique_vals = [vals[0]]
|
||||
indices = [0]
|
||||
for index, val in enumerate(vals):
|
||||
if val == unique_vals[-1]:
|
||||
continue
|
||||
unique_vals.append(val)
|
||||
indices.append(index)
|
||||
indices.append(len(vals))
|
||||
val_ranges = StringMobject.get_neighbouring_pairs(indices)
|
||||
unique_vals, range_lens = zip(*(
|
||||
(val, len(list(grouper)))
|
||||
for val, grouper in it.groupby(vals)
|
||||
))
|
||||
val_ranges = StringMobject.get_neighbouring_pairs(
|
||||
[0, *it.accumulate(range_lens)]
|
||||
)
|
||||
return list(zip(unique_vals, val_ranges))
|
||||
|
||||
@staticmethod
|
||||
@ -228,18 +265,6 @@ class StringMobject(SVGMobject, ABC):
|
||||
(*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
|
||||
def color_to_hex(color: ManimColor) -> str:
|
||||
return rgb_to_hex(color_to_rgb(color))
|
||||
@ -255,12 +280,26 @@ class StringMobject(SVGMobject, ABC):
|
||||
# Parsing
|
||||
|
||||
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]
|
||||
flags = [self.get_substr_flag(substr) for substr in cmd_substrs]
|
||||
specified_items = self.get_specified_items(
|
||||
flags = [
|
||||
self.get_substr_matched_val(substr, self.FLAG_DICT, 0)
|
||||
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 = [
|
||||
(span, attr_dict)
|
||||
for specified_span, attr_dict in specified_items
|
||||
@ -273,31 +312,15 @@ class StringMobject(SVGMobject, ABC):
|
||||
self.split_items = split_items
|
||||
self.labelled_spans = [span for span, _ in split_items]
|
||||
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)
|
||||
]
|
||||
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)
|
||||
]
|
||||
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
|
||||
def get_cmd_span_pairs(
|
||||
cmd_spans: list[Span], flags: list[int]
|
||||
@ -317,11 +340,17 @@ class StringMobject(SVGMobject, ABC):
|
||||
return result
|
||||
|
||||
@abstractmethod
|
||||
def get_specified_items(
|
||||
def get_internal_specified_items(
|
||||
self, cmd_span_pairs: list[tuple[Span, Span]]
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
return []
|
||||
|
||||
@abstractmethod
|
||||
def get_external_specified_items(
|
||||
self
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
return []
|
||||
|
||||
def split_span_by_levels(
|
||||
self, arbitrary_span: Span, cmd_spans: list[Span], flags: list[int]
|
||||
) -> list[Span]:
|
||||
@ -387,6 +416,18 @@ class StringMobject(SVGMobject, ABC):
|
||||
) -> tuple[str, str]:
|
||||
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:
|
||||
inserted_str_pairs = [
|
||||
(span, self.get_cmd_str_pair(
|
||||
@ -446,7 +487,7 @@ class StringMobject(SVGMobject, ABC):
|
||||
return []
|
||||
|
||||
group_labels, labelled_submob_ranges = zip(
|
||||
*self.compress_neighbours(self.labels)
|
||||
*self.group_neighbours(self.labels)
|
||||
)
|
||||
ordered_spans = [
|
||||
self.labelled_spans[label] if label != -1 else self.full_span
|
||||
|
@ -122,7 +122,8 @@ class SVGMobject(VMobject):
|
||||
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)
|
||||
root_style_node = ET.SubElement(config_style_node, "g", style_attrs)
|
||||
root_style_node.extend(root)
|
||||
|
@ -61,9 +61,8 @@ class _Alignment:
|
||||
self.value = _Alignment.VAL_DICT[s.upper()]
|
||||
|
||||
|
||||
class MarkupText(StringMobject):
|
||||
class Text(StringMobject):
|
||||
CONFIG = {
|
||||
"is_markup": True,
|
||||
"font_size": 48,
|
||||
"lsh": None,
|
||||
"justify": False,
|
||||
@ -85,28 +84,16 @@ class MarkupText(StringMobject):
|
||||
"isolate": (re.compile(r"[a-zA-Z]+"), re.compile(r"\S+")),
|
||||
}
|
||||
|
||||
# See https://docs.gtk.org/Pango/pango_markup.html
|
||||
MARKUP_COLOR_KEYS = {
|
||||
"foreground": False,
|
||||
"fgcolor": False,
|
||||
"color": False,
|
||||
"background": True,
|
||||
"bgcolor": True,
|
||||
"underline_color": True,
|
||||
"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"},
|
||||
CMD_PATTERN = r"""[<>&"']"""
|
||||
FLAG_DICT = {}
|
||||
CONTENT_REPL = {
|
||||
r"<": "<",
|
||||
r">": ">",
|
||||
r"&": "&",
|
||||
r"\"": """,
|
||||
r"'": "'"
|
||||
}
|
||||
MATCH_REPL = {}
|
||||
|
||||
def __init__(self, text: str, **kwargs):
|
||||
self.full2short(kwargs)
|
||||
@ -116,8 +103,6 @@ class MarkupText(StringMobject):
|
||||
self.font = get_customization()["style"]["font"]
|
||||
if not self.alignment:
|
||||
self.alignment = get_customization()["style"]["text_alignment"]
|
||||
if self.is_markup:
|
||||
self.validate_markup_string(text)
|
||||
|
||||
self.text = text
|
||||
super().__init__(text, **kwargs)
|
||||
@ -141,7 +126,6 @@ class MarkupText(StringMobject):
|
||||
self.base_color,
|
||||
self.isolate,
|
||||
self.text,
|
||||
self.is_markup,
|
||||
self.font_size,
|
||||
self.lsh,
|
||||
self.justify,
|
||||
@ -231,73 +215,15 @@ class MarkupText(StringMobject):
|
||||
|
||||
# Parsing
|
||||
|
||||
def get_cmd_spans(self) -> list[Span]:
|
||||
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 {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
"\"": """,
|
||||
"'": "'"
|
||||
}.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 {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
""": "\"",
|
||||
"'": "'"
|
||||
}.get(substr, substr)
|
||||
|
||||
def get_specified_items(
|
||||
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*(["'])([\s\S]*?)\2"""
|
||||
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)
|
||||
)
|
||||
return []
|
||||
|
||||
def get_external_specified_items(
|
||||
self
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
return [
|
||||
*internal_items,
|
||||
*[
|
||||
(span, {key: val})
|
||||
for t2x_dict, key in (
|
||||
@ -313,10 +239,6 @@ class MarkupText(StringMobject):
|
||||
(span, local_config)
|
||||
for selector, local_config in self.local_configs.items()
|
||||
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:
|
||||
converted_attr_dict = {"foreground": label_hex}
|
||||
for key, val in attr_dict.items():
|
||||
substitute_key = MarkupText.MARKUP_COLOR_KEYS.get(key, None)
|
||||
if substitute_key is None:
|
||||
converted_attr_dict[key] = val
|
||||
elif substitute_key:
|
||||
if key in (
|
||||
"background", "bgcolor",
|
||||
"underline_color", "overline_color", "strikethrough_color"
|
||||
):
|
||||
converted_attr_dict[key] = "black"
|
||||
elif key not in ("foreground", "fgcolor", "color"):
|
||||
converted_attr_dict[key] = val
|
||||
else:
|
||||
converted_attr_dict = attr_dict.copy()
|
||||
attrs_str = " ".join([
|
||||
@ -376,8 +300,8 @@ class MarkupText(StringMobject):
|
||||
def get_parts_by_text(self, selector: Selector) -> VGroup:
|
||||
return self.select_parts(selector)
|
||||
|
||||
def get_part_by_text(self, selector: Selector) -> VGroup:
|
||||
return self.select_part(selector)
|
||||
def get_part_by_text(self, selector: Selector, **kwargs) -> VGroup:
|
||||
return self.select_part(selector, **kwargs)
|
||||
|
||||
def set_color_by_text(self, selector: Selector, color: ManimColor):
|
||||
return self.set_parts_color(selector, color)
|
||||
@ -391,10 +315,70 @@ class MarkupText(StringMobject):
|
||||
return self.get_string()
|
||||
|
||||
|
||||
class Text(MarkupText):
|
||||
CONFIG = {
|
||||
"is_markup": False,
|
||||
class MarkupText(Text):
|
||||
# Unsupported passthroughs:
|
||||
# "<?...?>", "<!--...-->", "<![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">": ">",
|
||||
r"\"": """,
|
||||
r"'": "'"
|
||||
}
|
||||
MATCH_REPL = {
|
||||
r"<.*>": "",
|
||||
r"&#x(.*);": lambda m: chr(int(m.group(1), 16)),
|
||||
r"&#(.*);": lambda m: chr(int(m.group(1), 10)),
|
||||
r"<": "<",
|
||||
r">": ">",
|
||||
r"&": "&",
|
||||
r""": "\"",
|
||||
r"'": "'"
|
||||
}
|
||||
|
||||
# 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):
|
||||
|
@ -1,6 +1,4 @@
|
||||
default:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
default: |-
|
||||
\usepackage[english]{babel}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[T1]{fontenc}
|
||||
@ -22,9 +20,7 @@ default:
|
||||
\DisableLigatures{encoding = *, family = * }
|
||||
\linespread{1}
|
||||
|
||||
ctex:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
ctex: |-
|
||||
\usepackage[UTF8]{ctex}
|
||||
\usepackage[english]{babel}
|
||||
\usepackage{amsmath}
|
||||
@ -43,31 +39,23 @@ ctex:
|
||||
\usepackage{microtype}
|
||||
\linespread{1}
|
||||
|
||||
blank:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{xcolor}
|
||||
|
||||
basic:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
basic: |-
|
||||
\usepackage[english]{babel}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
|
||||
# A collection of TeX templates for the fonts described at
|
||||
# http://jf.burnol.free.fr/showcase.html
|
||||
|
||||
# American Typewriter
|
||||
american_typewriter:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
american_typewriter: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{American Typewriter}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Antykwa Poltawskiego (TX Fonts for Greek and math symbols)
|
||||
antykwa:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
antykwa: |-
|
||||
\usepackage[OT4,OT1]{fontenc}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
@ -75,42 +63,32 @@ antykwa:
|
||||
\usepackage[defaultmathsizes,nolessnomore]{mathastext}
|
||||
|
||||
# Apple Chancery
|
||||
apple_chancery:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
apple_chancery: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Apple Chancery}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Auriocus Kalligraphicus (Symbol Greek)
|
||||
auriocus_kalligraphicus:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
auriocus_kalligraphicus: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{aurical}
|
||||
\renewcommand{\rmdefault}{AuriocusKalligraphicus}
|
||||
\usepackage[symbolgreek]{mathastext}
|
||||
|
||||
# Baskervald ADF with Fourier
|
||||
baskervald_adf_fourier:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
baskervald_adf_fourier: |-
|
||||
\usepackage[upright]{fourier}
|
||||
\usepackage{baskervald}
|
||||
\usepackage[defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
# Baskerville (Italic)
|
||||
baskerville_it:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
baskerville_it: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Baskerville}
|
||||
\usepackage[defaultmathsizes,italic]{mathastext}
|
||||
|
||||
# Biolinum
|
||||
biolinum:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
biolinum: |-
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\usepackage[no-math]{fontspec}
|
||||
@ -121,9 +99,7 @@ biolinum:
|
||||
\renewcommand\familydefault\rmdefault
|
||||
|
||||
# BrushScriptX-Italic (PX math and Greek)
|
||||
brushscriptx:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
brushscriptx: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{pxfonts}
|
||||
\renewcommand{\rmdefault}{pbsi}
|
||||
@ -133,69 +109,53 @@ brushscriptx:
|
||||
\boldmath
|
||||
|
||||
# Chalkboard SE
|
||||
chalkboard_se:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
chalkboard_se: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Chalkboard SE}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Chalkduster
|
||||
chalkduster:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
chalkduster: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Chalkduster}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Comfortaa
|
||||
comfortaa:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
comfortaa: |-
|
||||
\usepackage[default]{comfortaa}
|
||||
\usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext}
|
||||
\let\varphi\phi
|
||||
\linespread{1.06}
|
||||
|
||||
# Comic Sans MS
|
||||
comic_sans:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
comic_sans: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Comic Sans MS}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Droid Sans
|
||||
droid_sans:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
droid_sans: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidsans}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
# Droid Sans (Italic)
|
||||
droid_sans_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
droid_sans_it: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidsans}
|
||||
\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
|
||||
\let\varphi\phi
|
||||
|
||||
# Droid Serif
|
||||
droid_serif:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
droid_serif: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidserif}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
# Droid Serif (PX math symbols) (Italic)
|
||||
droid_serif_px_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
droid_serif_px_it: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{pxfonts}
|
||||
\usepackage[default]{droidserif}
|
||||
@ -203,16 +163,12 @@ droid_serif_px_it:
|
||||
\let\varphi\phi
|
||||
|
||||
# ECF Augie (Euler Greek)
|
||||
ecf_augie:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
ecf_augie: |-
|
||||
\renewcommand\familydefault{fau}
|
||||
\usepackage[defaultmathsizes,eulergreek]{mathastext}
|
||||
|
||||
# ECF JD (with TX fonts)
|
||||
ecf_jd:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
ecf_jd: |-
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\renewcommand\familydefault{fjd}
|
||||
@ -220,9 +176,7 @@ ecf_jd:
|
||||
\mathversion{bold}
|
||||
|
||||
# ECF Skeetch (CM Greek)
|
||||
ecf_skeetch:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
ecf_skeetch: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\DeclareFontFamily{T1}{fsk}{}
|
||||
\DeclareFontShape{T1}{fsk}{m}{n}{<->s*[1.315] fskmw8t}{}
|
||||
@ -230,9 +184,7 @@ ecf_skeetch:
|
||||
\usepackage[noendash,defaultmathsizes,nohbar,defaultimath]{mathastext}
|
||||
|
||||
# ECF Tall Paul (with Symbol font)
|
||||
ecf_tall_paul:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
ecf_tall_paul: |-
|
||||
\DeclareFontFamily{T1}{ftp}{}
|
||||
\DeclareFontShape{T1}{ftp}{m}{n}{<->s*[1.4] ftpmw8t}{}
|
||||
\renewcommand\familydefault{ftp}
|
||||
@ -240,9 +192,7 @@ ecf_tall_paul:
|
||||
\let\infty\inftypsy
|
||||
|
||||
# ECF Webster (with TX fonts)
|
||||
ecf_webster:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
ecf_webster: |-
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\renewcommand\familydefault{fwb}
|
||||
@ -252,9 +202,7 @@ ecf_webster:
|
||||
\mathversion{bold}
|
||||
|
||||
# Electrum ADF (CM Greek)
|
||||
electrum_adf:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
electrum_adf: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[LGRgreek,basic,defaultmathsizes]{mathastext}
|
||||
\usepackage[lf]{electrum}
|
||||
@ -262,9 +210,7 @@ electrum_adf:
|
||||
\let\varphi\phi
|
||||
|
||||
# Epigrafica
|
||||
epigrafica:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
epigrafica: |-
|
||||
\usepackage[LGR,OT1]{fontenc}
|
||||
\usepackage{epigrafica}
|
||||
\usepackage[basic,LGRgreek,defaultmathsizes]{mathastext}
|
||||
@ -272,25 +218,19 @@ epigrafica:
|
||||
\linespread{1.2}
|
||||
|
||||
# Fourier Utopia (Fourier upright Greek)
|
||||
fourier_utopia:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
fourier_utopia: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[upright]{fourier}
|
||||
\usepackage{mathastext}
|
||||
|
||||
# French Cursive (Euler Greek)
|
||||
french_cursive:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
french_cursive: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{frcursive}
|
||||
\usepackage[eulergreek,noplusnominus,noequal,nohbar,nolessnomore,noasterisk]{mathastext}
|
||||
|
||||
# GFS Bodoni
|
||||
gfs_bodoni:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
gfs_bodoni: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\renewcommand{\rmdefault}{bodoni}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
@ -298,18 +238,14 @@ gfs_bodoni:
|
||||
\linespread{1.06}
|
||||
|
||||
# GFS Didot (Italic)
|
||||
gfs_didot:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
gfs_didot: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\renewcommand\rmdefault{udidot}
|
||||
\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
|
||||
\let\varphi\phi
|
||||
|
||||
# GFS NeoHellenic
|
||||
gfs_neoHellenic:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
gfs_neoHellenic: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\renewcommand{\rmdefault}{neohellenic}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
@ -317,18 +253,14 @@ gfs_neoHellenic:
|
||||
\linespread{1.06}
|
||||
|
||||
# GNU FreeSerif (and TX fonts symbols)
|
||||
gnu_freesans_tx:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
gnu_freesans_tx: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\usepackage{txfonts}
|
||||
\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# GNU FreeSerif and FreeSans
|
||||
gnu_freeserif_freesans:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
gnu_freeserif_freesans: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}
|
||||
\setsansfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSansBold,ItalicFont=FreeSansOblique,BoldItalicFont=FreeSansBoldOblique,Scale=MatchLowercase]{FreeSans}
|
||||
@ -340,9 +272,7 @@ gnu_freeserif_freesans:
|
||||
\renewcommand{\familydefault}{\rmdefault}
|
||||
|
||||
# Helvetica with Fourier (Italic)
|
||||
helvetica_fourier_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
helvetica_fourier_it: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[scaled]{helvet}
|
||||
\usepackage{fourier}
|
||||
@ -350,9 +280,7 @@ helvetica_fourier_it:
|
||||
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
# Latin Modern Typewriter Proportional
|
||||
latin_modern_tw:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
latin_modern_tw: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[variablett]{lmodern}
|
||||
\renewcommand{\rmdefault}{\ttdefault}
|
||||
@ -362,9 +290,7 @@ latin_modern_tw:
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
# Latin Modern Typewriter Proportional (CM Greek) (Italic)
|
||||
latin_modern_tw_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
latin_modern_tw_it: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[variablett,nomath]{lmodern}
|
||||
\renewcommand{\familydefault}{\ttdefault}
|
||||
@ -372,18 +298,14 @@ latin_modern_tw_it:
|
||||
\linespread{1.08}
|
||||
|
||||
# Libertine
|
||||
libertine:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
libertine: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{libertine}
|
||||
\usepackage[greek=n]{libgreek}
|
||||
\usepackage[noasterisk,defaultmathsizes]{mathastext}
|
||||
|
||||
# Libris ADF with Fourier
|
||||
libris_adf_fourier:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
libris_adf_fourier: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[upright]{fourier}
|
||||
\usepackage{libris}
|
||||
@ -391,36 +313,28 @@ libris_adf_fourier:
|
||||
\usepackage[noasterisk]{mathastext}
|
||||
|
||||
# Minion Pro and Myriad Pro (and TX fonts symbols)
|
||||
minion_pro_myriad_pro:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
minion_pro_myriad_pro: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidserif}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
# Minion Pro (and TX fonts symbols)
|
||||
minion_pro_tx:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
minion_pro_tx: |-
|
||||
\usepackage{txfonts}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Minion Pro}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# New Century Schoolbook (Symbol Greek)
|
||||
new_century_schoolbook:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
new_century_schoolbook: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{newcent}
|
||||
\usepackage[symbolgreek]{mathastext}
|
||||
\linespread{1.1}
|
||||
|
||||
# New Century Schoolbook (Symbol Greek, PX math symbols)
|
||||
new_century_schoolbook_px:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
new_century_schoolbook_px: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{pxfonts}
|
||||
\usepackage{newcent}
|
||||
@ -428,33 +342,25 @@ new_century_schoolbook_px:
|
||||
\linespread{1.06}
|
||||
|
||||
# Noteworthy Light
|
||||
noteworthy_light:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
noteworthy_light: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Noteworthy Light}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Palatino (Symbol Greek)
|
||||
palatino:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
palatino: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{palatino}
|
||||
\usepackage[symbolmax,defaultmathsizes]{mathastext}
|
||||
|
||||
# Papyrus
|
||||
papyrus:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
papyrus: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Papyrus}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Romande ADF with Fourier (Italic)
|
||||
romande_adf_fourier_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
romande_adf_fourier_it: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{fourier}
|
||||
\usepackage{romande}
|
||||
@ -462,9 +368,7 @@ romande_adf_fourier_it:
|
||||
\renewcommand{\itshape}{\swashstyle}
|
||||
|
||||
# SliTeX (Euler Greek)
|
||||
slitex:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
slitex: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{tpslifonts}
|
||||
\usepackage[eulergreek,defaultmathsizes]{mathastext}
|
||||
@ -472,26 +376,20 @@ slitex:
|
||||
\linespread{1.2}
|
||||
|
||||
# Times with Fourier (Italic)
|
||||
times_fourier_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
times_fourier_it: |-
|
||||
\usepackage{fourier}
|
||||
\renewcommand{\rmdefault}{ptm}
|
||||
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
# URW Avant Garde (Symbol Greek)
|
||||
urw_avant_garde:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
urw_avant_garde: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{avant}
|
||||
\renewcommand{\familydefault}{\sfdefault}
|
||||
\usepackage[symbolgreek,defaultmathsizes]{mathastext}
|
||||
|
||||
# URW Zapf Chancery (CM Greek)
|
||||
urw_zapf_chancery:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
urw_zapf_chancery: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\DeclareFontFamily{T1}{pzc}{}
|
||||
\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}
|
||||
@ -505,25 +403,19 @@ urw_zapf_chancery:
|
||||
\boldmath
|
||||
|
||||
# Venturis ADF with Fourier (Italic)
|
||||
venturis_adf_fourier_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
venturis_adf_fourier_it: |-
|
||||
\usepackage{fourier}
|
||||
\usepackage[lf]{venturis}
|
||||
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
# Verdana (Italic)
|
||||
verdana_it:
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
verdana_it: |-
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Verdana}
|
||||
\usepackage[defaultmathsizes,italic]{mathastext}
|
||||
|
||||
# Vollkorn (TX fonts for Greek and math symbols)
|
||||
vollkorn:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
vollkorn: |-
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
@ -531,17 +423,13 @@ vollkorn:
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
# Vollkorn with Fourier (Italic)
|
||||
vollkorn_fourier_it:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
vollkorn_fourier_it: |-
|
||||
\usepackage{fourier}
|
||||
\usepackage{vollkorn}
|
||||
\usepackage[italic,nohbar]{mathastext}
|
||||
|
||||
# Zapf Chancery
|
||||
zapf_chancery:
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
zapf_chancery: |-
|
||||
\DeclareFontFamily{T1}{pzc}{}
|
||||
\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}
|
||||
\DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{}
|
||||
|
@ -44,8 +44,9 @@ def init_customization() -> None:
|
||||
},
|
||||
"universal_import_line": "from manimlib import *",
|
||||
"style": {
|
||||
"tex_compiler": "",
|
||||
"tex_font": "",
|
||||
"font": "",
|
||||
"font": "Consolas",
|
||||
"background_color": "",
|
||||
},
|
||||
"window_position": "UR",
|
||||
@ -57,7 +58,7 @@ def init_customization() -> None:
|
||||
"medium": "1280x720",
|
||||
"high": "1920x1080",
|
||||
"4k": "3840x2160",
|
||||
"default_resolution": "high",
|
||||
"default_resolution": "",
|
||||
},
|
||||
"fps": 30,
|
||||
}
|
||||
@ -106,14 +107,16 @@ def init_customization() -> None:
|
||||
|
||||
console.print("[bold]Styles:[/bold]")
|
||||
style_config = configuration["style"]
|
||||
style_config["tex_font"] = Prompt.ask(
|
||||
" Which [bold]font[/bold] for LaTeX do you want",
|
||||
default="default"
|
||||
)
|
||||
style_config["font"] = Prompt.ask(
|
||||
" Which [bold]font[/bold] for non-LaTeX text do you want",
|
||||
default="Consolas"
|
||||
compiler = Prompt.ask(
|
||||
" Select an executable program to use to compile a LaTeX source file",
|
||||
choices=["latex", "xelatex"],
|
||||
default="latex"
|
||||
)
|
||||
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(
|
||||
" Which [bold]background color[/bold] do you want [italic](hex code)",
|
||||
default="#333333"
|
||||
@ -127,7 +130,7 @@ def init_customization() -> None:
|
||||
)
|
||||
table.add_row("480p15", "720p30", "1080p60", "2160p60")
|
||||
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",
|
||||
choices=["low", "medium", "high", "ultra_high"],
|
||||
default="high"
|
||||
|
@ -15,14 +15,7 @@ from manimlib.utils.simple_functions import hash_string
|
||||
SAVED_TEX_CONFIG = {}
|
||||
|
||||
|
||||
def get_tex_font_config(tex_font: str) -> str:
|
||||
"""
|
||||
Returns a dict which should look something like this:
|
||||
{
|
||||
"compiler": "latex",
|
||||
"preamble": "..."
|
||||
}
|
||||
"""
|
||||
def get_tex_font_preamble(tex_font: str) -> str:
|
||||
name = re.sub(r"[^a-zA-Z]", "_", tex_font).lower()
|
||||
with open(os.path.join(
|
||||
get_manim_dir(), "manimlib", "tex_fonts.yml"
|
||||
@ -35,30 +28,40 @@ def get_tex_font_config(tex_font: str) -> str:
|
||||
)
|
||||
name = "default"
|
||||
result = templates_dict[name]
|
||||
if name not in ("default", "ctex", "blank", "basic"):
|
||||
result["preamble"] = "\n".join((
|
||||
templates_dict["basic"]["preamble"], result["preamble"]
|
||||
))
|
||||
if name not in ("default", "ctex", "basic"):
|
||||
result = templates_dict["basic"] + "\n" + result
|
||||
return result
|
||||
|
||||
|
||||
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
|
||||
if not SAVED_TEX_CONFIG:
|
||||
tex_font = get_custom_config()["style"]["tex_font"]
|
||||
SAVED_TEX_CONFIG.update(get_tex_font_config(tex_font))
|
||||
style_config = get_custom_config()["style"]
|
||||
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
|
||||
|
||||
|
||||
def tex_content_to_svg_file(
|
||||
content: str, tex_font: str, additional_preamble: str
|
||||
) -> str:
|
||||
if not tex_font:
|
||||
tex_config = get_tex_config()
|
||||
else:
|
||||
tex_config = get_tex_font_config(tex_font)
|
||||
|
||||
if not tex_font or tex_font == tex_config["font"]:
|
||||
preamble = tex_config["preamble"]
|
||||
else:
|
||||
preamble = get_tex_font_preamble(tex_font)
|
||||
|
||||
if additional_preamble:
|
||||
preamble += "\n" + additional_preamble
|
||||
full_tex = "\n\n".join((
|
||||
|
Reference in New Issue
Block a user