Text 与段落¶
Manim 中最常用的文本类是 Text:它把字符串交给 Pango 排版、再转成 SVG 矢量图形,因此无需安装 TeX 就能显示包括中文在内的 Unicode 文本。本节覆盖 Text 的完整参数体系、两种索引约定、多行排版 Paragraph,以及自定义字体 register_font。
Text¶
Text(text, fill_opacity=1.0, stroke_width=0, color=None, font_size=48, line_spacing=-1, font='', slant='NORMAL', weight='NORMAL', t2c=None, t2f=None, t2g=None, t2s=None, t2w=None, gradient=None, tab_width=4, warn_missing_font=True, height=None, width=None, should_center=True, disable_ligatures=False, use_svg_cache=False, **kwargs) 官方文档 ↗
Text 用于渲染一段纯文本,每个可见字符对应一个子对象(submobject),可以按索引或切片单独着色、移动、替换。
Text(
text: str,
fill_opacity: float = 1.0,
stroke_width: float = 0,
color: ParsableManimColor | None = None,
font_size: float = 48,
line_spacing: float = -1,
font: str = "",
slant: str = "NORMAL",
weight: str = "NORMAL",
t2c: dict[str, str] | None = None,
t2f: dict[str, str] | None = None,
t2g: dict[str, Iterable[ParsableManimColor]] | None = None,
t2s: dict[str, str] | None = None,
t2w: dict[str, str] | None = None,
gradient: Iterable[ParsableManimColor] | None = None,
tab_width: int = 4,
warn_missing_font: bool = True,
height: float | None = None,
width: float | None = None,
should_center: bool = True,
disable_ligatures: bool = False,
use_svg_cache: bool = False,
)
name |
type |
default |
desc |
|---|---|---|---|
|
str |
— |
要渲染的文本 |
|
float |
48 |
字号(Manim 字体单位,非像素) |
|
str |
"" |
字体名或字体文件路径;空串用 Pango 默认字体 |
|
str |
"NORMAL" |
字重: |
|
str |
"NORMAL" |
字态: |
|
ParsableManimColor | None |
None |
整体颜色 |
|
Iterable[color] | None |
None |
渐变填充色列表,与 |
|
dict | None |
None |
文本子串 → 颜色映射 |
|
dict | None |
None |
文本子串 → 字体映射 |
|
dict | None |
None |
文本子串 → 渐变映射 |
|
dict | None |
None |
文本子串 → 字态映射 |
|
dict | None |
None |
文本子串 → 字重映射 |
|
float |
-1 |
行距倍数,-1 时用字体默认行距 |
|
int |
4 |
制表符展开为空格数 |
|
bool |
False |
禁用连字,强制字符与字形一一对应 |
|
bool |
True |
字体找不到时是否告警 |
|
float | None |
None |
指定后整体缩放到目标高度/宽度 |
|
bool |
True |
构造后是否居中到原点 |
|
bool |
False |
相同文本复用 SVG 缓存 |
下面这个示例依次演示 gradient 整体渐变、t2c 子串着色、weight/slant 字重字态:
gradient 接受颜色列表做线性渐变;t2c 按子串匹配着色;weight 与 slant 控制整段字的粗斜体。
查看源码 text_basics.py
"""TextBasics: Text 的渐变、局部着色与字重字态。"""
from manim import *
class TextBasics(Scene):
def construct(self):
gradient_text = Text("渐变文字", gradient=(BLUE, GREEN))
keycolored_text = Text("关键词着色:ManimCE 教程", t2c={"ManimCE": YELLOW})
styled_text = Text("粗体与斜体", weight=BOLD, slant=ITALIC)
self.play(Write(gradient_text))
self.play(FadeOut(gradient_text), FadeIn(keycolored_text))
self.play(FadeOut(keycolored_text), FadeIn(styled_text))
self.wait(0.5)
t2c 与切片的索引约定¶
t2c 等映射的键除了子串,还可以是作用于原始字符串的切片(如 "[3:7]")。这里有一个极易踩坑的地方:两种索引方式对空白的处理不一致(v0.21.0 源码中的明确行为):
my_text[3:5]:索引到渲染出的字符,即“去掉空白后的文本”。Text("Hello world")的索引5指的是"w",而不是空格。t2c={"[3:7]": RED}:切片作用于原始text参数,空白也算在内。t2c={"[3:7]": RED}着色的会是"llo W"。
direct_text[0:5] 按去空白后的渲染字符计数(空格不占位),而 t2c={"[0:3]": ...} 按原始字符串切片——两套约定不要混用。
查看源码 text_index_slice.py
"""TextIndexSlice: Text 两种索引约定——渲染字符索引与原文切片。"""
from manim import *
class TextIndexSlice(Scene):
def construct(self):
t2c_text = Text("t2c 按原文切片着色", t2c={"[0:3]": YELLOW}, font_size=36)
direct_text = Text("Hello World", font_size=48)
self.play(Write(t2c_text))
self.play(FadeOut(t2c_text), Write(direct_text))
# direct_text[0:5] 按“去掉空白后的渲染字符”计数:Hello
# 空格不占位,World 从索引 5 开始
self.play(
direct_text[0:5].animate.set_color(BLUE),
direct_text[5:10].animate.set_color(RED),
)
self.wait(0.5)
常见坑¶
常见错误
连字(ligature)会让多个字符合并成一个字形,破坏“一字符一字形”的对应关系,直接索引会错位。涉及逐字符动画时传 disable_ligatures=True。若排版后字形数仍少于非空白字符数,v0.21.0 会直接抛出 ValueError,提示选择实现连字方式不同的字体(例如带 calt 特性的编程连字字体 Fira Code)。
常见错误
font 传不存在的字体名不会报错,只会静默回退到默认字体并在日志告警(可用 warn_missing_font=False 关闭)。要确认字体是否真正生效,渲染后用 text.submobjects[0] 的实际字形核对,或直接指定字体文件路径。
提示
gradient 与 t2c 互斥:同时传入时按 t2c 优先处理。想“整体渐变 + 局部改色”,请先 gradient 再对子对象 set_color。
Paragraph¶
Paragraph(*text, line_spacing=-1, alignment=None, **kwargs) 官方文档 ↗
Paragraph 用于多行段落:每个位置参数是一行文本,整体是一个 VGroup,每行是一个子对象,可单独取出行做动画。
Paragraph(*text: str, line_spacing: float = -1, alignment: str | None = None, **kwargs)
name |
type |
default |
desc |
|---|---|---|---|
|
str |
— |
每一行一个位置参数 |
|
float |
-1 |
行距倍数,-1 时用默认行距 |
|
str | None |
None |
每行对齐方式: |
其余 **kwargs(font_size、t2c、font 等)原样传给内部的 Text。
Paragraph 每行是一个子对象,para[2] 直接取出行设置颜色;alignment="center" 让每行居中。
查看源码 paragraph_demo.py
"""ParagraphDemo: Paragraph 多行对齐。"""
from manim import *
class ParagraphDemo(Scene):
def construct(self):
para = Paragraph(
"Paragraph 把多行文本",
"排列成一个整体对象",
"支持逐行设置对齐",
alignment="center",
font_size=32,
)
self.play(FadeIn(para, shift=UP))
self.play(para[2].animate.set_color(YELLOW))
self.wait(0.5)
Text 与 Paragraph 的分工:
Text |
Paragraph |
|
|---|---|---|
输入 |
单个字符串(含 |
每行一个参数 |
结构 |
每个字符一个子对象 |
每行一个子对象 |
逐字动画 |
直接支持 |
需先取行再索引 |
逐行对齐 |
不支持(整体对齐) |
支持 |
适用场景 |
标题、标注、逐字效果 |
正文段落、行级动画 |
register_font¶
register_font(font_file) 官方文档 ↗
register_font 把字体文件临时加入 Pango 搜索路径,让 Text(..., font=...) 能用未安装到系统的字体。必须用上下文管理器 with 使用,退出后字体即从搜索路径移除。
from manim import *
class CustomFont(Scene):
def construct(self):
with register_font("assets/MyFont.ttf"):
text = Text("自定义字体", font="My Font Name")
self.play(Write(text))
文件查找顺序:绝对路径 → assets/fonts/ → fonts/ → 当前目录。找不到文件抛 FileNotFoundError。
平台注意
register_font 在 macOS 上依赖 ManimPango>=0.2.3,更早版本会抛 AttributeError。Windows 与 Linux 无此限制。CI 环境若缺字体,渲染结果会回退为默认字体——建议把字体文件随仓库放在 assets/fonts/ 下。
remove_invisible_chars¶
remove_invisible_chars(mobject) 返回一个去掉不可见字符(空格等宽度为零的占位)的副本,常用于 TransformMatchingShapes 等需要“按可见字形配对”的场景,避免空白占位干扰匹配。
def remove_invisible_chars(mobject: VMobject) -> VMobject
版本说明
v0.21.0 起,字形缺失/不对应时的报错信息显著改进:错误信息会明确指出是连字(含 calt 类编程连字)导致字符与字形无法一一对应,并建议更换字体。另外空白与换行从不成为子对象,索引约定与文档字符串中的描述完全一致(见上文「t2c 与切片的索引约定」)。
自测¶
✏️ 练习
Text("Hello World")[6] 选中的是哪个字符?t2c={"[6:11]": RED} 着色的又是哪段?
✅ 参考答案
text[6] 按去空白后的渲染字符计数,"HelloWorld" 索引 6 是 "o"(World 的第二个字母)。t2c 切片按原始字符串计空白,"[6:11]" 着色的是 "World"。
✏️ 练习
要对 "efficient" 中的 ffi 连字做逐字变色动画,只按索引取子对象会发生什么?怎么避免?
✅ 参考答案
默认情况下 ffi 可能被排成一个连字字形,索引与子对象错位。构造时传 disable_ligatures=True 强制一字符一字形,再按索引操作;若字形数仍对不上,v0.21.0 会抛 ValueError 并提示换用连字实现方式不同的字体。
下一步¶
纯文本之外,MarkupText 让你用 Pango 标记语言直接控制字号、颜色、上下标等富文本效果。