Manim 文本渲染完全指南:Text、MarkupText、Tex、MathTex 与 Typst 的选型与实践
2026/9/12 0:40:37 网站建设 项目流程

Manim 文本渲染完全指南:Text、MarkupText、Tex、MathTex 与 Typst 的选型与实践

【免费下载链接】manimA community-maintained Python framework for creating mathematical animations.项目地址: https://gitcode.com/GitHub_Trending/man/manim

导读

在 Manim 中为动画添加文字与公式有三种互不冲突的渲染路径:基于 Pango 的普通文本类(TextMarkupTextParagraph)、基于 LaTeX 的公式排版类(TexMathTex),以及无需 TeX 发行版的 Typst 排版类(TypstMathTypst)。本文以 docs/source/guides/using_text.rst 为核心骨架,结合 manim/mobject/text/text_mobject.py、manim/mobject/text/tex_mobject.py、manim/mobject/text/typst_mobject.py 等源码,系统讲解每条路径的能力边界、核心参数、逐字符着色、公式切分与字体模板等实战技巧。读完本文,你将能够针对「纯文本、富文本标记、数学公式、中文排版」等不同场景做出正确选型,并熟练使用t2c/t2gsubstrings_to_isolateindex_labels等高阶能力。

三种渲染路径总览

Manim 渲染视频中的文字一共有三条可选路径:

  1. Pango:对应模块 manim/mobject/text/text_mobject.py,提供TextMarkupText及派生类Paragraph,适合普通文本、多语言文本与富文本标记;
  2. LaTeX:对应模块 manim/mobject/text/tex_mobject.py,提供TexMathTex,适合数学公式与专业排版,但依赖系统中安装的 TeX 发行版;
  3. Typst:对应模块 manim/mobject/text/typst_mobject.py,提供TypstMathTypst,通过 Typst 编译器将标记直接编译为 SVG 矢量图导入,无需安装 TeX 发行版,但需要额外安装可选的typst依赖(pip install manim[typst],源码要求typst>=0.14)。

三者产出物都是矢量 mobject,可以继续参与TransformWrite等动画编排,唯一的区别是底层的排版引擎与能力边界。

使用 Text:Pango 渲染的普通文本

最简单的添加文字方式就是Text类。它基于 Pango 库渲染,天然支持非英文字母表,例如你好こんにちは안녕하세요مرحبا بالعالم都能直接渲染。

class HelloWorld(Scene): def construct(self): text = Text("Hello world", font_size=144) self.add(text)

在源码 text_mobject.py 的 Text 类定义 中可以看到,Text继承自SVGMobject,其内部把每个字符拆分为独立的子对象(submobject),因此它「表现得像一个 VGroup」:可以切片、索引、迭代,也可以逐字符修改颜色与透明度。空白字符与换行符在内部会被剥离,不会成为独立子对象——这一点在下面介绍t2c切片语义时会再次强调。

字体:font 参数与字体发现

通过font参数可以切换字体:

class FontsExample(Scene): def construct(self): ft = Text("Noto Sans", font="Noto Sans") self.add(ft)

有两个前提需要注意(源码 text_mobject.py 的文档字符串与 第 476-490 行 的字体校验逻辑):

  • 该字体必须已安装在系统中,且 Pango 能识别到它;
  • 可以用manimpango.list_fonts()获取系统字体列表:
>>> import manimpango >>> manimpango.list_fonts() [...]

Text.font_list()静态方法(带functools.cache缓存)正是调用manimpango.list_fonts()获取字体表,warn_missing_font=True(默认值)时,若传入的字体不在表中会发出警告;sans-serif会被特殊映射为sans。注意字体族名称在不同操作系统上可能不同。

倾斜与字重:slant 与 weight

  • slant控制字形风格,取值NORMAL(默认)、ITALICOBLIQUE。多数字体下ITALICOBLIQUE视觉接近,但ITALIC使用罗马风格衬线斜体,OBLIQUE使用纯倾斜变换;
  • weight控制字重(粗体程度),可用取值见manimpango.Weight枚举。
class SlantsExample(Scene): def construct(self): a = Text("Italic", slant=ITALIC) self.add(a)
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))

源码层面,Text.__init__slantweight原样保存到self.slantself.weight(text_mobject.py),随后交由 manimpango 生成对应的 SVG 字形。

颜色:color 与 t2c

整体着色使用color

class SimpleColor(Scene): def construct(self): col = Text("RED COLOR", color=RED) self.add(col)

特定字符着色使用t2c(text-to-color 字典)。t2c接受两种键:

  • 索引切片:键形如"[2:-1]""[4:8]",语义与 Python 字符串切片一致,值为Color颜色;
  • 字词匹配:键为需要单独着色的单词或字符,值为颜色。
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)

