manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- manim/utils/testing/__init__.py +17 -0
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
.. important::
|
|
8
8
|
|
|
9
|
-
See the corresponding tutorial :ref:`
|
|
9
|
+
See the corresponding tutorial :ref:`using-text-objects`, especially for information about fonts.
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
The simplest way to add text to your animations is to use the :class:`~.Text` class. It uses the Pango library to render text.
|
|
@@ -49,21 +49,22 @@ Examples
|
|
|
49
49
|
|
|
50
50
|
from __future__ import annotations
|
|
51
51
|
|
|
52
|
+
import functools
|
|
53
|
+
|
|
52
54
|
__all__ = ["Text", "Paragraph", "MarkupText", "register_font"]
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
import copy
|
|
56
58
|
import hashlib
|
|
57
|
-
import os
|
|
58
59
|
import re
|
|
60
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
59
61
|
from contextlib import contextmanager
|
|
60
62
|
from itertools import chain
|
|
61
63
|
from pathlib import Path
|
|
62
|
-
from typing import
|
|
64
|
+
from typing import TYPE_CHECKING, Any
|
|
63
65
|
|
|
64
66
|
import manimpango
|
|
65
67
|
import numpy as np
|
|
66
|
-
from colour import Color
|
|
67
68
|
from manimpango import MarkupUtils, PangoUtils, TextSetting
|
|
68
69
|
|
|
69
70
|
from manim import config, logger
|
|
@@ -71,15 +72,22 @@ from manim.constants import *
|
|
|
71
72
|
from manim.mobject.geometry.arc import Dot
|
|
72
73
|
from manim.mobject.svg.svg_mobject import SVGMobject
|
|
73
74
|
from manim.mobject.types.vectorized_mobject import VGroup, VMobject
|
|
74
|
-
from manim.
|
|
75
|
-
from manim.utils.
|
|
75
|
+
from manim.typing import Point3D
|
|
76
|
+
from manim.utils.color import ManimColor, ParsableManimColor, color_gradient
|
|
77
|
+
|
|
78
|
+
if TYPE_CHECKING:
|
|
79
|
+
from typing_extensions import Self
|
|
80
|
+
|
|
81
|
+
from manim.typing import Point3D
|
|
76
82
|
|
|
77
83
|
TEXT_MOB_SCALE_FACTOR = 0.05
|
|
78
84
|
DEFAULT_LINE_SPACING_SCALE = 0.3
|
|
79
85
|
TEXT2SVG_ADJUSTMENT_FACTOR = 4.8
|
|
80
86
|
|
|
87
|
+
__all__ = ["Text", "Paragraph", "MarkupText", "register_font"]
|
|
88
|
+
|
|
81
89
|
|
|
82
|
-
def remove_invisible_chars(mobject:
|
|
90
|
+
def remove_invisible_chars(mobject: VMobject) -> VMobject:
|
|
83
91
|
"""Function to remove unwanted invisible characters from some mobjects.
|
|
84
92
|
|
|
85
93
|
Parameters
|
|
@@ -92,24 +100,14 @@ def remove_invisible_chars(mobject: SVGMobject) -> SVGMobject:
|
|
|
92
100
|
:class:`~.SVGMobject`
|
|
93
101
|
The SVGMobject without unwanted invisible characters.
|
|
94
102
|
"""
|
|
95
|
-
|
|
96
|
-
iscode = False
|
|
97
|
-
if mobject.__class__.__name__ == "Text":
|
|
98
|
-
mobject = mobject[:]
|
|
99
|
-
elif mobject.__class__.__name__ == "Code":
|
|
100
|
-
iscode = True
|
|
101
|
-
code = mobject
|
|
102
|
-
mobject = mobject.code
|
|
103
103
|
mobject_without_dots = VGroup()
|
|
104
|
-
if mobject[0]
|
|
105
|
-
for
|
|
106
|
-
mobject_without_dots.add(
|
|
107
|
-
|
|
104
|
+
if isinstance(mobject[0], VGroup):
|
|
105
|
+
for submob in mobject:
|
|
106
|
+
mobject_without_dots.add(
|
|
107
|
+
VGroup(k for k in submob if not isinstance(k, Dot))
|
|
108
|
+
)
|
|
108
109
|
else:
|
|
109
|
-
mobject_without_dots.add(*(k for k in mobject if k
|
|
110
|
-
if iscode:
|
|
111
|
-
code.code = mobject_without_dots
|
|
112
|
-
return code
|
|
110
|
+
mobject_without_dots.add(*(k for k in mobject if not isinstance(k, Dot)))
|
|
113
111
|
return mobject_without_dots
|
|
114
112
|
|
|
115
113
|
|
|
@@ -132,10 +130,17 @@ class Paragraph(VGroup):
|
|
|
132
130
|
--------
|
|
133
131
|
Normal usage::
|
|
134
132
|
|
|
135
|
-
paragraph = Paragraph(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
paragraph = Paragraph(
|
|
134
|
+
"this is a awesome",
|
|
135
|
+
"paragraph",
|
|
136
|
+
"With \nNewlines",
|
|
137
|
+
"\tWith Tabs",
|
|
138
|
+
" With Spaces",
|
|
139
|
+
"With Alignments",
|
|
140
|
+
"center",
|
|
141
|
+
"left",
|
|
142
|
+
"right",
|
|
143
|
+
)
|
|
139
144
|
|
|
140
145
|
Remove unwanted invisible characters::
|
|
141
146
|
|
|
@@ -146,11 +151,11 @@ class Paragraph(VGroup):
|
|
|
146
151
|
|
|
147
152
|
def __init__(
|
|
148
153
|
self,
|
|
149
|
-
*text:
|
|
154
|
+
*text: str,
|
|
150
155
|
line_spacing: float = -1,
|
|
151
|
-
alignment:
|
|
152
|
-
**kwargs,
|
|
153
|
-
)
|
|
156
|
+
alignment: str | None = None,
|
|
157
|
+
**kwargs: Any,
|
|
158
|
+
):
|
|
154
159
|
self.line_spacing = line_spacing
|
|
155
160
|
self.alignment = alignment
|
|
156
161
|
self.consider_spaces_as_chars = kwargs.get("disable_ligatures", False)
|
|
@@ -292,7 +297,7 @@ class Paragraph(VGroup):
|
|
|
292
297
|
|
|
293
298
|
|
|
294
299
|
class Text(SVGMobject):
|
|
295
|
-
r"""Display (non-LaTeX) text rendered using `Pango <https://pango.
|
|
300
|
+
r"""Display (non-LaTeX) text rendered using `Pango <https://pango.org/>`_.
|
|
296
301
|
|
|
297
302
|
Text objects behave like a :class:`.VGroup`-like iterable of all characters
|
|
298
303
|
in the given text. In particular, slicing is possible.
|
|
@@ -301,6 +306,13 @@ class Text(SVGMobject):
|
|
|
301
306
|
----------
|
|
302
307
|
text
|
|
303
308
|
The text that needs to be created as a mobject.
|
|
309
|
+
font
|
|
310
|
+
The font family to be used to render the text. This is either a system font or
|
|
311
|
+
one loaded with `register_font()`. Note that font family names may be different
|
|
312
|
+
across operating systems.
|
|
313
|
+
warn_missing_font
|
|
314
|
+
If True (default), Manim will issue a warning if the font does not exist in the
|
|
315
|
+
(case-sensitive) list of fonts returned from `manimpango.list_fonts()`.
|
|
304
316
|
|
|
305
317
|
Returns
|
|
306
318
|
-------
|
|
@@ -344,7 +356,7 @@ class Text(SVGMobject):
|
|
|
344
356
|
)
|
|
345
357
|
text6.scale(1.3).shift(DOWN)
|
|
346
358
|
self.add(text1, text2, text3, text4, text5 , text6)
|
|
347
|
-
Group(*self.mobjects).arrange(DOWN, buff=.8).
|
|
359
|
+
Group(*self.mobjects).arrange(DOWN, buff=.8).set(height=config.frame_height-LARGE_BUFF)
|
|
348
360
|
|
|
349
361
|
.. manim:: TextMoreCustomization
|
|
350
362
|
:save_last_frame:
|
|
@@ -401,33 +413,55 @@ class Text(SVGMobject):
|
|
|
401
413
|
|
|
402
414
|
"""
|
|
403
415
|
|
|
416
|
+
@staticmethod
|
|
417
|
+
@functools.cache
|
|
418
|
+
def font_list() -> list[str]:
|
|
419
|
+
value: list[str] = manimpango.list_fonts()
|
|
420
|
+
return value
|
|
421
|
+
|
|
404
422
|
def __init__(
|
|
405
423
|
self,
|
|
406
424
|
text: str,
|
|
407
425
|
fill_opacity: float = 1.0,
|
|
408
426
|
stroke_width: float = 0,
|
|
409
|
-
color:
|
|
427
|
+
color: ParsableManimColor | None = None,
|
|
410
428
|
font_size: float = DEFAULT_FONT_SIZE,
|
|
411
429
|
line_spacing: float = -1,
|
|
412
430
|
font: str = "",
|
|
413
431
|
slant: str = NORMAL,
|
|
414
432
|
weight: str = NORMAL,
|
|
415
|
-
t2c: dict[str, str] = None,
|
|
416
|
-
t2f: dict[str, str] = None,
|
|
417
|
-
t2g: dict[str,
|
|
418
|
-
t2s: dict[str, str] = None,
|
|
419
|
-
t2w: dict[str, str] = None,
|
|
420
|
-
gradient:
|
|
433
|
+
t2c: dict[str, str] | None = None,
|
|
434
|
+
t2f: dict[str, str] | None = None,
|
|
435
|
+
t2g: dict[str, Iterable[ParsableManimColor]] | None = None,
|
|
436
|
+
t2s: dict[str, str] | None = None,
|
|
437
|
+
t2w: dict[str, str] | None = None,
|
|
438
|
+
gradient: Iterable[ParsableManimColor] | None = None,
|
|
421
439
|
tab_width: int = 4,
|
|
440
|
+
warn_missing_font: bool = True,
|
|
422
441
|
# Mobject
|
|
423
|
-
height: float = None,
|
|
424
|
-
width: float = None,
|
|
442
|
+
height: float | None = None,
|
|
443
|
+
width: float | None = None,
|
|
425
444
|
should_center: bool = True,
|
|
426
445
|
disable_ligatures: bool = False,
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
446
|
+
use_svg_cache: bool = False,
|
|
447
|
+
**kwargs: Any,
|
|
448
|
+
):
|
|
430
449
|
self.line_spacing = line_spacing
|
|
450
|
+
if font and warn_missing_font:
|
|
451
|
+
fonts_list = Text.font_list()
|
|
452
|
+
# handle special case of sans/sans-serif
|
|
453
|
+
if font.lower() == "sans-serif":
|
|
454
|
+
font = "sans"
|
|
455
|
+
if font not in fonts_list:
|
|
456
|
+
# check if the capitalized version is in the supported fonts
|
|
457
|
+
if font.capitalize() in fonts_list:
|
|
458
|
+
font = font.capitalize()
|
|
459
|
+
elif font.lower() in fonts_list:
|
|
460
|
+
font = font.lower()
|
|
461
|
+
elif font.title() in fonts_list:
|
|
462
|
+
font = font.title()
|
|
463
|
+
else:
|
|
464
|
+
logger.warning(f"Font {font} not in {fonts_list}.")
|
|
431
465
|
self.font = font
|
|
432
466
|
self._font_size = float(font_size)
|
|
433
467
|
# needs to be a float or else size is inflated when font_size = 24
|
|
@@ -452,11 +486,16 @@ class Text(SVGMobject):
|
|
|
452
486
|
t2g = kwargs.pop("text2gradient", t2g)
|
|
453
487
|
t2s = kwargs.pop("text2slant", t2s)
|
|
454
488
|
t2w = kwargs.pop("text2weight", t2w)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
489
|
+
assert t2c is not None
|
|
490
|
+
assert t2f is not None
|
|
491
|
+
assert t2g is not None
|
|
492
|
+
assert t2s is not None
|
|
493
|
+
assert t2w is not None
|
|
494
|
+
self.t2c: dict[str, str] = {k: ManimColor(v).to_hex() for k, v in t2c.items()}
|
|
495
|
+
self.t2f: dict[str, str] = t2f
|
|
496
|
+
self.t2g: dict[str, Iterable[ParsableManimColor]] = t2g
|
|
497
|
+
self.t2s: dict[str, str] = t2s
|
|
498
|
+
self.t2w: dict[str, str] = t2w
|
|
460
499
|
|
|
461
500
|
self.original_text = text
|
|
462
501
|
self.disable_ligatures = disable_ligatures
|
|
@@ -471,8 +510,8 @@ class Text(SVGMobject):
|
|
|
471
510
|
else:
|
|
472
511
|
self.line_spacing = self._font_size + self._font_size * self.line_spacing
|
|
473
512
|
|
|
474
|
-
|
|
475
|
-
file_name = self._text2svg(
|
|
513
|
+
parsed_color: ManimColor = ManimColor(color) if color else VMobject().color
|
|
514
|
+
file_name = self._text2svg(parsed_color.to_hex())
|
|
476
515
|
PangoUtils.remove_last_M(file_name)
|
|
477
516
|
super().__init__(
|
|
478
517
|
file_name,
|
|
@@ -481,6 +520,7 @@ class Text(SVGMobject):
|
|
|
481
520
|
height=height,
|
|
482
521
|
width=width,
|
|
483
522
|
should_center=should_center,
|
|
523
|
+
use_svg_cache=use_svg_cache,
|
|
484
524
|
**kwargs,
|
|
485
525
|
)
|
|
486
526
|
self.text = text
|
|
@@ -493,28 +533,68 @@ class Text(SVGMobject):
|
|
|
493
533
|
if len(each.points) == 0:
|
|
494
534
|
continue
|
|
495
535
|
points = each.points
|
|
496
|
-
|
|
497
|
-
|
|
536
|
+
curve_start = points[0]
|
|
537
|
+
assert len(curve_start) == self.dim, curve_start
|
|
538
|
+
# Some of the glyphs in this text might not be closed,
|
|
539
|
+
# so we close them by identifying when one curve ends
|
|
540
|
+
# but it is not where the next curve starts.
|
|
541
|
+
# It is more efficient to temporarily create a list
|
|
542
|
+
# of points and add them one at a time, then turn them
|
|
543
|
+
# into a numpy array at the end, rather than creating
|
|
544
|
+
# new numpy arrays every time a point or fixing line
|
|
545
|
+
# is added (which is O(n^2) for numpy arrays).
|
|
546
|
+
closed_curve_points: list[Point3D] = []
|
|
547
|
+
# OpenGL has points be part of quadratic Bezier curves;
|
|
548
|
+
# Cairo uses cubic Bezier curves.
|
|
549
|
+
if nppc == 3: # RendererType.OPENGL
|
|
550
|
+
|
|
551
|
+
def add_line_to(end: Point3D) -> None:
|
|
552
|
+
nonlocal closed_curve_points
|
|
553
|
+
start = closed_curve_points[-1]
|
|
554
|
+
closed_curve_points += [
|
|
555
|
+
start,
|
|
556
|
+
(start + end) / 2,
|
|
557
|
+
end,
|
|
558
|
+
]
|
|
559
|
+
|
|
560
|
+
else: # RendererType.CAIRO
|
|
561
|
+
|
|
562
|
+
def add_line_to(end: Point3D) -> None:
|
|
563
|
+
nonlocal closed_curve_points
|
|
564
|
+
start = closed_curve_points[-1]
|
|
565
|
+
closed_curve_points += [
|
|
566
|
+
start,
|
|
567
|
+
(start + start + end) / 3,
|
|
568
|
+
(start + end + end) / 3,
|
|
569
|
+
end,
|
|
570
|
+
]
|
|
571
|
+
|
|
498
572
|
for index, point in enumerate(points):
|
|
499
|
-
|
|
573
|
+
closed_curve_points.append(point)
|
|
500
574
|
if (
|
|
501
575
|
index != len(points) - 1
|
|
502
576
|
and (index + 1) % nppc == 0
|
|
503
577
|
and any(point != points[index + 1])
|
|
504
578
|
):
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
579
|
+
# Add straight line from last point on this curve to the
|
|
580
|
+
# start point on the next curve. We represent the line
|
|
581
|
+
# as a cubic bezier curve where the two control points
|
|
582
|
+
# are half-way between the start and stop point.
|
|
583
|
+
add_line_to(curve_start)
|
|
584
|
+
curve_start = points[index + 1]
|
|
585
|
+
# Make sure last curve is closed
|
|
586
|
+
add_line_to(curve_start)
|
|
587
|
+
each.points = np.array(closed_curve_points, ndmin=2)
|
|
508
588
|
# anti-aliasing
|
|
509
589
|
if height is None and width is None:
|
|
510
590
|
self.scale(TEXT_MOB_SCALE_FACTOR)
|
|
511
591
|
self.initial_height = self.height
|
|
512
592
|
|
|
513
|
-
def __repr__(self):
|
|
593
|
+
def __repr__(self) -> str:
|
|
514
594
|
return f"Text({repr(self.original_text)})"
|
|
515
595
|
|
|
516
596
|
@property
|
|
517
|
-
def font_size(self):
|
|
597
|
+
def font_size(self) -> float:
|
|
518
598
|
return (
|
|
519
599
|
self.height
|
|
520
600
|
/ self.initial_height
|
|
@@ -525,14 +605,14 @@ class Text(SVGMobject):
|
|
|
525
605
|
)
|
|
526
606
|
|
|
527
607
|
@font_size.setter
|
|
528
|
-
def font_size(self, font_val):
|
|
608
|
+
def font_size(self, font_val: float) -> None:
|
|
529
609
|
# TODO: use pango's font size scaling.
|
|
530
610
|
if font_val <= 0:
|
|
531
611
|
raise ValueError("font_size must be greater than 0.")
|
|
532
612
|
else:
|
|
533
613
|
self.scale(font_val / self.font_size)
|
|
534
614
|
|
|
535
|
-
def _gen_chars(self):
|
|
615
|
+
def _gen_chars(self) -> VGroup:
|
|
536
616
|
chars = self.get_group_class()()
|
|
537
617
|
submobjects_char_index = 0
|
|
538
618
|
for char_index in range(len(self.text)):
|
|
@@ -550,7 +630,7 @@ class Text(SVGMobject):
|
|
|
550
630
|
submobjects_char_index += 1
|
|
551
631
|
return chars
|
|
552
632
|
|
|
553
|
-
def _find_indexes(self, word: str, text: str):
|
|
633
|
+
def _find_indexes(self, word: str, text: str) -> list[tuple[int, int]]:
|
|
554
634
|
"""Finds the indexes of ``text`` in ``word``."""
|
|
555
635
|
temp = re.match(r"\[([0-9\-]{0,}):([0-9\-]{0,})\]", word)
|
|
556
636
|
if temp:
|
|
@@ -558,7 +638,9 @@ class Text(SVGMobject):
|
|
|
558
638
|
end = int(temp.group(2)) if temp.group(2) != "" else len(text)
|
|
559
639
|
start = len(text) + start if start < 0 else start
|
|
560
640
|
end = len(text) + end if end < 0 else end
|
|
561
|
-
return [
|
|
641
|
+
return [
|
|
642
|
+
(start, end),
|
|
643
|
+
]
|
|
562
644
|
indexes = []
|
|
563
645
|
index = text.find(word)
|
|
564
646
|
while index != -1:
|
|
@@ -566,39 +648,15 @@ class Text(SVGMobject):
|
|
|
566
648
|
index = text.find(word, index + len(word))
|
|
567
649
|
return indexes
|
|
568
650
|
|
|
569
|
-
|
|
570
|
-
since="v0.14.0",
|
|
571
|
-
until="v0.15.0",
|
|
572
|
-
message="This was internal function, you shouldn't be using it anyway.",
|
|
573
|
-
)
|
|
574
|
-
def _set_color_by_t2c(self, t2c=None):
|
|
575
|
-
"""Sets color for specified strings."""
|
|
576
|
-
t2c = t2c if t2c else self.t2c
|
|
577
|
-
for word, color in list(t2c.items()):
|
|
578
|
-
for start, end in self._find_indexes(word, self.text):
|
|
579
|
-
self.chars[start:end].set_color(color)
|
|
580
|
-
|
|
581
|
-
@deprecated(
|
|
582
|
-
since="v0.14.0",
|
|
583
|
-
until="v0.15.0",
|
|
584
|
-
message="This was internal function, you shouldn't be using it anyway.",
|
|
585
|
-
)
|
|
586
|
-
def _set_color_by_t2g(self, t2g=None):
|
|
587
|
-
"""Sets gradient colors for specified
|
|
588
|
-
strings. Behaves similarly to ``set_color_by_t2c``."""
|
|
589
|
-
t2g = t2g if t2g else self.t2g
|
|
590
|
-
for word, gradient in list(t2g.items()):
|
|
591
|
-
for start, end in self._find_indexes(word, self.text):
|
|
592
|
-
self.chars[start:end].set_color_by_gradient(*gradient)
|
|
593
|
-
|
|
594
|
-
def _text2hash(self, color: Color):
|
|
651
|
+
def _text2hash(self, color: ParsableManimColor) -> str:
|
|
595
652
|
"""Generates ``sha256`` hash for file name."""
|
|
596
653
|
settings = (
|
|
597
|
-
"PANGO" + self.font + self.slant + self.weight + color
|
|
654
|
+
"PANGO" + self.font + self.slant + self.weight + str(color)
|
|
598
655
|
) # to differentiate Text and CairoText
|
|
599
656
|
settings += str(self.t2f) + str(self.t2s) + str(self.t2w) + str(self.t2c)
|
|
600
657
|
settings += str(self.line_spacing) + str(self._font_size)
|
|
601
658
|
settings += str(self.disable_ligatures)
|
|
659
|
+
settings += str(self.gradient)
|
|
602
660
|
id_str = self.text + settings
|
|
603
661
|
hasher = hashlib.sha256()
|
|
604
662
|
hasher.update(id_str.encode())
|
|
@@ -624,7 +682,7 @@ class Text(SVGMobject):
|
|
|
624
682
|
default = default_args[arg]
|
|
625
683
|
if left != default and getattr(right_setting, arg) != default:
|
|
626
684
|
raise ValueError(
|
|
627
|
-
f"Ambiguous style for text '{self.text[right_setting.start:right_setting.end]}':"
|
|
685
|
+
f"Ambiguous style for text '{self.text[right_setting.start : right_setting.end]}':"
|
|
628
686
|
+ f"'{arg}' cannot be both '{left}' and '{right}'."
|
|
629
687
|
)
|
|
630
688
|
setattr(right_setting, arg, left if left != default else right)
|
|
@@ -634,12 +692,14 @@ class Text(SVGMobject):
|
|
|
634
692
|
self,
|
|
635
693
|
t2xs: Sequence[tuple[dict[str, str], str]],
|
|
636
694
|
default_args: dict[str, Iterable[str]],
|
|
637
|
-
) ->
|
|
695
|
+
) -> list[TextSetting]:
|
|
638
696
|
settings = []
|
|
639
697
|
t2xwords = set(chain(*([*t2x.keys()] for t2x, _ in t2xs)))
|
|
640
698
|
for word in t2xwords:
|
|
641
699
|
setting_args = {
|
|
642
|
-
arg: t2x[word] if word in t2x else default_args[arg]
|
|
700
|
+
arg: str(t2x[word]) if word in t2x else default_args[arg]
|
|
701
|
+
# NOTE: when t2x[word] is a ManimColor, str will yield the
|
|
702
|
+
# hex representation
|
|
643
703
|
for t2x, arg in t2xs
|
|
644
704
|
}
|
|
645
705
|
|
|
@@ -648,42 +708,36 @@ class Text(SVGMobject):
|
|
|
648
708
|
return settings
|
|
649
709
|
|
|
650
710
|
def _get_settings_from_gradient(
|
|
651
|
-
self, default_args: dict[str,
|
|
652
|
-
) ->
|
|
711
|
+
self, default_args: dict[str, Any]
|
|
712
|
+
) -> list[TextSetting]:
|
|
653
713
|
settings = []
|
|
654
714
|
args = copy.copy(default_args)
|
|
655
715
|
if self.gradient:
|
|
656
|
-
colors = color_gradient(self.gradient, len(self.text))
|
|
716
|
+
colors: list[ManimColor] = color_gradient(self.gradient, len(self.text))
|
|
657
717
|
for i in range(len(self.text)):
|
|
658
|
-
args["color"] = colors[i].
|
|
718
|
+
args["color"] = colors[i].to_hex()
|
|
659
719
|
settings.append(TextSetting(i, i + 1, **args))
|
|
660
720
|
|
|
661
721
|
for word, gradient in self.t2g.items():
|
|
662
|
-
|
|
663
|
-
color = gradient if isinstance(gradient, str) else gradient[0]
|
|
664
|
-
gradient = [Color(color)]
|
|
665
|
-
colors = (
|
|
666
|
-
color_gradient(gradient, len(word))
|
|
667
|
-
if len(gradient) != 1
|
|
668
|
-
else len(word) * gradient
|
|
669
|
-
)
|
|
722
|
+
colors = color_gradient(gradient, len(word))
|
|
670
723
|
for start, end in self._find_indexes(word, self.text):
|
|
671
724
|
for i in range(start, end):
|
|
672
|
-
args["color"] = colors[i - start].
|
|
725
|
+
args["color"] = colors[i - start].to_hex()
|
|
673
726
|
settings.append(TextSetting(i, i + 1, **args))
|
|
674
727
|
return settings
|
|
675
728
|
|
|
676
|
-
def _text2settings(self, color:
|
|
729
|
+
def _text2settings(self, color: ParsableManimColor) -> list[TextSetting]:
|
|
677
730
|
"""Converts the texts and styles to a setting for parsing."""
|
|
678
|
-
t2xs = [
|
|
731
|
+
t2xs: list[tuple[dict[str, str], str]] = [
|
|
679
732
|
(self.t2f, "font"),
|
|
680
733
|
(self.t2s, "slant"),
|
|
681
734
|
(self.t2w, "weight"),
|
|
682
735
|
(self.t2c, "color"),
|
|
683
736
|
]
|
|
684
737
|
# setting_args requires values to be strings
|
|
685
|
-
|
|
686
|
-
|
|
738
|
+
|
|
739
|
+
default_args: dict[str, Any] = {
|
|
740
|
+
arg: getattr(self, arg) if arg != "color" else color for _, arg in t2xs
|
|
687
741
|
}
|
|
688
742
|
|
|
689
743
|
settings = self._get_settings_from_t2xs(t2xs, default_args)
|
|
@@ -720,15 +774,15 @@ class Text(SVGMobject):
|
|
|
720
774
|
|
|
721
775
|
line_num = 0
|
|
722
776
|
if re.search(r"\n", self.text):
|
|
723
|
-
for
|
|
777
|
+
for for_start, for_end in self._find_indexes("\n", self.text):
|
|
724
778
|
for setting in settings:
|
|
725
779
|
if setting.line_num == -1:
|
|
726
780
|
setting.line_num = line_num
|
|
727
|
-
if
|
|
781
|
+
if for_start < setting.end:
|
|
728
782
|
line_num += 1
|
|
729
783
|
new_setting = copy.copy(setting)
|
|
730
|
-
setting.end =
|
|
731
|
-
new_setting.start =
|
|
784
|
+
setting.end = for_end
|
|
785
|
+
new_setting.start = for_end
|
|
732
786
|
new_setting.line_num = line_num
|
|
733
787
|
settings.append(new_setting)
|
|
734
788
|
settings.sort(key=lambda setting: setting.start)
|
|
@@ -739,7 +793,7 @@ class Text(SVGMobject):
|
|
|
739
793
|
|
|
740
794
|
return settings
|
|
741
795
|
|
|
742
|
-
def _text2svg(self, color:
|
|
796
|
+
def _text2svg(self, color: ParsableManimColor) -> str:
|
|
743
797
|
"""Convert the text to SVG using Pango."""
|
|
744
798
|
size = self._font_size
|
|
745
799
|
line_spacing = self.line_spacing
|
|
@@ -774,15 +828,16 @@ class Text(SVGMobject):
|
|
|
774
828
|
|
|
775
829
|
return svg_file
|
|
776
830
|
|
|
777
|
-
def init_colors(self, propagate_colors=True):
|
|
831
|
+
def init_colors(self, propagate_colors: bool = True) -> Self:
|
|
778
832
|
if config.renderer == RendererType.OPENGL:
|
|
779
833
|
super().init_colors()
|
|
780
834
|
elif config.renderer == RendererType.CAIRO:
|
|
781
835
|
super().init_colors(propagate_colors=propagate_colors)
|
|
836
|
+
return self
|
|
782
837
|
|
|
783
838
|
|
|
784
839
|
class MarkupText(SVGMobject):
|
|
785
|
-
r"""Display (non-LaTeX) text rendered using `Pango <https://pango.
|
|
840
|
+
r"""Display (non-LaTeX) text rendered using `Pango <https://pango.org/>`_.
|
|
786
841
|
|
|
787
842
|
Text objects behave like a :class:`.VGroup`-like iterable of all characters
|
|
788
843
|
in the given text. In particular, slicing is possible.
|
|
@@ -832,7 +887,7 @@ class MarkupText(SVGMobject):
|
|
|
832
887
|
Here is a list of supported tags:
|
|
833
888
|
|
|
834
889
|
- ``<b>bold</b>``, ``<i>italic</i>`` and ``<b><i>bold+italic</i></b>``
|
|
835
|
-
- ``<
|
|
890
|
+
- ``<u>underline</u>`` and ``<s>strike through</s>``
|
|
836
891
|
- ``<tt>typewriter font</tt>``
|
|
837
892
|
- ``<big>bigger font</big>`` and ``<small>smaller font</small>``
|
|
838
893
|
- ``<sup>superscript</sup>`` and ``<sub>subscript</sub>``
|
|
@@ -911,7 +966,9 @@ class MarkupText(SVGMobject):
|
|
|
911
966
|
Global weight setting, e.g. `NORMAL` or `BOLD`. Local overrides are possible.
|
|
912
967
|
gradient
|
|
913
968
|
Global gradient setting. Local overrides are possible.
|
|
914
|
-
|
|
969
|
+
warn_missing_font
|
|
970
|
+
If True (default), Manim will issue a warning if the font does not exist in the
|
|
971
|
+
(case-sensitive) list of fonts returned from `manimpango.list_fonts()`.
|
|
915
972
|
|
|
916
973
|
Returns
|
|
917
974
|
-------
|
|
@@ -1077,29 +1134,50 @@ class MarkupText(SVGMobject):
|
|
|
1077
1134
|
|
|
1078
1135
|
"""
|
|
1079
1136
|
|
|
1137
|
+
@staticmethod
|
|
1138
|
+
@functools.cache
|
|
1139
|
+
def font_list() -> list[str]:
|
|
1140
|
+
value: list[str] = manimpango.list_fonts()
|
|
1141
|
+
return value
|
|
1142
|
+
|
|
1080
1143
|
def __init__(
|
|
1081
1144
|
self,
|
|
1082
1145
|
text: str,
|
|
1083
1146
|
fill_opacity: float = 1,
|
|
1084
1147
|
stroke_width: float = 0,
|
|
1085
|
-
color:
|
|
1148
|
+
color: ParsableManimColor | None = None,
|
|
1086
1149
|
font_size: float = DEFAULT_FONT_SIZE,
|
|
1087
|
-
line_spacing:
|
|
1150
|
+
line_spacing: float = -1,
|
|
1088
1151
|
font: str = "",
|
|
1089
1152
|
slant: str = NORMAL,
|
|
1090
1153
|
weight: str = NORMAL,
|
|
1091
1154
|
justify: bool = False,
|
|
1092
|
-
gradient:
|
|
1155
|
+
gradient: Iterable[ParsableManimColor] | None = None,
|
|
1093
1156
|
tab_width: int = 4,
|
|
1094
|
-
height: int = None,
|
|
1095
|
-
width: int = None,
|
|
1157
|
+
height: int | None = None,
|
|
1158
|
+
width: int | None = None,
|
|
1096
1159
|
should_center: bool = True,
|
|
1097
1160
|
disable_ligatures: bool = False,
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1161
|
+
warn_missing_font: bool = True,
|
|
1162
|
+
**kwargs: Any,
|
|
1163
|
+
):
|
|
1101
1164
|
self.text = text
|
|
1102
|
-
self.line_spacing = line_spacing
|
|
1165
|
+
self.line_spacing: float = line_spacing
|
|
1166
|
+
if font and warn_missing_font:
|
|
1167
|
+
fonts_list = Text.font_list()
|
|
1168
|
+
# handle special case of sans/sans-serif
|
|
1169
|
+
if font.lower() == "sans-serif":
|
|
1170
|
+
font = "sans"
|
|
1171
|
+
if font not in fonts_list:
|
|
1172
|
+
# check if the capitalized version is in the supported fonts
|
|
1173
|
+
if font.capitalize() in fonts_list:
|
|
1174
|
+
font = font.capitalize()
|
|
1175
|
+
elif font.lower() in fonts_list:
|
|
1176
|
+
font = font.lower()
|
|
1177
|
+
elif font.title() in fonts_list:
|
|
1178
|
+
font = font.title()
|
|
1179
|
+
else:
|
|
1180
|
+
logger.warning(f"Font {font} not in {fonts_list}.")
|
|
1103
1181
|
self.font = font
|
|
1104
1182
|
self._font_size = float(font_size)
|
|
1105
1183
|
self.slant = slant
|
|
@@ -1131,8 +1209,8 @@ class MarkupText(SVGMobject):
|
|
|
1131
1209
|
else:
|
|
1132
1210
|
self.line_spacing = self._font_size + self._font_size * self.line_spacing
|
|
1133
1211
|
|
|
1134
|
-
|
|
1135
|
-
file_name = self._text2svg(
|
|
1212
|
+
parsed_color: ManimColor = ManimColor(color) if color else VMobject().color
|
|
1213
|
+
file_name = self._text2svg(parsed_color)
|
|
1136
1214
|
|
|
1137
1215
|
PangoUtils.remove_last_M(file_name)
|
|
1138
1216
|
super().__init__(
|
|
@@ -1153,32 +1231,68 @@ class MarkupText(SVGMobject):
|
|
|
1153
1231
|
if len(each.points) == 0:
|
|
1154
1232
|
continue
|
|
1155
1233
|
points = each.points
|
|
1156
|
-
|
|
1157
|
-
|
|
1234
|
+
curve_start = points[0]
|
|
1235
|
+
assert len(curve_start) == self.dim, curve_start
|
|
1236
|
+
# Some of the glyphs in this text might not be closed,
|
|
1237
|
+
# so we close them by identifying when one curve ends
|
|
1238
|
+
# but it is not where the next curve starts.
|
|
1239
|
+
# It is more efficient to temporarily create a list
|
|
1240
|
+
# of points and add them one at a time, then turn them
|
|
1241
|
+
# into a numpy array at the end, rather than creating
|
|
1242
|
+
# new numpy arrays every time a point or fixing line
|
|
1243
|
+
# is added (which is O(n^2) for numpy arrays).
|
|
1244
|
+
closed_curve_points: list[Point3D] = []
|
|
1245
|
+
# OpenGL has points be part of quadratic Bezier curves;
|
|
1246
|
+
# Cairo uses cubic Bezier curves.
|
|
1247
|
+
if nppc == 3: # RendererType.OPENGL
|
|
1248
|
+
|
|
1249
|
+
def add_line_to(end: Point3D) -> None:
|
|
1250
|
+
nonlocal closed_curve_points
|
|
1251
|
+
start = closed_curve_points[-1]
|
|
1252
|
+
closed_curve_points += [
|
|
1253
|
+
start,
|
|
1254
|
+
(start + end) / 2,
|
|
1255
|
+
end,
|
|
1256
|
+
]
|
|
1257
|
+
|
|
1258
|
+
else: # RendererType.CAIRO
|
|
1259
|
+
|
|
1260
|
+
def add_line_to(end: Point3D) -> None:
|
|
1261
|
+
nonlocal closed_curve_points
|
|
1262
|
+
start = closed_curve_points[-1]
|
|
1263
|
+
closed_curve_points += [
|
|
1264
|
+
start,
|
|
1265
|
+
(start + start + end) / 3,
|
|
1266
|
+
(start + end + end) / 3,
|
|
1267
|
+
end,
|
|
1268
|
+
]
|
|
1269
|
+
|
|
1158
1270
|
for index, point in enumerate(points):
|
|
1159
|
-
|
|
1271
|
+
closed_curve_points.append(point)
|
|
1160
1272
|
if (
|
|
1161
1273
|
index != len(points) - 1
|
|
1162
1274
|
and (index + 1) % nppc == 0
|
|
1163
1275
|
and any(point != points[index + 1])
|
|
1164
1276
|
):
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1277
|
+
# Add straight line from last point on this curve to the
|
|
1278
|
+
# start point on the next curve.
|
|
1279
|
+
add_line_to(curve_start)
|
|
1280
|
+
curve_start = points[index + 1]
|
|
1281
|
+
# Make sure last curve is closed
|
|
1282
|
+
add_line_to(curve_start)
|
|
1283
|
+
each.points = np.array(closed_curve_points, ndmin=2)
|
|
1168
1284
|
|
|
1169
1285
|
if self.gradient:
|
|
1170
1286
|
self.set_color_by_gradient(*self.gradient)
|
|
1171
1287
|
for col in colormap:
|
|
1172
1288
|
self.chars[
|
|
1173
|
-
col["start"]
|
|
1174
|
-
- col["start_offset"] : col["end"]
|
|
1289
|
+
col["start"] - col["start_offset"] : col["end"]
|
|
1175
1290
|
- col["start_offset"]
|
|
1176
1291
|
- col["end_offset"]
|
|
1177
1292
|
].set_color(self._parse_color(col["color"]))
|
|
1178
1293
|
for grad in gradientmap:
|
|
1179
1294
|
self.chars[
|
|
1180
|
-
grad["start"]
|
|
1181
|
-
- grad["start_offset"] : grad["end"]
|
|
1295
|
+
grad["start"] - grad["start_offset"] : grad["end"]
|
|
1182
1296
|
- grad["start_offset"]
|
|
1183
1297
|
- grad["end_offset"]
|
|
1184
1298
|
].set_color_by_gradient(
|
|
@@ -1191,7 +1305,7 @@ class MarkupText(SVGMobject):
|
|
|
1191
1305
|
self.initial_height = self.height
|
|
1192
1306
|
|
|
1193
1307
|
@property
|
|
1194
|
-
def font_size(self):
|
|
1308
|
+
def font_size(self) -> float:
|
|
1195
1309
|
return (
|
|
1196
1310
|
self.height
|
|
1197
1311
|
/ self.initial_height
|
|
@@ -1202,17 +1316,21 @@ class MarkupText(SVGMobject):
|
|
|
1202
1316
|
)
|
|
1203
1317
|
|
|
1204
1318
|
@font_size.setter
|
|
1205
|
-
def font_size(self, font_val):
|
|
1319
|
+
def font_size(self, font_val: float) -> None:
|
|
1206
1320
|
# TODO: use pango's font size scaling.
|
|
1207
1321
|
if font_val <= 0:
|
|
1208
1322
|
raise ValueError("font_size must be greater than 0.")
|
|
1209
1323
|
else:
|
|
1210
1324
|
self.scale(font_val / self.font_size)
|
|
1211
1325
|
|
|
1212
|
-
def _text2hash(self, color:
|
|
1326
|
+
def _text2hash(self, color: ParsableManimColor) -> str:
|
|
1213
1327
|
"""Generates ``sha256`` hash for file name."""
|
|
1214
1328
|
settings = (
|
|
1215
|
-
"MARKUPPANGO"
|
|
1329
|
+
"MARKUPPANGO"
|
|
1330
|
+
+ self.font
|
|
1331
|
+
+ self.slant
|
|
1332
|
+
+ self.weight
|
|
1333
|
+
+ ManimColor(color).to_hex().lower()
|
|
1216
1334
|
) # to differentiate from classical Pango Text
|
|
1217
1335
|
settings += str(self.line_spacing) + str(self._font_size)
|
|
1218
1336
|
settings += str(self.disable_ligatures)
|
|
@@ -1222,23 +1340,25 @@ class MarkupText(SVGMobject):
|
|
|
1222
1340
|
hasher.update(id_str.encode())
|
|
1223
1341
|
return hasher.hexdigest()[:16]
|
|
1224
1342
|
|
|
1225
|
-
def _text2svg(self, color:
|
|
1343
|
+
def _text2svg(self, color: ParsableManimColor | None) -> str:
|
|
1226
1344
|
"""Convert the text to SVG using Pango."""
|
|
1345
|
+
color = ManimColor(color)
|
|
1227
1346
|
size = self._font_size
|
|
1228
|
-
line_spacing = self.line_spacing
|
|
1347
|
+
line_spacing: float = self.line_spacing
|
|
1229
1348
|
size /= TEXT2SVG_ADJUSTMENT_FACTOR
|
|
1230
1349
|
line_spacing /= TEXT2SVG_ADJUSTMENT_FACTOR
|
|
1231
1350
|
|
|
1232
1351
|
dir_name = config.get_dir("text_dir")
|
|
1233
|
-
if not dir_name.
|
|
1352
|
+
if not dir_name.is_dir():
|
|
1234
1353
|
dir_name.mkdir(parents=True)
|
|
1235
1354
|
hash_name = self._text2hash(color)
|
|
1236
1355
|
file_name = dir_name / (hash_name + ".svg")
|
|
1356
|
+
|
|
1237
1357
|
if file_name.exists():
|
|
1238
|
-
svg_file = str(file_name.resolve())
|
|
1358
|
+
svg_file: str = str(file_name.resolve())
|
|
1239
1359
|
else:
|
|
1240
1360
|
final_text = (
|
|
1241
|
-
f'<span foreground="{color}">{self.text}</span>'
|
|
1361
|
+
f'<span foreground="{color.to_hex()}">{self.text}</span>'
|
|
1242
1362
|
if color is not None
|
|
1243
1363
|
else self.text
|
|
1244
1364
|
)
|
|
@@ -1261,11 +1381,12 @@ class MarkupText(SVGMobject):
|
|
|
1261
1381
|
)
|
|
1262
1382
|
return svg_file
|
|
1263
1383
|
|
|
1264
|
-
def _count_real_chars(self, s):
|
|
1384
|
+
def _count_real_chars(self, s: str) -> int:
|
|
1265
1385
|
"""Counts characters that will be displayed.
|
|
1266
1386
|
|
|
1267
1387
|
This is needed for partial coloring or gradients, because space
|
|
1268
|
-
counts to the text's `len`, but has no corresponding character.
|
|
1388
|
+
counts to the text's `len`, but has no corresponding character.
|
|
1389
|
+
"""
|
|
1269
1390
|
count = 0
|
|
1270
1391
|
level = 0
|
|
1271
1392
|
# temporarily replace HTML entities by single char
|
|
@@ -1279,7 +1400,7 @@ class MarkupText(SVGMobject):
|
|
|
1279
1400
|
count += 1
|
|
1280
1401
|
return count
|
|
1281
1402
|
|
|
1282
|
-
def _extract_gradient_tags(self):
|
|
1403
|
+
def _extract_gradient_tags(self) -> list[dict[str, Any]]:
|
|
1283
1404
|
"""Used to determine which parts (if any) of the string should be formatted
|
|
1284
1405
|
with a gradient.
|
|
1285
1406
|
|
|
@@ -1290,7 +1411,7 @@ class MarkupText(SVGMobject):
|
|
|
1290
1411
|
self.original_text,
|
|
1291
1412
|
re.S,
|
|
1292
1413
|
)
|
|
1293
|
-
gradientmap = []
|
|
1414
|
+
gradientmap: list[dict[str, Any]] = []
|
|
1294
1415
|
for tag in tags:
|
|
1295
1416
|
start = self._count_real_chars(self.original_text[: tag.start(0)])
|
|
1296
1417
|
end = start + self._count_real_chars(tag.group(5))
|
|
@@ -1308,17 +1429,19 @@ class MarkupText(SVGMobject):
|
|
|
1308
1429
|
"end_offset": end_offset,
|
|
1309
1430
|
},
|
|
1310
1431
|
)
|
|
1311
|
-
self.text = re.sub(
|
|
1432
|
+
self.text = re.sub(
|
|
1433
|
+
"<gradient[^>]+>(.+?)</gradient>", r"\1", self.text, count=0, flags=re.S
|
|
1434
|
+
)
|
|
1312
1435
|
return gradientmap
|
|
1313
1436
|
|
|
1314
|
-
def _parse_color(self, col):
|
|
1437
|
+
def _parse_color(self, col: str) -> str:
|
|
1315
1438
|
"""Parse color given in ``<color>`` or ``<gradient>`` tags."""
|
|
1316
1439
|
if re.match("#[0-9a-f]{6}", col):
|
|
1317
1440
|
return col
|
|
1318
1441
|
else:
|
|
1319
|
-
return
|
|
1442
|
+
return ManimColor(col).to_hex()
|
|
1320
1443
|
|
|
1321
|
-
def _extract_color_tags(self):
|
|
1444
|
+
def _extract_color_tags(self) -> list[dict[str, Any]]:
|
|
1322
1445
|
"""Used to determine which parts (if any) of the string should be formatted
|
|
1323
1446
|
with a custom color.
|
|
1324
1447
|
|
|
@@ -1333,7 +1456,7 @@ class MarkupText(SVGMobject):
|
|
|
1333
1456
|
re.S,
|
|
1334
1457
|
)
|
|
1335
1458
|
|
|
1336
|
-
colormap = []
|
|
1459
|
+
colormap: list[dict[str, Any]] = []
|
|
1337
1460
|
for tag in tags:
|
|
1338
1461
|
start = self._count_real_chars(self.original_text[: tag.start(0)])
|
|
1339
1462
|
end = start + self._count_real_chars(tag.group(4))
|
|
@@ -1350,15 +1473,17 @@ class MarkupText(SVGMobject):
|
|
|
1350
1473
|
"end_offset": end_offset,
|
|
1351
1474
|
},
|
|
1352
1475
|
)
|
|
1353
|
-
self.text = re.sub(
|
|
1476
|
+
self.text = re.sub(
|
|
1477
|
+
"<color[^>]+>(.+?)</color>", r"\1", self.text, count=0, flags=re.S
|
|
1478
|
+
)
|
|
1354
1479
|
return colormap
|
|
1355
1480
|
|
|
1356
|
-
def __repr__(self):
|
|
1481
|
+
def __repr__(self) -> str:
|
|
1357
1482
|
return f"MarkupText({repr(self.original_text)})"
|
|
1358
1483
|
|
|
1359
1484
|
|
|
1360
1485
|
@contextmanager
|
|
1361
|
-
def register_font(font_file: str | Path):
|
|
1486
|
+
def register_font(font_file: str | Path) -> Iterator[None]:
|
|
1362
1487
|
"""Temporarily add a font file to Pango's search path.
|
|
1363
1488
|
|
|
1364
1489
|
This searches for the font_file at various places. The order it searches it described below.
|
|
@@ -1396,7 +1521,6 @@ def register_font(font_file: str | Path):
|
|
|
1396
1521
|
This method is available for macOS for ``ManimPango>=v0.2.3``. Using this
|
|
1397
1522
|
method with previous releases will raise an :class:`AttributeError` on macOS.
|
|
1398
1523
|
"""
|
|
1399
|
-
|
|
1400
1524
|
input_folder = Path(config.input_file).parent.resolve()
|
|
1401
1525
|
possible_paths = [
|
|
1402
1526
|
Path(font_file),
|
|
@@ -1411,7 +1535,7 @@ def register_font(font_file: str | Path):
|
|
|
1411
1535
|
logger.debug("Found file at %s", file_path.absolute())
|
|
1412
1536
|
break
|
|
1413
1537
|
else:
|
|
1414
|
-
error = f"Can't find {font_file}.
|
|
1538
|
+
error = f"Can't find {font_file}. Checked paths: {possible_paths}"
|
|
1415
1539
|
raise FileNotFoundError(error)
|
|
1416
1540
|
|
|
1417
1541
|
try:
|