Skip to content

Commit 5e1070f

Browse files
Fix the coloring schems to make sure it is consistent and refactored _break_up_by_substrings function to improve understandability
1 parent 084b82f commit 5e1070f

File tree

1 file changed

+100
-61
lines changed

1 file changed

+100
-61
lines changed

manim/mobject/text/tex_mobject.py

Lines changed: 100 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -363,74 +363,113 @@ def _break_up_by_substrings(self) -> Self:
363363
curr_index = new_index
364364
i += 1
365365
elif tex_string.strip().startswith(("^", "_")):
366-
# Handle consecutive scripts as a group
367-
script_group = [tex_string]
368-
j = i + 1
369-
while j < len(self.tex_strings) and self.tex_strings[
370-
j
371-
].strip().startswith(("^", "_")):
372-
script_group.append(self.tex_strings[j])
373-
j += 1
374-
375-
# Calculate total submobjects needed for all scripts
376-
total_script_submobs = sum(
377-
len(
378-
SingleStringMathTex(
379-
s,
380-
tex_environment=self.tex_environment,
381-
tex_template=self.tex_template,
382-
).submobjects
383-
)
384-
for s in script_group
385-
)
366+
# Handle consecutive scripts as a group, matching by Y-position
367+
script_group, j = self._group_consecutive_scripts(i)
368+
total_script_submobs = self._total_submobs_for_scripts(script_group)
369+
script_pool = self.submobjects[curr_index:curr_index + total_script_submobs]
370+
371+
self._assign_script_group(script_group, script_pool, new_submobjects)
386372

387-
# Get the pool of available submobjects for all scripts
388-
script_pool = self.submobjects[
389-
curr_index : curr_index + total_script_submobs
390-
]
391-
392-
# Process each script in the group
393-
for script_tex in script_group:
394-
script_mob = SingleStringMathTex(
395-
script_tex,
396-
tex_environment=self.tex_environment,
397-
tex_template=self.tex_template,
398-
)
399-
script_num_submobs = len(script_mob.submobjects)
400-
401-
if script_num_submobs > 0 and len(script_pool) > 0:
402-
# Select submobjects by Y position
403-
is_superscript = script_tex.strip().startswith("^")
404-
sorted_pool = sorted(
405-
script_pool,
406-
key=lambda mob: mob.get_center()[1],
407-
reverse=is_superscript, # highest first for ^, lowest first for _
408-
)
409-
410-
# Take the first script_num_submobs from sorted pool
411-
selected = sorted_pool[:script_num_submobs]
412-
script_mob.submobjects = selected
413-
414-
# Remove selected submobjects from pool
415-
for sel in selected:
416-
if sel in script_pool:
417-
script_pool.remove(sel)
418-
419-
new_submobjects.append(script_mob)
420-
421-
# Update indices
422373
curr_index += total_script_submobs
423-
i = j # Skip past all processed scripts
374+
i = j
424375
else:
425-
# Normal (non-script) processing
426-
sub_tex_mob.submobjects = self.submobjects[curr_index:new_index]
427-
new_submobjects.append(sub_tex_mob)
428-
curr_index = new_index
429-
i += 1
376+
# Base element processing: check if followed by scripts
377+
next_is_script = (i + 1 < len(self.tex_strings) and
378+
self.tex_strings[i + 1].strip().startswith(("^", "_")))
379+
380+
if next_is_script and num_submobs > 0:
381+
script_group, j = self._group_consecutive_scripts(i + 1)
382+
total_script_submobs = self._total_submobs_for_scripts(script_group)
383+
total_needed = num_submobs + total_script_submobs
384+
385+
all_submobs = self.submobjects[curr_index:curr_index + total_needed]
386+
387+
# Only use special handling if scripts have content (non-empty)
388+
if total_script_submobs > 0 and len(all_submobs) == total_needed:
389+
# LaTeX may render base+scripts in unexpected order
390+
# Find base by Y-position: closest to baseline (Y=0)
391+
base_submob = min(all_submobs, key=lambda m: abs(m.get_center()[1]))
392+
sub_tex_mob.submobjects = [base_submob]
393+
394+
script_pool = [m for m in all_submobs if m != base_submob]
395+
new_submobjects.append(sub_tex_mob)
396+
397+
self._assign_script_group(script_group, script_pool, new_submobjects)
398+
399+
curr_index += total_needed
400+
i = j
401+
else:
402+
# Fallback if counts don't match
403+
sub_tex_mob.submobjects = self.submobjects[curr_index:new_index]
404+
new_submobjects.append(sub_tex_mob)
405+
curr_index = new_index
406+
i += 1
407+
else:
408+
sub_tex_mob.submobjects = self.submobjects[curr_index:new_index]
409+
new_submobjects.append(sub_tex_mob)
410+
curr_index = new_index
411+
i += 1
430412

431413
self.submobjects = new_submobjects
432414
return self
433415

416+
def _group_consecutive_scripts(self, start_index: int) -> tuple[list[str], int]:
417+
"""Collect consecutive script tex_strings starting at ``start_index``.
418+
419+
Returns the list of scripts and the index just after the group.
420+
Scripts are tex strings starting with '^' or '_'.
421+
"""
422+
script_group = [self.tex_strings[start_index]]
423+
j = start_index + 1
424+
while j < len(self.tex_strings) and self.tex_strings[j].strip().startswith(("^", "_")):
425+
script_group.append(self.tex_strings[j])
426+
j += 1
427+
return script_group, j
428+
429+
def _total_submobs_for_scripts(self, script_group: list[str]) -> int:
430+
"""Calculate total submobject count for a group of script strings.
431+
432+
Creates temporary SingleStringMathTex instances to inspect counts.
433+
"""
434+
total = 0
435+
for s in script_group:
436+
total += len(
437+
SingleStringMathTex(
438+
s, tex_environment=self.tex_environment, tex_template=self.tex_template
439+
).submobjects
440+
)
441+
return total
442+
443+
def _assign_script_group(self, script_group: list[str], script_pool: list[VMobject], new_submobjects: list[VMobject]) -> None:
444+
"""Assign submobjects from ``script_pool`` to scripts in ``script_group``.
445+
446+
Selection strategy:
447+
- Superscripts ('^'): Pick highest Y-position items (above baseline)
448+
- Subscripts ('_'): Pick lowest Y-position items (below baseline)
449+
450+
Selected submobjects are removed from pool to prevent reuse.
451+
"""
452+
for script_tex in script_group:
453+
script_mob = SingleStringMathTex(
454+
script_tex, tex_environment=self.tex_environment, tex_template=self.tex_template
455+
)
456+
script_num_submobs = len(script_mob.submobjects)
457+
458+
if script_num_submobs > 0 and len(script_pool) > 0:
459+
is_superscript = script_tex.strip().startswith("^")
460+
# Sort by Y-position: reverse=True for superscripts (highest first)
461+
sorted_pool = sorted(script_pool, key=lambda mob: mob.get_center()[1], reverse=is_superscript)
462+
463+
selected = sorted_pool[:script_num_submobs]
464+
script_mob.submobjects = selected
465+
466+
# Remove selected items from pool
467+
for sel in selected:
468+
if sel in script_pool:
469+
script_pool.remove(sel)
470+
471+
new_submobjects.append(script_mob)
472+
434473
def get_parts_by_tex(
435474
self, tex: str, substring: bool = True, case_sensitive: bool = True
436475
) -> VGroup:

0 commit comments

Comments
 (0)