重要切片语义陷阱(源码 text_mobject.py 的 warning 注释):直接切片对象本身(my_text[3:5])是对渲染后字符(已去掉空白)的索引;而t2c/t2s/t2w/t2f/t2g字典里的"[3:7]"切片针对的是原始 text 参数字符串(含空白)。例如Text("Hello World")t2c={'[3:7]': RED}会命中索引 3~6 的字符(其中索引 5 是空格,无内容可着色),而my_text[3:7]选中的是 4 个渲染字符loWo。当要选中的子串已知时,优先直接用子串本身做键(如t2c={"world": RED}),按文本搜索匹配,不受该陷阱影响。此外,文本中的连字(ligature)会破坏「字符 ↔ 子对象」的一一对应关系,导致基于索引的着色不可靠,见下文「禁用连字」一节;若想彻底避免这类问题,可直接改用MarkupText

渐变:gradient 与 t2g

gradient参数接受任意长度的颜色迭代器,为整段文本铺设渐变:

class GradientExample(Scene): def construct(self): t = Text("Hello", gradient=(RED, BLUE, GREEN), font_size=96) self.add(t)

t2gt2c语法一致,只是值为颜色元组,用于给特定字符/索引区域单独加渐变:

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)

行距:line_spacing

line_spacing控制多行文本的行距,默认值为-1(自动)。数值越大行距越宽:

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

许多字体(尤其拉丁字体)会把fifl等字符组合渲染成单个连字字形,这会让字符与子对象不再是「一对一」映射,从而破坏基于索引的逐字符着色/迭代。传入disable_ligatures=True即可强制关闭连字:

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))

警告:对重度依赖连字的文本(如阿拉伯语)禁用连字可能产生意外结果,使用时需自行评估。

迭代 Text:像 VGroup 一样逐字符操作

Text对象行为类似VGroup,可以切片、索引、遍历。例如逐字符随机染色:

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)

同样地,连字可能造成「一个字母对不上一个子对象」的问题;如果需要严格的一对一映射,请配合disable_ligatures=True

派生类 Paragraph:多行段落排版

ParagraphText家族的另一个成员(text_mobject.py),用于排版多行段落。它接受多个字符串参数(每行一个),关键参数包括:

  • line_spacing:行距,默认-1表示自动;
  • alignment:对齐方式,取值"left""right""center",默认None
paragraph = Paragraph( "this is a awesome", "paragraph", "With \nNewlines", "\tWith Tabs", " With Spaces", "With Alignments", "center", "left", "right", )

段落对象支持按行对齐微调(源码中的_set_line_alignment_change_alignment_for_a_line等私有方法),也可以借助remove_invisible_chars()(同文件第 90-111 行)清除由空格等产生的不可见点状子对象,便于做Transform动画。

使用 MarkupText:PangoMarkup 富文本

MarkupTextText的唯一区别是:它接受并处理PangoMarkup(一种类似 HTML 的标记语言),而不是渲染纯文本。通过标记可以实现单段文本内的混排颜色、下划线等:

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)

再如用fgcolor让片段脱离整体颜色:

class SingleLineColor(Scene): def construct(self): text = MarkupText( f'all in red <span fgcolor="{YELLOW}">except this</span>', color=RED ) self.add(text)

从源码看,MarkupText通过 manimpango 的MarkupUtils解析标记,并重写了_extract_color_tags_extract_gradient_tags等方法将标记转换为渲染设置。它的最大价值在于从根上绕开连字问题——按字词着色不再依赖索引,因此无需disable_ligatures兜底。更多 PangoMarkup 标签细节可查阅MarkupText的类文档。

使用 Typst:无需 TeX 发行版的排版方案

Manim 通过TypstMathTypst支持 Typst 排版。Typst mobject 会把 Typst 标记直接编译成 SVG 再以矢量图导入(源码 typst_mobject.py,内部调用typst_to_svg_file),既支持普通标记,也支持数学表达式。

重要:Typst 支持依赖可选包typst,安装方式为pip install manim[typst](源码要求typst>=0.14)。未安装时无法使用这两个类。

普通标记示例(*...*表示粗体、_..._表示斜体):

class HelloTypst(Scene): def construct(self): text = Typst(r"*Hello* from _Typst!_", font_size=96) self.add(text)

数学表达式使用MathTypst

class HelloMathTypst(Scene): def construct(self): equation = MathTypst(r"sum_(k=1)^n k = (n(n + 1)) / 2", font_size=72) self.add(equation)

Typst 子表达式选择:select

