mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
* fix: replace double-brace splitting regex with state-machine parser
The previous re.split(r'{{|}}', ...) call split on any occurrence of
{{ or }} in the input string, which broke strings whose only }} came
from closing two nested LaTeX brace groups (e.g. ^{\frac{Mq}{M+m}}).
The new _split_double_braces() static method uses a character-level
state machine with three guards:
* Escape priority: \\ is consumed before \{ / \}, so \\}} is
correctly read as an escaped backslash followed by a real }}, not
misinterpreted as \ + \} + lone }.
* Whitespace-gated opener: {{ is only treated as a Manim group opener
at the start of the string or after whitespace. Naturally-occurring
{{ in LaTeX is usually preceded by non-whitespace (e.g. \frac{{{n}}}
or a^{{2}}), so this eliminates the most common false positives.
* Depth-tracking closer: inside a Manim group, }} only closes the
group when the inner brace depth is zero, so {{ a^{b^{c}} }} is
handled correctly and nested LaTeX }} cannot trigger an early close.
Fixes #4601.
* docs: document double-brace whitespace requirement
Add a Notes section to the MathTex docstring explaining:
- how {{ }} splits a string into submobjects
- that {{ must be at start-of-string or preceded by whitespace
- that this leaves natural LaTeX like \frac{{{n}}}{k} untouched
- the { { ... } } escape hatch when a split is not wanted
Apply the same explanation to the double-brace paragraph in
docs/source/guides/using_text.rst.
* fix: use r-string for _split_double_braces docstring, halve backslash escaping
517 lines
16 KiB
ReStructuredText
517 lines
16 KiB
ReStructuredText
###########################
|
|
Rendering Text and Formulas
|
|
###########################
|
|
|
|
There are two different ways by which you can render **Text** in videos:
|
|
|
|
1. Using Pango (:mod:`~.text_mobject`)
|
|
2. Using LaTeX (:mod:`~.tex_mobject`)
|
|
|
|
If you want to render simple text, you should use either :class:`~.Text` or
|
|
:class:`~.MarkupText`, or one of its derivatives like :class:`~.Paragraph`.
|
|
See :ref:`using-text-objects` for more information.
|
|
|
|
LaTeX should be used when you need mathematical typesetting. See
|
|
:ref:`rendering-with-latex` for more information.
|
|
|
|
.. _using-text-objects:
|
|
|
|
Text Without LaTeX
|
|
******************
|
|
|
|
The simplest way to add text to your animations is to use the :class:`~.Text`
|
|
class. It uses the `Pango library`_ to render text. With Pango, you can also
|
|
render non-English alphabets like 你好 or こんにちは or 안녕하세요 or
|
|
مرحبا بالعالم.
|
|
|
|
Here is a simple *Hello World* animation.
|
|
|
|
.. manim:: HelloWorld
|
|
:save_last_frame:
|
|
:ref_classes: Text
|
|
|
|
class HelloWorld(Scene):
|
|
def construct(self):
|
|
text = Text("Hello world", font_size=144)
|
|
self.add(text)
|
|
|
|
You can also use :class:`~.MarkupText` which allows the use of PangoMarkup
|
|
(see the documentation of :class:`~.MarkupText` for details) to render text.
|
|
For example:
|
|
|
|
.. manim:: SingleLineColor
|
|
:save_last_frame:
|
|
:ref_classes: MarkupText
|
|
|
|
class SingleLineColor(Scene):
|
|
def construct(self):
|
|
text = MarkupText(
|
|
f'all in red <span fgcolor="{YELLOW}">except this</span>', color=RED
|
|
)
|
|
self.add(text)
|
|
|
|
.. _Pango library: https://pango.org
|
|
|
|
Working with :class:`~.Text`
|
|
============================
|
|
|
|
This section explains the properties of :class:`~.Text` and how can it be used
|
|
in your animations.
|
|
|
|
Using Fonts
|
|
-----------
|
|
|
|
You can set a different font using :attr:`~.Text.font`.
|
|
|
|
.. note::
|
|
|
|
The font used must be installed in your system, and Pango should know
|
|
about it. You can get a list of fonts using :func:`manimpango.list_fonts`.
|
|
|
|
>>> import manimpango
|
|
>>> manimpango.list_fonts()
|
|
[...]
|
|
|
|
|
|
.. manim:: FontsExample
|
|
:save_last_frame:
|
|
|
|
class FontsExample(Scene):
|
|
def construct(self):
|
|
ft = Text("Noto Sans", font="Noto Sans")
|
|
self.add(ft)
|
|
|
|
Setting Slant and Weight
|
|
------------------------
|
|
Slant is the style of the Text, and it can be ``NORMAL`` (the default),
|
|
``ITALIC`` or ``OBLIQUE``. Usually, for many fonts both ``ITALIC`` and
|
|
``OBLIQUE`` look similar, but ``ITALIC`` uses **Roman Style**, whereas
|
|
``OBLIQUE`` uses **Italic Style**.
|
|
|
|
Weight specifies the boldness of a font. You can see a list of weights in
|
|
:class:`manimpango.Weight`.
|
|
|
|
.. manim:: SlantsExample
|
|
:save_last_frame:
|
|
|
|
class SlantsExample(Scene):
|
|
def construct(self):
|
|
a = Text("Italic", slant=ITALIC)
|
|
self.add(a)
|
|
|
|
.. manim:: DifferentWeight
|
|
:save_last_frame:
|
|
|
|
class DifferentWeight(Scene):
|
|
def construct(self):
|
|
import manimpango
|
|
|
|
g = VGroup()
|
|
weight_list = dict(
|
|
sorted(
|
|
{
|
|
weight: manimpango.Weight(weight).value
|
|
for weight in manimpango.Weight
|
|
}.items(),
|
|
key=lambda x: x[1],
|
|
)
|
|
)
|
|
for weight in weight_list:
|
|
g += Text(weight.name, weight=weight.name, font="Open Sans")
|
|
self.add(g.arrange(DOWN).scale(0.5))
|
|
|
|
.. _using-colors:
|
|
|
|
Using Colors
|
|
------------
|
|
|
|
You can set the color of the text using :attr:`~.Text.color`:
|
|
|
|
.. manim:: SimpleColor
|
|
:save_last_frame:
|
|
|
|
class SimpleColor(Scene):
|
|
def construct(self):
|
|
col = Text("RED COLOR", color=RED)
|
|
self.add(col)
|
|
|
|
You can use utilities like :attr:`~.Text.t2c` for coloring specific characters.
|
|
This may be problematic if your text contains ligatures
|
|
as explained in :ref:`iterating-text`.
|
|
|
|
:attr:`~Text.t2c` accepts two types of dictionaries,
|
|
|
|
* The keys can contain indices like ``[2:-1]`` or ``[4:8]``,
|
|
this works similar to how `slicing <https://realpython.com/python-strings/#string-slicing>`_
|
|
works in Python. The values should be the color of the Text from :class:`~.Color`.
|
|
|
|
|
|
* The keys contain words or characters which should be colored separately
|
|
and the values should be the color from :class:`~.Color`:
|
|
|
|
.. manim:: Textt2cExample
|
|
:save_last_frame:
|
|
|
|
class Textt2cExample(Scene):
|
|
def construct(self):
|
|
t2cindices = Text('Hello', t2c={'[1:-1]': BLUE}).move_to(LEFT)
|
|
t2cwords = Text('World',t2c={'rl':RED}).next_to(t2cindices, RIGHT)
|
|
self.add(t2cindices, t2cwords)
|
|
|
|
If you want to avoid problems when using colors (due to ligatures), consider using
|
|
:class:`MarkupText`.
|
|
|
|
|
|
Using Gradients
|
|
---------------
|
|
|
|
You can add a gradient using :attr:`~.Text.gradient`. The value must
|
|
be an iterable of any length:
|
|
|
|
.. manim:: GradientExample
|
|
:save_last_frame:
|
|
|
|
class GradientExample(Scene):
|
|
def construct(self):
|
|
t = Text("Hello", gradient=(RED, BLUE, GREEN), font_size=96)
|
|
self.add(t)
|
|
|
|
You can also use :attr:`~.Text.t2g` for gradients with specific
|
|
characters of the text. It shares a similar syntax to :ref:`the
|
|
interface for colors <using-colors>`:
|
|
|
|
.. manim:: t2gExample
|
|
:save_last_frame:
|
|
|
|
class t2gExample(Scene):
|
|
def construct(self):
|
|
t2gindices = Text(
|
|
'Hello',
|
|
t2g={
|
|
'[1:-1]': (RED,GREEN),
|
|
},
|
|
).move_to(LEFT)
|
|
t2gwords = Text(
|
|
'World',
|
|
t2g={
|
|
'World':(RED,BLUE),
|
|
},
|
|
).next_to(t2gindices, RIGHT)
|
|
self.add(t2gindices, t2gwords)
|
|
|
|
Setting Line Spacing
|
|
--------------------
|
|
|
|
You can set the line spacing using :attr:`~.Text.line_spacing`:
|
|
|
|
.. manim:: LineSpacing
|
|
:save_last_frame:
|
|
|
|
class LineSpacing(Scene):
|
|
def construct(self):
|
|
a = Text("Hello\nWorld", line_spacing=1)
|
|
b = Text("Hello\nWorld", line_spacing=4)
|
|
self.add(Group(a,b).arrange(LEFT, buff=5))
|
|
|
|
|
|
.. _disable-ligatures:
|
|
|
|
Disabling Ligatures
|
|
-------------------
|
|
|
|
By disabling ligatures you would get a one-to-one mapping between characters and
|
|
submobjects. This fixes the issues with coloring text.
|
|
|
|
|
|
.. warning::
|
|
|
|
Be aware that using this method with text that heavily depends on
|
|
ligatures (Arabic text) may yield unexpected results.
|
|
|
|
You can disable ligatures by passing ``disable_ligatures`` to
|
|
:class:`Text`. For example:
|
|
|
|
.. manim:: DisableLigature
|
|
:save_last_frame:
|
|
|
|
class DisableLigature(Scene):
|
|
def construct(self):
|
|
li = Text("fl ligature",font_size=96)
|
|
nli = Text("fl ligature", disable_ligatures=True, font_size=96)
|
|
self.add(Group(li, nli).arrange(DOWN, buff=.8))
|
|
|
|
.. _iterating-text:
|
|
|
|
Iterating :class:`~.Text`
|
|
-------------------------
|
|
|
|
Text objects behave like :class:`VGroups <.VGroup>`. Therefore, you can slice and index
|
|
the text.
|
|
|
|
For example, you can set each letter to different color by iterating it.
|
|
|
|
.. manim:: IterateColor
|
|
:save_last_frame:
|
|
|
|
class IterateColor(Scene):
|
|
def construct(self):
|
|
text = Text("Colors", font_size=96)
|
|
for letter in text:
|
|
letter.set_color(random_bright_color())
|
|
self.add(text)
|
|
|
|
.. warning::
|
|
|
|
Please note that `Ligature`_ can cause problems here. If you need a
|
|
one-to-one mapping of characters to submobjects you should pass
|
|
the ``disable_ligatures`` parameter to :class:`~.Text`.
|
|
See :ref:`disable-ligatures`.
|
|
|
|
.. _Ligature: https://en.wikipedia.org/wiki/Ligature_(writing)
|
|
|
|
Working with :class:`~.MarkupText`
|
|
==================================
|
|
|
|
MarkupText is similar to :class:`~.Text`, the only difference between them is
|
|
that this accepts and processes PangoMarkup (which is similar to
|
|
html), instead of just rendering plain text.
|
|
|
|
Consult the documentation of :class:`~.MarkupText` for more details
|
|
and further references about PangoMarkup.
|
|
|
|
.. manim:: MarkupTest
|
|
:save_last_frame:
|
|
|
|
class MarkupTest(Scene):
|
|
def construct(self):
|
|
text = MarkupText(
|
|
f'<span underline="double" underline_color="green">double green underline</span> in red text<span fgcolor="{YELLOW}"> except this</span>',
|
|
color=RED,
|
|
font_size=34
|
|
)
|
|
self.add(text)
|
|
|
|
.. _rendering-with-latex:
|
|
|
|
Text With LaTeX
|
|
***************
|
|
|
|
Just as you can use :class:`~.Text` to add text to your videos, you can
|
|
use :class:`~.Tex` to insert LaTeX.
|
|
|
|
For example,
|
|
|
|
.. manim:: HelloLaTeX
|
|
:save_last_frame:
|
|
|
|
class HelloLaTeX(Scene):
|
|
def construct(self):
|
|
tex = Tex(r"\LaTeX", font_size=144)
|
|
self.add(tex)
|
|
|
|
.. note::
|
|
|
|
Note that we are using a raw string (``r'...'``) instead of a regular string (``'...'``).
|
|
This is because TeX code uses a lot of special characters - like ``\`` for example - that
|
|
have special meaning within a regular python string. An alternative would have been to
|
|
write ``\\`` to escape the backslash: ``Tex('\\LaTeX')``.
|
|
|
|
Working with :class:`~.MathTex`
|
|
===============================
|
|
|
|
Everything passed to :class:`~.MathTex` is in math mode by default. To be more precise,
|
|
:class:`~.MathTex` is processed within an ``align*`` environment. You can achieve a
|
|
similar effect with :class:`~.Tex` by enclosing your formula with ``$`` symbols:
|
|
``$\xrightarrow{x^6y^8}$``:
|
|
|
|
.. manim:: MathTeXDemo
|
|
:save_last_frame:
|
|
|
|
class MathTeXDemo(Scene):
|
|
def construct(self):
|
|
rtarrow0 = MathTex(r"\xrightarrow{x^6y^8}", font_size=96)
|
|
rtarrow1 = Tex(r"$\xrightarrow{x^6y^8}$", font_size=96)
|
|
|
|
self.add(VGroup(rtarrow0, rtarrow1).arrange(DOWN))
|
|
|
|
|
|
LaTeX commands and keyword arguments
|
|
====================================
|
|
|
|
We can use any standard LaTeX commands in the AMS maths packages. Such
|
|
as the ``mathtt`` math-text type or the ``looparrowright`` arrow.
|
|
|
|
.. manim:: AMSLaTeX
|
|
:save_last_frame:
|
|
|
|
class AMSLaTeX(Scene):
|
|
def construct(self):
|
|
tex = Tex(r'$\mathtt{H} \looparrowright$ \LaTeX', font_size=144)
|
|
self.add(tex)
|
|
|
|
On the Manim side, the :class:`~.Tex` class also accepts attributes to
|
|
change the appearance of the output. This is very similar to the
|
|
:class:`~.Text` class. For example, the ``color`` keyword changes the
|
|
color of the TeX mobject.
|
|
|
|
.. manim:: LaTeXAttributes
|
|
:save_last_frame:
|
|
|
|
class LaTeXAttributes(Scene):
|
|
def construct(self):
|
|
tex = Tex(r'Hello \LaTeX', color=BLUE, font_size=144)
|
|
self.add(tex)
|
|
|
|
Extra LaTeX Packages
|
|
====================
|
|
|
|
Some commands require special packages to be loaded into the TeX template.
|
|
For example, to use the ``mathscr`` script, we need to add the ``mathrsfs``
|
|
package. Since this package isn't loaded into Manim's tex template by default,
|
|
we have to add it manually.
|
|
|
|
.. manim:: AddPackageLatex
|
|
:save_last_frame:
|
|
|
|
class AddPackageLatex(Scene):
|
|
def construct(self):
|
|
myTemplate = TexTemplate()
|
|
myTemplate.add_to_preamble(r"\usepackage{mathrsfs}")
|
|
tex = Tex(
|
|
r"$\mathscr{H} \rightarrow \mathbb{H}$",
|
|
tex_template=myTemplate,
|
|
font_size=144,
|
|
)
|
|
self.add(tex)
|
|
|
|
Substrings and parts
|
|
====================
|
|
|
|
The TeX mobject can accept multiple strings as arguments. Afterwards you can
|
|
refer to the individual parts either by their index (like ``tex[1]``), or by
|
|
using :func:`~.set_color_by_tex`, which matches the argument exactly against
|
|
the strings passed to the constructor. In this example, we color the
|
|
``\bigstar`` part:
|
|
|
|
.. manim:: LaTeXSubstrings
|
|
:save_last_frame:
|
|
|
|
class LaTeXSubstrings(Scene):
|
|
def construct(self):
|
|
tex = Tex('Hello', r'$\bigstar$', r'\LaTeX', font_size=144)
|
|
tex.set_color_by_tex(r'$\bigstar$', RED)
|
|
self.add(tex)
|
|
|
|
Because :func:`~.set_color_by_tex` requires an exact match, it cannot directly
|
|
target a token inside a string that was passed as a single argument. To color
|
|
every ``x`` in a formula, use ``substrings_to_isolate`` to split the string at
|
|
each occurrence first:
|
|
|
|
.. manim:: CorrectLaTeXSubstringColoring
|
|
:save_last_frame:
|
|
|
|
class CorrectLaTeXSubstringColoring(Scene):
|
|
def construct(self):
|
|
equation = MathTex(
|
|
r"e^{x} = x^0 + x^1 + \frac{1}{2} x^2 + \frac{1}{6} x^3 + \cdots + \frac{1}{n!} x^n + \cdots",
|
|
substrings_to_isolate="x"
|
|
)
|
|
equation.set_color_by_tex("x", YELLOW)
|
|
self.add(equation)
|
|
|
|
Each isolated occurrence of ``x`` becomes its own sub-mobject that
|
|
:meth:`~.set_color_by_tex` can match exactly.
|
|
If one of the ``substrings_to_isolate`` is in a sub or superscript, it needs
|
|
to be enclosed by curly brackets.
|
|
|
|
Note that Manim also supports a custom syntax that allows splitting
|
|
a TeX string into substrings easily: simply enclose parts of your formula
|
|
that you want to isolate with double braces. In the string
|
|
``MathTex(r"{{ a^2 }} + {{ b^2 }} = {{ c^2 }}")``, the rendered mobject
|
|
will consist of the substrings ``a^2``, ``+``, ``b^2``, ``=``, and ``c^2``.
|
|
This makes transformations between similar text fragments easy
|
|
to write using :class:`~.TransformMatchingTex`.
|
|
|
|
For Manim to recognise a ``{{`` as a group opener, it must appear either
|
|
at the very start of the string or be immediately preceded by a whitespace
|
|
character. This means that ``{{`` embedded directly after non-whitespace
|
|
LaTeX — such as ``\frac{{{n}}}{k}`` or ``a^{{2}}`` — is left untouched,
|
|
which prevents accidental splitting of ordinary nested-brace expressions.
|
|
To stop a leading ``{{`` from being treated as a group opener, insert a
|
|
space between the two braces: ``{{ ... }}`` → ``{ { ... } }``.
|
|
|
|
Using ``index_labels`` to work with complicated strings
|
|
=======================================================
|
|
|
|
You might sometimes be working with a very complicated :class:`~.MathTex` mobject
|
|
that makes it difficult to work with its individual components. This is
|
|
where the debugging function :func:`.index_labels` is very useful.
|
|
|
|
The method shows the index of a mobject's submobjects, allowing you
|
|
to easily find the components of the mobject you would like to change.
|
|
|
|
.. manim:: IndexLabelsMathTex
|
|
:save_last_frame:
|
|
|
|
class IndexLabelsMathTex(Scene):
|
|
def construct(self):
|
|
text = MathTex(r"\binom{2n}{n+2}", font_size=96)
|
|
|
|
# index the first (and only) term of the MathTex mob
|
|
self.add(index_labels(text[0]))
|
|
|
|
text[0][1:3].set_color(YELLOW)
|
|
text[0][3:6].set_color(RED)
|
|
self.add(text)
|
|
|
|
|
|
LaTeX Maths Fonts - The Template Library
|
|
========================================
|
|
|
|
Changing fonts in LaTeX when typesetting mathematical formulae is
|
|
trickier than regular text. It requires changing the template that is used
|
|
to compile the TeX. Manim comes with a collection of :class:`~.TexFontTemplates`
|
|
ready for you to use. These templates will all work in math mode:
|
|
|
|
.. manim:: LaTeXMathFonts
|
|
:save_last_frame:
|
|
|
|
class LaTeXMathFonts(Scene):
|
|
def construct(self):
|
|
tex = Tex(
|
|
r"$x^2 + y^2 = z^2$",
|
|
tex_template=TexFontTemplates.french_cursive,
|
|
font_size=144,
|
|
)
|
|
self.add(tex)
|
|
|
|
Manim also has a :class:`~.TexTemplateLibrary` containing the TeX
|
|
templates used by 3Blue1Brown. One example is the ctex template,
|
|
used for typesetting Chinese script. For this to work, the ctex LaTeX package
|
|
must be installed on your system. Furthermore, if you are only
|
|
typesetting Text, you probably do not need :class:`~.Tex` at all, and
|
|
should use :class:`~.Text` instead.
|
|
|
|
.. manim:: LaTeXTemplateLibrary
|
|
:save_last_frame:
|
|
|
|
class LaTeXTemplateLibrary(Scene):
|
|
def construct(self):
|
|
tex = Tex('Hello 你好 \\LaTeX', tex_template=TexTemplateLibrary.ctex, font_size=144)
|
|
self.add(tex)
|
|
|
|
|
|
Aligning formulae
|
|
=================
|
|
|
|
:class:`~.MathTex` mobject is typeset in the LaTeX ``align*``
|
|
environment. This means you can use the ``&`` alignment character
|
|
when typesetting multiline formulae:
|
|
|
|
.. manim:: LaTeXAlignEnvironment
|
|
:save_last_frame:
|
|
|
|
class LaTeXAlignEnvironment(Scene):
|
|
def construct(self):
|
|
tex = MathTex(r'f(x) &= 3 + 2 + 1\\ &= 5 + 1 \\ &= 6', font_size=96)
|
|
self.add(tex)
|