Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 1 addition & 306 deletions manim/mobject/svg/text_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def construct(self):

"""

__all__ = ["Text", "Paragraph", "CairoText", "MarkupText", "register_font"]
__all__ = ["Text", "Paragraph", "MarkupText", "register_font"]


import copy
Expand Down Expand Up @@ -104,307 +104,6 @@ def remove_invisible_chars(mobject):
return mobject_without_dots


class CairoText(SVGMobject):
"""Display (non-LaTeX) text.

Text objects behave like a :class:`.VGroup`-like iterable of all characters
in the given text. In particular, slicing is possible.


Tests
-----

Check whether writing text works::

>>> Text('The horse does not eat cucumber salad.')
Text('The horse does not eat cucumber salad.')

"""

def __init__(
self,
text,
# Mobject
color=WHITE,
height=None,
width=None,
fill_opacity=1,
stroke_width=0,
should_center=True,
unpack_groups=True,
# Text
font="",
gradient=None,
line_spacing=-1,
size=1,
slant: str = NORMAL,
weight: str = NORMAL,
t2c=None,
t2f=None,
t2g=None,
t2s=None,
t2w=None,
tab_width=4,
**kwargs,
):
# self.full2short(config)
if t2c is None:
t2c = {}
if t2f is None:
t2f = {}
if t2g is None:
t2g = {}
if t2s is None:
t2s = {}
if t2w is None:
t2w = {}
# If long form arguments are present, they take precedence
t2c = kwargs.pop("text2color", t2c)
t2f = kwargs.pop("text2font", t2f)
t2g = kwargs.pop("text2gradient", t2g)
t2s = kwargs.pop("text2slant", t2s)
t2w = kwargs.pop("text2weight", t2w)
self.t2c = t2c
self.t2f = t2f
self.t2g = t2g
self.t2s = t2s
self.t2w = t2w
self.font = font
self.gradient = gradient
self.line_spacing = line_spacing
self.size = size
self.slant = slant
self.weight = weight
self.tab_width = tab_width
self.original_text = text
text_without_tabs = text
if text.find("\t") != -1:
text_without_tabs = text.replace("\t", " " * self.tab_width)
self.text = text_without_tabs
if self.line_spacing == -1:
self.line_spacing = self.size + self.size * 0.3
else:
self.line_spacing = self.size + self.size * self.line_spacing
file_name = self.text2svg()
PangoUtils.remove_last_M(file_name)
SVGMobject.__init__(
self,
file_name,
height=height,
width=width,
unpack_groups=unpack_groups,
color=color,
fill_opacity=fill_opacity,
stroke_width=stroke_width,
should_center=should_center,
**kwargs,
)
self.text = text
self.submobjects = [*self.gen_chars()]
self.chars = VGroup(*self.submobjects)
self.text = text_without_tabs.replace(" ", "").replace("\n", "")
nppc = self.n_points_per_cubic_curve
for each in self:
if len(each.points) == 0:
continue
points = each.points
last = points[0]
each.clear_points()
for index, point in enumerate(points):
each.append_points([point])
if (
index != len(points) - 1
and (index + 1) % nppc == 0
and any(point != points[index + 1])
):
each.add_line_to(last)
last = points[index + 1]
each.add_line_to(last)
if self.t2c:
self.set_color_by_t2c()
if self.gradient:
self.set_color_by_gradient(*self.gradient)
if self.t2g:
self.set_color_by_t2g()
# anti-aliasing
if height is None and width is None:
self.scale(TEXT_MOB_SCALE_FACTOR)

def __repr__(self):
return f"Text({repr(self.original_text)})"

def gen_chars(self):
chars = VGroup()
submobjects_char_index = 0
for char_index in range(self.text.__len__()):
if (
self.text[char_index] == " "
or self.text[char_index] == "\t"
or self.text[char_index] == "\n"
):
space = Dot(radius=0, fill_opacity=0, stroke_opacity=0)
if char_index == 0:
space.move_to(self.submobjects[submobjects_char_index].get_center())
else:
space.move_to(
self.submobjects[submobjects_char_index - 1].get_center()
)
chars.add(space)
else:
chars.add(self.submobjects[submobjects_char_index])
submobjects_char_index += 1
return chars

def find_indexes(self, word, text):
m = re.match(r"\[([0-9\-]{0,}):([0-9\-]{0,})\]", word)
if m:
start = int(m.group(1)) if m.group(1) != "" else 0
end = int(m.group(2)) if m.group(2) != "" else len(text)
start = len(text) + start if start < 0 else start
end = len(text) + end if end < 0 else end
return [(start, end)]
indexes = []
index = text.find(word)
while index != -1:
indexes.append((index, index + len(word)))
index = text.find(word, index + len(word))
return indexes

# def full2short(self, config_args):
# for kwargs in [config_args]:
# if kwargs.__contains__("text2color"):
# kwargs["t2c"] = kwargs.pop("text2color")
# if kwargs.__contains__("text2font"):
# kwargs["t2f"] = kwargs.pop("text2font")
# if kwargs.__contains__("text2gradient"):
# kwargs["t2g"] = kwargs.pop("text2gradient")
# if kwargs.__contains__("text2slant"):
# kwargs["t2s"] = kwargs.pop("text2slant")
# if kwargs.__contains__("text2weight"):
# kwargs["t2w"] = kwargs.pop("text2weight")

def set_color_by_t2c(self, t2c=None):
t2c = t2c if t2c else self.t2c
for word, color in list(t2c.items()):
for start, end in self.find_indexes(word, self.original_text):
self.chars[start:end].set_color(color)

def set_color_by_t2g(self, t2g=None):
t2g = t2g if t2g else self.t2g
for word, gradient in list(t2g.items()):
for start, end in self.find_indexes(word, self.original_text):
self.chars[start:end].set_color_by_gradient(*gradient)

def str2slant(self, string):
if string == NORMAL:
return cairo.FontSlant.NORMAL
if string == ITALIC:
return cairo.FontSlant.ITALIC
if string == OBLIQUE:
return cairo.FontSlant.OBLIQUE

def str2weight(self, string):
if string == NORMAL:
return cairo.FontWeight.NORMAL
if string == BOLD:
return cairo.FontWeight.BOLD

def text2hash(self):
settings = self.font + self.slant + self.weight
settings += str(self.t2f) + str(self.t2s) + str(self.t2w)
settings += str(self.line_spacing) + str(self.size)
id_str = self.text + settings
hasher = hashlib.sha256()
hasher.update(id_str.encode())
return hasher.hexdigest()[:16]

def text2settings(self):
settings = []
t2x = [self.t2f, self.t2s, self.t2w]
for i in range(len(t2x)):
fsw = [self.font, self.slant, self.weight]
if t2x[i]:
for word, x in list(t2x[i].items()):
for start, end in self.find_indexes(word, self.text):
fsw[i] = x
settings.append(TextSetting(start, end, *fsw))
# Set All text settings(default font slant weight)
fsw = [self.font, self.slant, self.weight]
settings.sort(key=lambda setting: setting.start)
temp_settings = settings.copy()
start = 0
for setting in settings:
if setting.start != start:
temp_settings.append(TextSetting(start, setting.start, *fsw))
start = setting.end
if start != len(self.text):
temp_settings.append(TextSetting(start, len(self.text), *fsw))
settings = sorted(temp_settings, key=lambda setting: setting.start)

if re.search(r"\n", self.text):
line_num = 0
for start, end in self.find_indexes("\n", self.text):
for setting in settings:
if setting.line_num == -1:
setting.line_num = line_num
if start < setting.end:
line_num += 1
new_setting = copy.copy(setting)
setting.end = end
new_setting.start = end
new_setting.line_num = line_num
settings.append(new_setting)
settings.sort(key=lambda setting: setting.start)
break
for setting in settings:
if setting.line_num == -1:
setting.line_num = 0
return settings

def text2svg(self):
# anti-aliasing
size = self.size * 10
line_spacing = self.line_spacing * 10

if self.font == "":
if NOT_SETTING_FONT_MSG:
logger.warning(NOT_SETTING_FONT_MSG)

dir_name = config.get_dir("text_dir")
if not os.path.exists(dir_name):
os.makedirs(dir_name)

hash_name = self.text2hash()
file_name = os.path.join(dir_name, hash_name) + ".svg"
if os.path.exists(file_name):
return file_name
surface = cairo.SVGSurface(file_name, 600, 400)
context = cairo.Context(surface)
context.set_font_size(size)
context.move_to(START_X, START_Y)

settings = self.text2settings()
offset_x = 0
last_line_num = 0
for setting in settings:
font = setting.font.decode("utf-8")
slant = self.str2slant(setting.slant)
weight = self.str2weight(setting.weight)
text = self.text[setting.start : setting.end].replace("\n", " ")

context.select_font_face(font, slant, weight)
if setting.line_num != last_line_num:
offset_x = 0
last_line_num = setting.line_num
context.move_to(
START_X + offset_x, START_Y + line_spacing * setting.line_num
)
context.show_text(text)
offset_x += context.text_extents(text)[4]
surface.finish()
return file_name


class Paragraph(VGroup):
r"""Display a paragraph of text.

Expand Down Expand Up @@ -1466,10 +1165,6 @@ def register_font(font_file: typing.Union[str, Path]):
AttributeError:
If this method is used on macOS.

Notes
-----
This method of adding font files also works with :class:`CairoText`.

.. important ::

This method is available for macOS for ``ManimPango>=v0.2.3``. Using this
Expand Down