Typst mobject 支持通过两种方式标记子表达式,然后用select()选中并着色:

  1. Typst源码中直接写 Typst 标签(<label>);
  2. MathTypst中使用 Manim 的{{ ... }}简写(注意:{{ }}简写目前MathTypst支持Typst需要在源码中写标签,例如#box[body] <label>)。
eq = MathTypst("{{ a + b : lhs }} = {{ c }}") eq.select("lhs").set_color(BLUE) eq.select(0).set_color(YELLOW)

select既接受标签名(字符串)也接受索引(整数),返回对应的VGroup供继续着色或变换;源码中通过_select_label_rebuild_label_aliases维护标签别名映射,{{ ... : label }}中的: label会被正则_LABEL_RE提取为 Typst 标签(typst_mobject.py)。调试对齐时还可启用track_baselines=True配合baseline_frames/get_baseline_frame查看逐元素基线框。

使用 Tex 与 MathTex:LaTeX 排版

Text对应,Manim 用Tex插入 LaTeX 文本,用MathTex插入数学公式:

class HelloLaTeX(Scene): def construct(self): tex = Tex(r"\LaTeX", font_size=144) self.add(tex)

注意:这里必须使用原始字符串r'...',因为 TeX 代码里充满\等对 Python 字符串有特殊含义的字符。等价写法是手动转义:Tex('\\LaTeX')

TexMathTex的实现位于 manim/mobject/text/tex_mobject.py:Tex继承自MathTex(第 614 行),MathTex继承自SingleStringMathTex,最终都会调用tex_to_svg_file()把 TeX 源码编译为 SVG。同一模块还提供了BulletedList(带项目符号的列表)与Title(带下划线的标题)两个实用派生类。

MathTex 的数学模式与 align* 环境

MathTex的一切内容默认处于数学模式;更准确地说,MathTex在 LaTeX 的align*环境中处理内容。在源码 tex_mobject.py 可以看到默认tex_environment="align*"。用Tex加上$符号可达到类似效果:

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))

使用 AMS 宏包命令与外观属性

任何标准 AMS 数学宏包命令都可以直接使用,例如\mathtt\looparrowright

class AMSLaTeX(Scene): def construct(self): tex = Tex(r'$\mathtt{H} \looparrowright$ \LaTeX', font_size=144) self.add(tex)

在 Manim 侧,Tex还接受与Text类似的观感参数,例如color直接改变整个 TeX mobject 的颜色:

class LaTeXAttributes(Scene): def construct(self): tex = Tex(r'Hello \LaTeX', color=BLUE, font_size=144) self.add(tex)

额外 LaTeX 宏包:TexTemplate 与 add_to_preamble

部分命令需要额外的宏包加载进 TeX 模板。例如\mathscr需要mathrsfs宏包,而它默认不在 Manim 的模板中,需要手动加入:

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)

TexTemplate类定义在 manim/utils/tex.py,核心字段包括:

  • tex_compiler:TeX 编译器,可为单个字符串或按序尝试的列表(如"latex""pdflatex""lualatex"["lualatex", "pdflatex"]);
  • output_format:编译输出格式,如.dvi.pdf.xdv
  • documentclass:文档类,默认为\documentclass[preview]{standalone}
  • preamble:文档导言区(\documentclass\begin{document}之间);
  • placeholder_text:会被待渲染表达式替换的占位文本,默认为YourTextHere
  • add_to_preamble(txt, prepend=False):向导言区追加/前置内容;
  • 也支持TexTemplate.from_file(...).tex文件整体读取模板(此时add_to_preamble不再生效)。

子串与部分:多字符串参数、set_color_by_tex 与 {{ }} 语法

Tex/MathTex可以接受多个字符串参数,之后按索引(tex[1])或用set_color_by_tex精确匹配参数来引用各部分。set_color_by_tex要求精确匹配构造函数传入的字符串:

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)

因为要求精确匹配,它无法直接命中「作为单个参数传入的字符串内部的某个 token」。要逐个给公式里的x着色,先用substrings_to_isolate="x"把字符串在每个x处切开:

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)

被隔离出的每个x都会成为独立子对象,set_color_by_tex因而能精确命中。若某个substrings_to_isolate出现在上标/下标中,需要给它加上花括号包裹。

Manim 还提供了一种自定义语法{{ ... }},把单个 TeX 字符串拆成多个子串,例如:

MathTex(r"{{ a^2 }} + {{ b^2 }} = {{ c^2 }}")

渲染出的 mobject 将包含子串a^2+b^2=c^2,这让同构公式之间的TransformMatchingTex变换变得极易书写。关于解析规则,源码 tex_mobject.py 的_split_double_braces给出了精确语义:

  • {{只有出现在字符串最开头紧跟在空白字符之后时才被视为分组开始符;因此嵌入在非空白 LaTeX 之后的{{(如\frac{{{n}}}{k}a^{{2}})不会被误拆,避免了破坏普通嵌套花括号表达式;
  • 分组内部会跟踪真实 LaTeX 花括号深度,}}只有在内部深度为 0 时才闭合 Manim 分组,所以{{ a^{b^{c}} }}能正确处理;
  • 转义序列\\\{\}会作为原子单元消费,避免误判。

若想阻止开头的{{被识别为分组符,在两个花括号之间插入空格即可:{{ ... }}{ { ... } }。编译失败时,若检测到发生了{{ }}拆分,源码会额外输出一条提示错误,引导用户用空格规避自动拆分(tex_mobject.py)。

调试复杂公式:index_labels

面对结构复杂的MathTex,可以用调试函数index_labels()(定义于 manim/utils/debug.py)把每个子对象的索引号显示出来,快速定位要修改的部件:

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)

index_labels返回由Integer组成的VGroup,支持label_heightbackground_stroke_widthbackground_stroke_color等外观参数;额外参数会透传给内部的Integermobject。

LaTeX 数学字体:模板库 TexFontTemplates 与 TexTemplateLibrary

数学模式下更换字体比普通文本更麻烦,需要更换编译 TeX 所用的模板。Manim 内置了两套现成模板:

  • TexFontTemplates(manim/utils/tex_templates.py):一大批可直接在数学模式使用的字体模板,例如french_cursivecomic_sanszapf_chancery等几十种(完整列表见源码第 99-152 行注释)。注意多数模板要求本机安装了对应字体才能正常工作。
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)
  • TexTemplateLibrary(manim/utils/tex_templates.py):包含 3Blue1Brown 使用的 TeX 模板,成员有defaultthreeb1bctexsimple。其中ctex用于排版中文脚本,它把默认导言中的\DisableLigatures{...}替换为\usepackage[UTF8]{ctex},并使用xelatex编译器与.xdv输出格式;使用前提是系统安装了 ctex LaTeX 宏包
class LaTeXTemplateLibrary(Scene): def construct(self): tex = Tex('Hello 你好 \\LaTeX', tex_template=TexTemplateLibrary.ctex, font_size=144) self.add(tex)

选型建议:如果只是排版纯文本(即使包含中文),通常并不需要Tex,直接用Text(Pango)即可——它不依赖 TeX 发行版,渲染更快。

多行公式对齐:& 对齐符

MathTexalign*环境中排版,因此多行公式可以直接使用&对齐字符与\\换行:

class LaTeXAlignEnvironment(Scene): def construct(self): tex = MathTex(r'f(x) &= 3 + 2 + 1\\ &= 5 + 1 \\ &= 6', font_size=96) self.add(tex)

align*环境会按&位置纵向对齐各行,得到教科书式的等号对齐效果。

场景选型速查

需求推荐类依赖关键参数
纯文本 / 多语言文本TextPangofontslantweightcolort2ct2ggradientline_spacingdisable_ligatures
富文本标记(混排颜色/下划线)MarkupTextPangoPangoMarkup 标签 +colorfont_size
多行段落ParagraphPangoline_spacingalignment
数学公式MathTexTeX 发行版substrings_to_isolate{{ }}set_color_by_textex_template
混合文本 + 少量公式TexTeX 发行版tex_environment(默认center)、tex_template
免 TeX 的标记/公式Typst/MathTypstpip install manim[typst]select(){{ ... : label }}track_baselines
中文 LaTeX 排版Tex(..., tex_template=TexTemplateLibrary.ctex)TeX 发行版 + ctex 宏包tex_template

结语

Manim 的三条文本渲染路径覆盖了从「最简纯文本」到「专业数学排版」再到「免 TeX 发行版」的完整梯度:Text/MarkupText/Paragraph负责日常文字与富文本,Tex/MathTex负责公式与专业排版(辅以TexTemplate/TexFontTemplates/TexTemplateLibrary控制字体宏包),Typst/MathTypst则提供了无需安装 TeX 的轻量替代。配合t2c/t2gdisable_ligaturessubstrings_to_isolate{{ }}分组语法与index_labels调试工具,你可以精确到字符或公式片段地控制每一处视觉细节。相关源码与测试(如 manim/mobject/text/text_mobject.py、manim/mobject/text/tex_mobject.py、manim/mobject/text/typst_mobject.py)都可作为继续深入的第一手材料。

【免费下载链接】manimA community-maintained Python framework for creating mathematical animations.项目地址: https://gitcode.com/GitHub_Trending/man/manim

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询