mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
fix: fixed bad text slicing for Paragraph's lines (#2721)
* fix: fixed bad text slicing for Paragraph's lines Inside Paragraph's _gen_chars method, a bad string slicing caused the text's characters to be obtained in an incorrect order. This would cause chars (and thus self.chars and self.lines in __init__) to store incorrect values. If Paragraph's alignment parameter was None, nothing wrong seemed to happen, but if alignment was 'left', 'center or 'right', the text's lines would not display correctly. This happened because the slicing was of the form [begin : begin + string_length + 1]. This bug was fixed by changing it to [begin : begin + char_count], where char_count is the number of characters in the corresponding string which are not " ", "\n" or "\t". I've also cleaned __init__'s code: - There was a redundant fragment of code which did exactly the same thing as _gen_chars, storing data in chars_lines_text_list. I deleted this code and used self.chars instead. - lines_str joins the text strings with '\n', but then lines_str_list split lines_str again, with '\n' as delimiter. I rewrote lines_str_list as just list(text). - self.lines is a list which essentially stores list(self.chars) as its first element. The original code used a for loop to append self.chars' lines, one by one, to self.lines[0]. I replaced this for loop with list(self.chars). - self.lines' second element was rewritten as a repetition of [self.alignment], self.chars.__len()__ times. - I replaced the for loop used to append the line centers to self.lines_initial_positions, and instead used [line.get_center() for line in self.lines[0]]. * revert: reverted lines_str and lines_str_list changes * fix: added Paragraph.consider_spaces_as_chars to fix issue * fix: changed `config` kwargs name to `kwargs` Using `config` as a name for keyword arguments in `Paragraph.__init__` is inconsistent with the use of `kwargs` everywhere else and also shadows the `config` global variable, which is why this variable was renamed as `kwargs`. * refactor: changed `var.__len__()` to `len(var)` Changed 8 appearances (1 in `remove_invisible_chars`, 6 in `Paragraph` and 1 in `Text`) of magic method `__len__` (as in `var.__len__()`) to `len(var)`. * refactor: changed docstrings and added type hints * fix: changed space char hardcoding to isspace() string method Co-authored-by: Tristan Schulz <mrdiverlp@gmail.com>
This commit is contained in:
parent
a20f8aeb6c
commit
b5681fd905
1 changed files with 73 additions and 73 deletions
|
|
@ -79,12 +79,12 @@ DEFAULT_LINE_SPACING_SCALE = 0.3
|
|||
TEXT2SVG_ADJUSTMENT_FACTOR = 4.8
|
||||
|
||||
|
||||
def remove_invisible_chars(mobject):
|
||||
def remove_invisible_chars(mobject: SVGMobject) -> SVGMobject:
|
||||
"""Function to remove unwanted invisible characters from some mobjects.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mobject : :class:`~.SVGMobject`
|
||||
mobject
|
||||
Any SVGMobject from which we want to remove unwanted invisible characters.
|
||||
|
||||
Returns
|
||||
|
|
@ -102,7 +102,7 @@ def remove_invisible_chars(mobject):
|
|||
mobject = mobject.code
|
||||
mobject_without_dots = VGroup()
|
||||
if mobject[0].__class__ == VGroup:
|
||||
for i in range(mobject.__len__()):
|
||||
for i in range(len(mobject)):
|
||||
mobject_without_dots.add(VGroup())
|
||||
mobject_without_dots[i].add(*(k for k in mobject[i] if k.__class__ != Dot))
|
||||
else:
|
||||
|
|
@ -123,10 +123,10 @@ class Paragraph(VGroup):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
line_spacing : :class:`float`, optional
|
||||
Represents the spacing between lines. Default to -1, which means auto.
|
||||
alignment : :class:`str`, optional
|
||||
Defines the alignment of paragraph. Default to "left". Possible values are "left", "right", "center"
|
||||
line_spacing
|
||||
Represents the spacing between lines. Defaults to -1, which means auto.
|
||||
alignment
|
||||
Defines the alignment of paragraph. Defaults to None. Possible values are "left", "right" or "center".
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
|
@ -144,50 +144,37 @@ class Paragraph(VGroup):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, *text, line_spacing=-1, alignment=None, **config):
|
||||
def __init__(
|
||||
self,
|
||||
*text: Sequence[str],
|
||||
line_spacing: float = -1,
|
||||
alignment: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
self.line_spacing = line_spacing
|
||||
self.alignment = alignment
|
||||
self.consider_spaces_as_chars = kwargs.get("disable_ligatures", False)
|
||||
super().__init__()
|
||||
|
||||
lines_str = "\n".join(list(text))
|
||||
self.lines_text = Text(lines_str, line_spacing=line_spacing, **config)
|
||||
self.lines_text = Text(lines_str, line_spacing=line_spacing, **kwargs)
|
||||
lines_str_list = lines_str.split("\n")
|
||||
self.chars = self._gen_chars(lines_str_list)
|
||||
|
||||
chars_lines_text_list = self.get_group_class()()
|
||||
char_index_counter = 0
|
||||
for line_index in range(lines_str_list.__len__()):
|
||||
chars_lines_text_list.add(
|
||||
self.lines_text[
|
||||
char_index_counter : char_index_counter
|
||||
+ lines_str_list[line_index].__len__()
|
||||
+ 1
|
||||
],
|
||||
)
|
||||
char_index_counter += lines_str_list[line_index].__len__() + 1
|
||||
self.lines = []
|
||||
self.lines.append([])
|
||||
for line_no in range(chars_lines_text_list.__len__()):
|
||||
self.lines[0].append(chars_lines_text_list[line_no])
|
||||
self.lines_initial_positions = []
|
||||
for line_no in range(self.lines[0].__len__()):
|
||||
self.lines_initial_positions.append(self.lines[0][line_no].get_center())
|
||||
self.lines.append([])
|
||||
self.lines[1].extend(
|
||||
[self.alignment for _ in range(chars_lines_text_list.__len__())],
|
||||
)
|
||||
self.lines = [list(self.chars), [self.alignment] * len(self.chars)]
|
||||
self.lines_initial_positions = [line.get_center() for line in self.lines[0]]
|
||||
self.add(*self.lines[0])
|
||||
self.move_to(np.array([0, 0, 0]))
|
||||
if self.alignment:
|
||||
self._set_all_lines_alignments(self.alignment)
|
||||
|
||||
def _gen_chars(self, lines_str_list):
|
||||
"""Function to convert plain string to 2d-VGroup of chars. 2d-VGroup mean "VGroup of VGroup".
|
||||
def _gen_chars(self, lines_str_list: list) -> VGroup:
|
||||
"""Function to convert a list of plain strings to a VGroup of VGroups of chars.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines_str_list : :class:`str`
|
||||
Plain text string.
|
||||
lines_str_list
|
||||
List of plain text strings.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -196,72 +183,85 @@ class Paragraph(VGroup):
|
|||
"""
|
||||
char_index_counter = 0
|
||||
chars = self.get_group_class()()
|
||||
for line_no in range(lines_str_list.__len__()):
|
||||
for line_no in range(len(lines_str_list)):
|
||||
line_str = lines_str_list[line_no]
|
||||
# Count all the characters in line_str
|
||||
# Spaces may or may not count as characters
|
||||
if self.consider_spaces_as_chars:
|
||||
char_count = len(line_str)
|
||||
else:
|
||||
char_count = 0
|
||||
for char in line_str:
|
||||
if not char.isspace():
|
||||
char_count += 1
|
||||
|
||||
chars.add(self.get_group_class()())
|
||||
chars[line_no].add(
|
||||
*self.lines_text.chars[
|
||||
char_index_counter : char_index_counter
|
||||
+ lines_str_list[line_no].__len__()
|
||||
+ 1
|
||||
char_index_counter : char_index_counter + char_count
|
||||
]
|
||||
)
|
||||
char_index_counter += lines_str_list[line_no].__len__() + 1
|
||||
char_index_counter += char_count
|
||||
if self.consider_spaces_as_chars:
|
||||
# If spaces count as characters, count the extra \n character
|
||||
# which separates Paragraph's lines to avoid issues
|
||||
char_index_counter += 1
|
||||
return chars
|
||||
|
||||
def _set_all_lines_alignments(self, alignment):
|
||||
def _set_all_lines_alignments(self, alignment: str) -> Paragraph:
|
||||
"""Function to set all line's alignment to a specific value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
alignment : :class:`str`
|
||||
alignment
|
||||
Defines the alignment of paragraph. Possible values are "left", "right", "center".
|
||||
"""
|
||||
for line_no in range(0, self.lines[0].__len__()):
|
||||
for line_no in range(len(self.lines[0])):
|
||||
self._change_alignment_for_a_line(alignment, line_no)
|
||||
return self
|
||||
|
||||
def _set_line_alignment(self, alignment, line_no):
|
||||
def _set_line_alignment(self, alignment: str, line_no: int) -> Paragraph:
|
||||
"""Function to set one line's alignment to a specific value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
alignment : :class:`str`
|
||||
alignment
|
||||
Defines the alignment of paragraph. Possible values are "left", "right", "center".
|
||||
line_no : :class:`int`
|
||||
line_no
|
||||
Defines the line number for which we want to set given alignment.
|
||||
"""
|
||||
self._change_alignment_for_a_line(alignment, line_no)
|
||||
return self
|
||||
|
||||
def _set_all_lines_to_initial_positions(self):
|
||||
def _set_all_lines_to_initial_positions(self) -> Paragraph:
|
||||
"""Set all lines to their initial positions."""
|
||||
self.lines[1] = [None for _ in range(self.lines[0].__len__())]
|
||||
for line_no in range(0, self.lines[0].__len__()):
|
||||
self.lines[1] = [None] * len(self.lines[0])
|
||||
for line_no in range(len(self.lines[0])):
|
||||
self[line_no].move_to(
|
||||
self.get_center() + self.lines_initial_positions[line_no],
|
||||
)
|
||||
return self
|
||||
|
||||
def _set_line_to_initial_position(self, line_no):
|
||||
def _set_line_to_initial_position(self, line_no: int) -> Paragraph:
|
||||
"""Function to set one line to initial positions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line_no : :class:`int`
|
||||
line_no
|
||||
Defines the line number for which we want to set given alignment.
|
||||
"""
|
||||
self.lines[1][line_no] = None
|
||||
self[line_no].move_to(self.get_center() + self.lines_initial_positions[line_no])
|
||||
return self
|
||||
|
||||
def _change_alignment_for_a_line(self, alignment, line_no):
|
||||
def _change_alignment_for_a_line(self, alignment: str, line_no: int) -> None:
|
||||
"""Function to change one line's alignment to a specific value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
alignment : :class:`str`
|
||||
alignment
|
||||
Defines the alignment of paragraph. Possible values are "left", "right", "center".
|
||||
line_no : :class:`int`
|
||||
line_no
|
||||
Defines the line number for which we want to set given alignment.
|
||||
"""
|
||||
self.lines[1][line_no] = alignment
|
||||
|
|
@ -299,13 +299,13 @@ class Text(SVGMobject):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
text : :class:`str`
|
||||
The text that need to created as mobject.
|
||||
text
|
||||
The text that needs to be created as a mobject.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`Text`
|
||||
The mobject like :class:`.VGroup`.
|
||||
The mobject-like :class:`.VGroup`.
|
||||
|
||||
Examples
|
||||
---------
|
||||
|
|
@ -426,7 +426,7 @@ class Text(SVGMobject):
|
|||
unpack_groups: bool = True,
|
||||
disable_ligatures: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
) -> None:
|
||||
|
||||
self.line_spacing = line_spacing
|
||||
self.font = font
|
||||
|
|
@ -540,8 +540,8 @@ class Text(SVGMobject):
|
|||
def _gen_chars(self):
|
||||
chars = self.get_group_class()()
|
||||
submobjects_char_index = 0
|
||||
for char_index in range(self.text.__len__()):
|
||||
if self.text[char_index] in (" ", "\t", "\n"):
|
||||
for char_index in range(len(self.text)):
|
||||
if self.text[char_index].isspace():
|
||||
space = Dot(radius=0, fill_opacity=0, stroke_opacity=0)
|
||||
if char_index == 0:
|
||||
space.move_to(self.submobjects[submobjects_char_index].get_center())
|
||||
|
|
@ -895,23 +895,23 @@ class MarkupText(SVGMobject):
|
|||
Parameters
|
||||
----------
|
||||
|
||||
text : :class:`str`
|
||||
The text that need to created as mobject.
|
||||
fill_opacity : :class:`int`
|
||||
The fill opacity with 1 meaning opaque and 0 meaning transparent.
|
||||
stroke_width : :class:`int`
|
||||
text
|
||||
The text that needs to be created as mobject.
|
||||
fill_opacity
|
||||
The fill opacity, with 1 meaning opaque and 0 meaning transparent.
|
||||
stroke_width
|
||||
Stroke width.
|
||||
font_size : :class:`float`
|
||||
font_size
|
||||
Font size.
|
||||
line_spacing : :class:`int`
|
||||
line_spacing
|
||||
Line spacing.
|
||||
font : :class:`str`
|
||||
font
|
||||
Global font setting for the entire text. Local overrides are possible.
|
||||
slant : :class:`str`
|
||||
slant
|
||||
Global slant setting, e.g. `NORMAL` or `ITALIC`. Local overrides are possible.
|
||||
weight : :class:`str`
|
||||
weight
|
||||
Global weight setting, e.g. `NORMAL` or `BOLD`. Local overrides are possible.
|
||||
gradient: :class:`tuple`
|
||||
gradient
|
||||
Global gradient setting. Local overrides are possible.
|
||||
|
||||
|
||||
|
|
@ -1107,7 +1107,7 @@ class MarkupText(SVGMobject):
|
|||
unpack_groups: bool = True,
|
||||
disable_ligatures: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
) -> None:
|
||||
|
||||
self.text = text
|
||||
self.line_spacing = line_spacing
|
||||
|
|
@ -1385,7 +1385,7 @@ def register_font(font_file: str | Path):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
font_file :
|
||||
font_file
|
||||
The font file to add.
|
||||
|
||||
Examples
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue