Texto
scene.text: prosa, ecuaciones, partes semánticas, estilo, flujo, selecciones y documentos Typst
scene.text(...) crea prosa, títulos, párrafos, matemáticas y contenido mixto; scene.text.equation(...) es su atajo para ecuaciones en bloque. Ambas devuelven un Text: un Drawable vectorial que conserva la estructura semántica, se mide con el mismo motor que Layout y permite seleccionar y animar partes sueltas.
from gaanim import GOLD, Scene, TextFlow, part
scene = Scene(frame=(16, 9), background="#0f172a")
formula = part("formula", "$E = ", part("mass", "m", color=GOLD), " c^2$")
copy = scene.text(
"La energía es ", formula,
role="body",
flow=TextFlow(wrap=5, align="center", line_spacing=1.2),
).move_to(0, 0)
scene.play([copy.animate.write(by="part").duration(1.0)])
scene.render()
Cada pieza tiene una responsabilidad:
TextStylecontrola el aspecto de los glifos y las métricas tipográficas.TextFlowcontrola cómo se componen las líneas dentro del texto.- Layout controla la caja exterior: relleno, ajuste, crecimiento y posición.
TextSelectionseñala glifos dentro de unText; nunca es un hijo independiente de Layout.
Crear texto
Las dos fábricas de texto y los roles tipográficos del tema.
$…$ activa la matemática y \$ escribe un dólar literal. color acepta un Color, una cadena CSS/hex o una tupla RGB(A); None hereda el color del estilo o del tema. lang="es" con hyphenate=True da párrafos justificados con guiones en español. Contenido, delimitadores, roles, métricas o flujos inválidos lanzan TypeError o ValueError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
content | str | TextPart | TextParts | obligatorio | Una o varias cadenas, partes semánticas o grupos de partes. El resultado no puede estar vacío. |
role | str | None | None | Rol tipográfico. Si todo es matemático se infiere math; si no, body. |
style / flow | TextStyle | None / TextFlow | None | None | Estilo y flujo reutilizables. |
font … baseline | argumentos con nombre | None | Ajustes directos de estilo: fuente, métricas, color, opacidad, espaciado y línea base. Sustituyen a style. |
wrap … lang | argumentos con nombre | None | Ajustes directos de flujo: ajuste de línea, alineación, límite de líneas, desbordamiento, dirección, guiones e idioma. Sustituyen a flow. |
markup | bool | None | None | Interpreta *negrita* y _cursiva_. False deja * y _ literales (la matemática $…$ sigue activa); None usa text_markup del tema, que por defecto es True. |
title = scene.text("Movimiento circular", role="title")
note = scene.text("Radio $r = 1.5$", size=0.36, color="#94a3b8").move_to(0, -1)$ … $: esos espacios hacen que Typst componga una ecuación en bloque. Cada frontera entre piezas es un espacio normal de Typst, así que Typst decide el espaciado de operadores e identificadores. Sin rol ni tamaño explícitos usa el tamaño matemático de 0.44 unidades. Las filas de una ecuación partida con \ guardan la separación de Typst entre filas con line_spacing=1 y cada unidad de más la ensancha un tamaño de letra, como entre líneas de texto. Contenido vacío o matemática inválida lanzan ValueError al crear la ecuación.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
content | str | TextPart | TextParts | obligatorio | Ecuación sin los delimitadores $. |
opciones | igual que scene.text | None | Comparte todo el estilo y el flujo de scene.text (salvo lang y markup). |
from gaanim import GOLD, Scene, part, parts
scene = Scene(frame=(16, 9), background="#0f172a")
equation = scene.text.equation(
part("sum_force", "sum F_t"),
"=",
parts(mass="m", acceleration="a_t"),
).move_to(0, 0)
equation["acceleration"].fill(GOLD)
scene.play([equation.animate.write(by="part").duration(1.0)])
scene.render()
siunitx en LaTeX: un espacio fino entre número y unidad (salvo en °, ′ y ″), unidades en redonda, exponentes y una barra en lugar de una fracción. Así 1 m³ y 181 L quedan espaciados igual. Una unidad mal formada lanza ValueError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
value | int | float | obligatorio | El número; un entero conserva sus cifras. |
unit | str | "" | Factores separados por espacios, *, · o ., cada uno con un exponente opcional (m^3, s^-1); / divide por los que siguen: "kg m/s^2". |
decimals | int | None | None | Decimales fijos; sin él, la forma más corta del número. |
decimal_separator | str | "." | Un carácter, por ejemplo ",". |
str — Matemática Typst para scene.text.equation.from gaanim import Scene, quantity
scene = Scene(frame=(16, 9), background="#0f172a")
volumen = scene.text.equation("V =", quantity(1, "m^3"), "=", quantity(1000, "L")).move_to(0, 0.8)
gravedad = scene.text.equation("g =", quantity(9.81, "m/s^2", decimal_separator=",")).move_to(0, -0.6)
scene.play([volumen.animate.write(), gravedad.animate.write()])
scene.render()
Roles
Los roles son title, subtitle, kicker, heading, body, caption, label, code y math. Sus tamaños predeterminados, en unidades de escena, son: title 0.64, subtitle 0.48, kicker 0.32, heading 0.48, body 0.40, caption 0.32, label 0.36, code 0.36 y math 0.44. La prosa usa New Computer Modern, el código JetBrains Mono y la matemática New Computer Modern Math. Las tres vienen con Gaanim, así que el texto se ve igual en cualquier equipo y en el navegador. Con un tema, kicker toma el color accent, ideal para la línea corta sobre un título:
kicker = scene.text("MISMO TERREMOTO. TRES EDIFICIOS.", role="kicker").move_to(0, 3.77)
title = scene.text("¿Cuál sufrirá más?", role="title").move_to(0, 3)El estilo se resuelve en este orden, de menor a mayor prioridad: rol y tema, TextStyle/TextFlow, argumentos directos de scene.text, estilo local de cada part y, por último, selection.fill(...) o text.fill(...) posteriores.
Partes semánticas y matemáticas
Nombra fragmentos del contenido para colorearlos, animarlos o transformarlos después sin contar caracteres.
ValueError o TypeError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
name | str | obligatorio | Nombre no vacío y único entre sus hermanos. |
content | str | TextPart | TextParts | obligatorio | Contenido anidado. |
style / estilo directo | TextStyle | argumentos con nombre | None | Tipografía local que hereda todo el subárbol. |
from gaanim import BLUE, GOLD, Scene, part
scene = Scene(frame=(16, 9), background="#0f172a")
formula = part(
"formula",
"$",
part("variable", "x", color=BLUE),
" dot 5 = ",
part("result", "25", color=GOLD),
"$",
)
text = scene.text("Resultado: ", formula).move_to(0, 0)
scene.play([text.animate.write(by="part", stagger=0.05).duration(1.1)])
scene.render()
part(). Dentro de $…$, las entradas contiguas son tokens distintos de Typst y conservan su espaciado ajustado. Usa part() para estilos locales o anidamiento. Entradas vacías, repetidas o mezclar un mapa con argumentos con nombre lanzan ValueError; nombres o valores que no son cadenas, TypeError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
mapping | Mapping[str, str] | None | None | Nombres y texto en orden. Admite nombres que no son identificadores de Python, como "tb:dist". |
content | str con nombre | obligatorio | Atajo para nombres que son identificadores: parts(mass="m") equivale a parts({"mass": "m"}). |
from gaanim import GOLD, Scene, parts
scene = Scene(frame=(16, 9), background="#0f172a")
equation = scene.text.equation(
"-",
parts(mass_left="m", gravity="g sin(theta)"),
"=",
parts(mass_right="m", length="L", acceleration="theta''"),
).move_to(0, 0)
scene.play([equation.animate.write(by="part").duration(1.2)])
scene.play([equation["gravity"].animate.indicate().duration(0.6)])
scene.play([equation["acceleration"].animate.fill(GOLD).duration(0.6)])
scene.render()
Un mapa admite nombres que los argumentos con nombre no pueden expresar:
label = scene.text(parts({"tb:dist": "d = ", "x-1": "4.2 m"}))
label["tb:dist"].fill(GOLD)Énfasis en línea
Las cadenas admiten un marcado mínimo inspirado en Typst: *negrita*, _cursiva_ y ambos anidados, *_así_*. Se compila en las mismas pasadas que TextStyle, así que funciona con ajuste de línea, selecciones, animaciones, partes y bounds().
from gaanim import GOLD, Scene
scene = Scene(frame=(16, 9), background="#0f172a")
copy = scene.text(
"Normal, _cursiva_, *negrita* y *_ambas_*.",
size=0.36,
).move_to(0, 0)
scene.play([copy.animate.write(by="word", stagger=0.05).duration(1.2)])
scene.play([copy.words[2].animate.indicate().duration(0.6)])
scene.render()
\\*y\\_escriben los delimitadores literales.- El marcado puede cruzar cadenas contiguas y fronteras de
part()sin añadir huecos. - Dentro de
$…$los marcadores son sintaxis matemática:x_1es un subíndice y*una multiplicación. - Los guiones bajos dentro de palabras (
snake_case), los marcadores repetidos (__init__) y una expresión con espacios como5 * 4quedan literales. - Un delimitador de apertura sin cierre, o un anidamiento cruzado, lanza
ValueError. - Para contenido técnico lleno de
_o*(tb:dist_comp,X1_2) pasamarkup=False: todo queda literal, barras invertidas incluidas, y$…$sigue siendo matemática.text.become(...)conserva el modo salvo que pasesmarkup=. Theme(text_markup=False)lo hace predeterminado parascene.text,badgeychip.
label = scene.text("Valores: V_e del piso 1 (tb:agriet_xy)", markup=False)Delimitadores matemáticos
$…$cambia el compositor Typst a matemáticas;$$…$$usa el mismo compositor y no crea un objeto distinto.scene.text.equation(...)añade$ … $: no escribas los delimitadores.\$escribe un dólar literal y un delimitador sin pareja lanzaValueError.- Con
role="code"el texto es literal: todo$,*,_y barra invertida se muestra tal cual, así quescene.text("$ gaanim init", role="code")oscene.text("stagger(*grows)", role="code")no necesitan escapes. - Si todo el contenido es matemático, el rol inferido es
math; si mezcla prosa y matemáticas,body. - Dentro de la matemática, cada frontera entre piezas es un espacio normal de Typst, así que no hace falta escribir
"= ". Fuera de la matemática las fronteras son exactas. - Los estilos locales de una parte quedan dentro de la misma ecuación Typst: cambiar su color, fuente o tamaño nunca añade huecos artificiales.
- La sintaxis puede cruzar fronteras:
part("x", "x"), "_1"se compila comox _1y Typst mantiene_1como subíndice.
Estilo y flujo
Aspecto de los glifos y composición de las líneas, como valores reutilizables.
opacity se aplica a todo el texto. Valores inválidos lanzan ValueError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
font / math_font | str | None | None | Familias de prosa y de matemáticas. |
fallbacks | Sequence[str] | () | Familias de respaldo en orden. |
size | float | None | None | Tamaño positivo en unidades de escena. |
weight | int | None | None | Peso numérico de 1 a 1000. |
color / stroke / stroke_width | Color | None / Color | None / float | None | None | Relleno de los glifos y contorno opcional, de stroke_width de ancho (0,02 si solo se da stroke). |
opacity | float | None | None | Opacidad de todo el texto, de 0 a 1. |
letter_spacing / word_spacing | float | None | None | Espaciado adicional no negativo, en unidades de escena. |
decorations | Sequence[str] | () | underline, strike o strikethrough. |
baseline | float | None | None | Desplazamiento de la línea base; positivo sube los glifos dentro de su línea. |
display = TextStyle(
font="New Computer Modern",
math_font="New Computer Modern Math",
size=0.42,
weight=650,
color=GOLD,
letter_spacing=0.02,
decorations=("underline",),
)
heading = scene.text("Resultados", style=display)scene.text sustituyen a este objeto.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
wrap | "auto" | False | float | "auto" | "auto" usa el ancho que ofrece Layout (o el área segura); False mantiene una línea salvo saltos explícitos; un número limita el ancho tipográfico. |
align | str | "left" | left, center, right o justify. |
line_spacing | float | 1.0 | Distancia entre líneas base, en múltiplos del tamaño de letra, como line-height en CSS: con 1.2 y letra de 0.4, las líneas quedan a 0.48. Con el valor por defecto, 1.0, las líneas quedan a un tamaño de letra, sin solaparse; súbelo para párrafos largos. Debe ser positivo. |
max_lines / overflow | int | None / str | None / "clip" | Límite de líneas y qué pasa con el resto: clip conserva las palabras que caben (una primera palabra que no cabe se corta entre caracteres), ellipsis las termina con «…» y visible dibuja todas las líneas. |
direction | str | "auto" | auto, ltr o rtl. |
hyphenate / lang | bool / str | None | False / None | Guiones de Typst y código ISO 639 en minúsculas ("es", "en"…) que elige los patrones; None usa inglés. |
from gaanim import GOLD, Scene, TextFlow
scene = Scene(frame=(16, 9), background="#0f172a", margin=0.375)
body = scene.text(
"El mismo texto se mide con el ancho que ofrece su tarjeta de Layout v2.",
role="body",
flow=TextFlow(wrap="auto", align="justify", line_spacing=1.25),
)
page = scene.layout.column(
[scene.text("Texto responsive", role="heading").fill(GOLD), body],
within="safe", width="fill", height="fill", padding=0.35, gap=0.225,
)
scene.play([page.animate.fade_in().duration(0.7)])
scene.render()
Sin flow ni text_align, el anclaje de move_to también alinea un texto con saltos de línea explícitos: Anchor.*_LEFT y TextAnchor.BASELINE_LEFT alinean a la izquierda, *_RIGHT a la derecha y los anclajes centrados centran. Un flow o text_align explícitos siempre ganan; el texto de una sola línea conserva la alineación izquierda.
note = scene.text("Fuente: ensayo 3\nEscala 1:50").move_to(7.5, -4, Anchor.BOTTOM_RIGHT)Posición
Colocación por línea base para que palabras y ecuaciones se alineen.
TextAnchor.BASELINE_CENTER: el centro horizontal visual en x y la línea base en y. Así, textos y ecuaciones con distintas ascendentes, fracciones o tamaños comparten línea base. Un bloque de varias líneas sin anclaje usa su centro visual, y un TextAnchor explícito usa la primera línea. Los anclajes geométricos (Anchor.TOP_LEFT…) se basan en los límites y no garantizan una línea base común. Un Drawable o un AnchorPoint centran el texto sobre la referencia sin seguirla. Un texto gestionado por Layout lanza LayoutOwnershipError; text.animate.move_to(...) sí lo mueve.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
x / y | float | fuente reactiva | obligatorio | Punto de destino en unidades de escena. |
anchor | Anchor | str | TextAnchor | None | None | Anclaje geométrico (un Anchor o su nombre, como "top_left"), o TextAnchor.BASELINE_LEFT, BASELINE_CENTER o BASELINE_RIGHT sobre la línea base. |
word = scene.text("Tipografía").move_to(0, 1)
equation = scene.text.equation("frac(x_1^2, y_2) = 1").move_to(0, -0.5, TextAnchor.BASELINE_CENTER)
left = scene.text("alineado a la izquierda").move_to(-4, -2, TextAnchor.BASELINE_LEFT)
corner = scene.text("esquina").move_to(-7, 4, Anchor.TOP_LEFT)Text hereda todos los métodos de Drawable y conserva su tipo al encadenarlos.
Drawable, redefinidos para devolver el Text y conservar el anclaje tipográfico de un move_to posterior.title = scene.text("Gaanim", role="title").glow(BLUE).move_to(0, 1, TextAnchor.BASELINE_CENTER)Selecciones
Señalan grafemas, palabras, líneas o partes de un texto para darles estilo o animarlas sin separarlas del texto.
Text. Un rango de grafemas, palabras o líneas selecciona el texto desde la primera hasta la última unidad, puntuación incluida. Cadenas que no son parte ni texto lanzan KeyError; índices fuera de rango, IndexError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
name | str | obligatorio | Una parte de primer nivel; sigue indexando para bajar por partes anidadas. Una cadena que no es parte selecciona cada aparición literal de ese texto, sin distinguir mayúsculas ni espacios. |
index / slice | int | slice | obligatorio | Grafemas renderizados; admite índices negativos y rangos contiguos con paso 1. |
from gaanim import BLUE, GOLD, Scene, part
scene = Scene(frame=(16, 9), background="#0f172a")
copy = scene.text(
"La ", part("concept", "energía"), " depende de ",
part("formula", "$", part("mass", "m"), " c^2$"),
).move_to(0, 0)
copy["concept"].fill(GOLD)
copy["formula"]["mass"].fill(BLUE)
scene.play([copy.animate.write(by="word").duration(1.0)])
scene.play(copy.words[1].animate.pulse().duration(0.6))
scene.play(copy["formula"]["mass"].animate.focus().duration(0.6))
scene.render()
TextQuery) sobre las unidades del texto. words sigue los límites de palabra de Unicode, así que la puntuación no es una palabra: "uno dos, tres" tiene tres y words[1:3] selecciona dos, tres. lines sigue los saltos \n explícitos, no las líneas creadas por el ajuste. parts recorre el árbol semántico en profundidad. len(query) cuenta las unidades y "mass" in text.parts comprueba si existe una parte, también con rutas anidadas como "formula.mass".copy = scene.text("uno dos, tres")
tail = copy.words[1:3]
if "mass" in copy.parts:
copy["mass"].fill(GOLD)formula = scene.text.equation("E = ", part("mass", "m"), " c^2")
formula["mass"].fill(GOLD)animate.marker. Aparecen en el cursor (el inicio de la escena durante la declaración) y no siguen movimientos posteriores del texto.quote = scene.text("Lo que no se mide no se puede mejorar")
quote.words[0:2].marker(opacity=0.35)Drawable.annotate alrededor de una parte del texto. Siguen los glifos aunque el texto se mueva.Annotations — Constructor de anotaciones alrededor de los glifos seleccionados.eq = scene.text.equation("E = ", part("rhs", "m c^2"))
scene.play([eq["rhs"].annotate.underline(color=RED, seed=1).animate.create()])TextSelectionAnimation limitado a los glifos seleccionados. Acepta fill y opacity, combinables (animate.fill(RED).opacity(0.6)); los destinos de transformación, escala, rotación, material o trazo lanzan TypeError. Cada método devuelve un Anim para scene.play.eq = scene.text("$E = ", part("mass", "m"), " c^2$")
scene.play([eq.animate.write().duration(0.8), eq["mass"].animate.fill(RED).opacity(0.7).duration(0.8)])indicate hace crecer los glifos 1,2 veces y los tiñe del "accent" del tema (o de su "foreground" si ya tienen el color del acento); pulse los hace crecer dos veces seguidas, como un latido, sin teñirlos; wiggle los sacude de lado a lado; wave levanta cada glifo un tercio de su altura y lo baja, uno tras otro; highlight los tiñe del acento y los devuelve a su color sin moverlos; focus atenúa el resto del texto. No cambian la medición del texto ni provocan reflow.eq = scene.text.equation("F = ", part("mass", "m"), " a")
scene.play([eq["mass"].animate.highlight().duration(0.8)])
scene.play([eq["mass"].animate.wave().duration(0.8)])"fade", "wipe" (traza y luego rellena) o "from_below" (fundido con una subida corta), para que un término entre en una ecuación ya visible. Los glifos seleccionados siguen ocultos hasta el reveal, también mientras se escribe el resto. Un estilo desconocido lanza ValueError. Para revelar un texto completo por líneas o palabras, usa Anim.reveal (ver Animaciones).eq = scene.text.equation("a^2 + b^2 = ", part("rhs", "c^2"))
scene.play([eq["rhs"].animate.reveal("from_below").duration(0.6)])above=True) y hace aparecer label. La llave y la etiqueta son objetos nuevos, del color de los glifos, que permanecen en pantalla.eq = scene.text.equation("E = ", part("mass", "m"), " c^2")
scene.play([eq["mass"].animate.brace("masa")])label a offset del centro de la selección con una línea guía. La línea y la etiqueta permanecen; un desplazamiento no finito lanza ValueError.eq = scene.text.equation("E = m ", part("c", "c"), "^2")
scene.play([eq["c"].animate.annotate("velocidad de la luz", offset=(0, 0.8))])padding y crece desde la izquierda; las líneas se suceden y se reparten la duración según su longitud. Las bandas quedan detrás de los glifos pero delante de lo creado antes del texto, y permanecen. Un blend desconocido, un skew no finito, una opacidad fuera de [0, 1] o un padding negativo lanzan ValueError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
color | Color | None | None | Color del rotulador; por defecto un amarillo cuyo alfa se multiplica por opacity. |
skew | float | 0.05 | Inclinación de cada banda en radianes (positivo sube hacia la derecha), limitada para cubrir líneas largas. |
blend | str | "normal" | "normal" pinta la banda encima de lo que tiene debajo; "multiply" la multiplica con el fondo o la tarjeta, como tinta sobre papel (sobre negro no se ve). En ambos casos va detrás de los glifos. |
opacity | float | 0.45 | De 0 a 1. |
padding | float | None | None | Margen en unidades del mundo; None usa el 10 % de la altura de línea. |
from gaanim import Scene
scene = Scene(frame=(16, 9), background="#f8f5ee")
quote = scene.text("Lo que no se mide no se puede mejorar", color="#1f2937").move_to(0, 0)
scene.play([quote.words[4:9].animate.marker(skew=0.04).duration(1.2)])
scene.render()
morph_to sustituye la selección por el destino y copy_to conserva el origen mientras una copia viaja hasta el destino.energy = scene.text("$E = ", part("mass", "m"), " c^2$").move_to(0, 1)
momentum = scene.text("$p = ", part("mass", "m"), " v$").move_to(0, -1)
scene.play([energy["mass"].animate.copy_to(momentum["mass"]).duration(0.8)])Las animaciones de selección resuelven el código matemático escrito a los glifos que emitió Typst. Primero se busca el fragmento literal; si no aparece, el analizador matemático de Typst convierte nombres de símbolo (theta, sum, arrow.r.long), atajos (<=) y primas (theta'') al mismo Unicode que la ecuación renderizada.
Animación de texto completo
La escritura por grupos, los revelados con máscara, el desenfoque de entrada, el tracking, la máquina de escribir y el efecto de decodificación están en Animaciones (Anim.write, Anim.reveal, Anim.conceal, Anim.blur_in, Anim.tracking, Anim.typewriter, Anim.scramble). Aquí están el animador de rango, que los generaliza, y el tracking inmediato.
reveal, conceal y blur_in son preajustes de este animador. Nombres inválidos lanzan ValueError; sobre un objeto que no es Text, TypeError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
by | str | "grapheme" | Unidad: grapheme, word, line (explícita) o part. La puntuación se une a su vecina. |
shape | str | "smooth" | Perfil del selector. square, ramp, smooth, ease_in y ease_out llevan cada unidad del estado «fuera» al reposo (revelado); triangle y round suben al estado «fuera» y vuelven (ola). |
order | str | "forward" | Qué unidad alcanza primero el rango: forward, reverse, center o random. |
seed | int | 0 | Fija la permutación de order="random". |
from gaanim import Scene, parallel
scene = Scene(frame=(16, 9), background="#0f172a")
title = scene.text("Tipografía cinética", role="title").move_to(0, 0)
wave = title.animator(by="grapheme", shape="smooth", order="forward", seed=0)
wave.set(offset=(0, -0.4), opacity=0.0, scale=0.6, rotation=0.2) # estado "fuera"
scene.play([wave.animate.sweep().duration(1.2)]) # recorre el rango 0 → 1
bob = title.animator(by="word", shape="round").set(offset=(0, 0.3))
scene.play(parallel(bob.animate.sweep().duration(1.0))) # una ola que pasa
scene.render()sweep(). Valores inválidos lanzan ValueError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
offset | (float, float) | None | None | Desplazamiento en unidades de escena. |
opacity | float | None | None | Opacidad absoluta de 0 a 1. |
scale / rotation | float | None | None | Alrededor del centro de cada unidad; la rotación en radianes, menos de media vuelta. |
blur / tracking | float | None | None | Sigma gaussiana y espacio extra entre glifos, en unidades de escena. |
color | Color | None | None | Relleno sólido. |
animator.animate.sweep(...). Devuelve un Anim que mueve el rango de start a end: 0 está antes de la primera unidad y 1 después de la última, así que sweep(1, 0) lo recorre hacia atrás. stagger es el retraso en segundos entre unidades (None lo adapta) y easing suaviza cada unidad. Funciona en scene.play, parallel, sequence y stagger; los barridos de entrada mantienen su primer fotograma hasta empezar.value unidades de escena extra entre glifos vecinos, sin recomponer el párrafo: cada fila crece desde su borde izquierdo, su centro o su borde derecho según la alineación. 0 restaura el espaciado. Anímalo con animate.tracking(value).title = scene.text("Tipografía", role="title").tracking(0.4)
scene.play([title.animate.tracking(0.0).duration(1.2)])Texto sobre una trayectoria
reverse=True lo pone por fuera. En una trayectoria cerrada el texto da la vuelta; en una abierta, los glifos que pasan un extremo siguen en línea recta. La trayectoria se lee en coordenadas de escena donde está path en ese momento y pasa a ser del texto: moverlo la arrastra, y cambiar path después no mueve los glifos. path sigue siendo un drawable normal. Acepta los argumentos de estilo de scene.text. Un align desconocido, un offset no finito, un path de otra escena o un grupo (que no tiene trayectoria propia) y un contenido sin caracteres visibles lanzan ValueError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
content | TextContent | obligatorio | Una línea de texto; nunca se parte. |
path | Drawable | obligatorio | Drawable cuya trayectoria siguen los glifos. |
align | str | "start" | "start", "center" o "end": el texto empieza, se centra o termina en la trayectoria. |
orient | bool | True | Gira cada glifo con la dirección de la trayectoria; con False quedan derechos. |
reverse | bool | False | Recorre la trayectoria del final al principio. |
offset | float | 0.0 | Desplazamiento inicial, como fracción de la longitud de la trayectoria. |
Text — Texto cuyos glifos siguen la trayectoria.from gaanim import *
scene = Scene(frame=(16, 9))
circle = scene.geometry.circle(2.4).no_fill().stroke(GRAY, 0.03)
ring = scene.text.on_path("GAANIM · MOTION · DESIGN · ", circle, reverse=True, size=0.5)
scene.play([ring.animate.path_offset(1.0).duration(4).easing(Easing.LINEAR)])
scene.render()
on_path por su trayectoria, como fracción de su longitud. Declarado antes del primer play es el desplazamiento inicial; después corta en el cursor. Anímalo con animate.path_offset(value).wave = scene.geometry.path([("move", [(-6, 0)]), ("cubic", [(-2, 3), (2, -3), (6, 0)])])
label = scene.text.on_path("una ola de texto", wave, align="center").path_offset(-0.2)
scene.play([label.animate.path_offset(0.2).duration(2)])Transiciones estructurales
text.animate.transform_to(target) (ver Anim.transform_to en Animaciones) empareja las partes semánticas de dos textos: los términos compartidos viajan a su nuevo sitio y el resto aparece o desaparece. Cada glifo conserva su color, también los de part(), y al terminar queda visible el destino. La transición usa la misma duración para el texto y para el reflow de su Layout. Un destino de otra escena lanza ValueError. Entre textos gestionados por Layouts distintos (por ejemplo, celdas de dos matrices) la transformación es un morph de forma única: el origen conserva su identidad y adopta el contorno, el color y la línea base del destino.
from gaanim import GOLD, Scene, part
scene = Scene(frame=(16, 9), background="#0f172a")
before = scene.text("$x + ", part("obsolete", "3"), " = 7$").move_to(0, 0)
after = scene.text("$x = ", part("result", "4", color=GOLD), "$").move_to(0, 0)
scene.play([before.animate.write().duration(0.8)])
scene.play([before["obsolete"].animate.cancel().duration(0.5)])
scene.play([before.animate.transform_to(after).duration(0.8)])
scene.render()
Cambiar el contenido
Sustituye el contenido de un texto sin crear otro objeto.
Text, incrementa su versión estructural y pide reflow a su Layout. Si el texto es libre y ya se mostró, el nuevo contenido lo reemplaza en el cursor, colocado como se declaró el texto (move_to, place), y las animaciones posteriores del Text actúan sobre él. Es una llamada inmediata de autoría, no un Anim: no la pongas en scene.play. markup=None conserva el modo de marcado actual. Contenido o delimitadores inválidos lanzan ValueError.copy = scene.text("Calculando…")
scene.wait(0.5)
copy.become("Resultado: ", part("value", "$42$", color=GOLD))Documentos Typst y código
Para lo que no cabe en un Text: documentos Typst completos y bloques de código. Para medir cualquier objeto, texto incluido, usa bounds() (ver Drawable).
@preview/…) como un Drawable. Conserva sus proporciones y se escala para que su texto de 11 pt mida lo mismo que el rol body. No ofrece las selecciones de Text. Su texto toma el color del rol body del tema, como scene.text, y también los trazos que Typst dibuja en negro por defecto (tablas, rect, line, formas de CeTZ). El documento empieza con theme, un diccionario con los colores del tema por nombre (theme.accent, theme.foreground, theme.panel, theme.rule…): los paquetes pensados para papel blanco eligen ellos mismos el negro y el blanco, así que pásales estos colores por sus opciones (ver Colores del tema en Typst). Los trazos que continúan uno a otro, como una curva que un paquete de gráficas dibuja muestra a muestra, son una sola pieza para write. Una fuente vacía lanza ValueError y un archivo ilegible, RuntimeError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
source | str | os.PathLike | obligatorio | Marcado Typst en línea o la ruta de un archivo .typ (relativa a la carpeta de assets). |
width | str | float | int | None | None | Ancho de página Typst antes de escalar: "16cm", "800pt" o un número en puntos. Sin él, la página se ajusta al contenido: #align(center) centra dentro del contenido y los párrafos solo cortan línea donde la fuente lo indica. Pásalo para que un párrafo se ajuste o para columnas 1fr. |
from gaanim import Scene
scene = Scene(frame=(16, 9), background="#0f172a")
tbl = scene.text.typst('#table(columns: 2, [*Método*], [*Error*], [Base], [0.18], [GPU], [0.04])')
scene.play([tbl.animate.fade_in().duration(0.6)])
scene.render()
También carga archivos:
from pathlib import Path
title = scene.text.typst(Path("assets/title.typ"))Colores del tema en Typst
Cada documento empieza con theme, los colores del tema por nombre, los mismos que scene.canvas.color(...). El texto ya toma el color del tema; úsalos para lo que el documento pinta por su cuenta:
from gaanim import Scene
scene = Scene()
table = scene.text.typst("""
#table(
columns: 3,
stroke: 0.6pt + theme.rule,
fill: (x, y) => if y == 0 { theme.panel },
[*x*], [*y*], [*x + y*],
[1], [2], [#text(fill: theme.accent)[3]],
)
""")
scene.play([table.animate.write()])
scene.render()
Los paquetes de Typst Universe están pensados para papel blanco y eligen ellos mismos el negro y el blanco de ejes, marcas y fondos de etiqueta. Dales los colores del tema por sus opciones de estilo, como en examples/text/typst_packages.py:
#import "@preview/simple-plot:1.0.0": plot
#plot(
style: (
axis: (stroke: theme.rule + 0.8pt, arrow: (symbol: "stealth", fill: theme.rule)),
ticks: (stroke: theme.rule + 0.6pt, label-fill: theme.muted, label-bg: none),
labels: (fill: theme.foreground, bg: none),
),
(fn: x => x * x, stroke: theme.accent + 1.2pt),
)dracula, nord, solarized-*, gruvbox-dark, tokyo-night, catppuccin-*) usan su esquema original, y technical, presentation, paper y los temas propios los derivan de la paleta (palabras clave en accent, comentarios en muted). Los tokens code, code_keyword, code_string, code_number, code_comment, code_function y code_type de Theme(colors=...) los sustituyen. Sobre un background en el que los colores del tema no se leen (un panel oscuro en un tema claro, o al revés), el texto y cada token se ajustan hasta leerse; los que ya se leen no cambian. Cada carácter sigue siendo un glifo propio, coloreado o no. Una fuente vacía, un lenguaje con caracteres distintos de letras y dígitos ASCII, - o _, una font vacía o un tamaño no positivo lanzan ValueError; un theme inexistente lanza FileNotFoundError, y uno que no es un tmTheme, ValueError.| Nombre | Tipo | Predeterminado | Descripción |
|---|---|---|---|
source | str | obligatorio | El código. Los tabuladores y los saltos de línea se conservan. |
language | str | "text" | Nombre o extensión del lenguaje: "python", "rust", "js", "typst", "json"… Uno desconocido, como "text", queda sin resaltar. |
width | float | 7.6 | Ancho del panel. |
height | float | 3.0 | Alto del panel. |
font_size | float | 0.2 | Tamaño base del texto; el código se dibuja al 80 %. |
background | ColorLike | None | None | Fondo del panel; por defecto, el panel del tema (o el de theme). |
color | ColorLike | None | None | Color del texto que no se resalta: nombres, operadores y puntuación; por defecto, el foreground del tema (o el de theme), o su background si el foreground no se lee sobre el fondo del bloque. |
accent | ColorLike | None | None | Color del borde, la regla y el nombre del lenguaje; por defecto, el accent del tema, aclarado u oscurecido si no se lee sobre un background propio. |
theme | str | os.PathLike | None | None | Archivo .tmTheme (el formato de TextMate, Sublime Text y bat) con los colores de los tokens, resuelto como los demás assets. |
highlight | bool | True | Con False, todo el código va en color. |
font | str | None | None | Familia monoespaciada. Sin ella, o si no está instalada, JetBrains Mono, incluida en Gaanim, para que el bloque se vea igual en cualquier equipo. |
from gaanim import Scene
scene = Scene(frame=(16, 9))
snippet = scene.text.code(
"def area(radius):\n"
" # circle area\n"
" return 3.14159 * radius ** 2",
language="python",
width=8,
height=2.4,
font_size=0.3,
)
scene.play([snippet.animate.fade_in().duration(0.5)])
scene.render()
Con otro esquema, sin resaltar o con otra fuente:
papel = scene.text.code("let x = 42; // respuesta", language="rust", theme="assets/codigo.tmTheme")
plain = scene.text.code("x = 42", language="python", highlight=False)
fira = scene.text.code("x = 42", language="python", font="Fira Code")Integración con Layout
Un Text libre y el mismo Text dentro de una fila, columna, grid o stack usan el mismo medidor. wrap="auto" lo hace sensible al ancho ofrecido; un ajuste numérico queda además limitado por ese ancho. Un texto que cabe conserva sus líneas naturales, y uno que se ajusta mantiene las líneas con las que se midió: un contenedor que abraza su contenido ("hug") nunca lo reparte en más líneas. Los cambios de métrica, become y animate.transform_to invalidan la medición y piden reflow; los énfasis transitorios (indicate, pulse, wiggle, wave, highlight, focus) no.
Una caja decide dónde descansa un Text hijo: colocarlo de forma inmediata con move_to, shift_by o next_to lanza LayoutOwnershipError; ajusta su text.item(...) o las propiedades de la caja. Todas las animaciones de .animate, de posición incluidas, siguen disponibles y parten de ese sitio. Consulta Layout.
Errores y casos límite
TypeError: contenido que no esstrniTextPart, o claves de consulta del tipo equivocado.ValueError: contenido vacío, matemáticas desequilibradas, partes hermanas repetidas, rol, estilo o flujo inválidos, agrupación u orden inválidos, rangos con paso distinto de 1.KeyError: una cadena que no es parte semántica ni texto delText.IndexError: índice fuera de rango o rango vacío.LayoutOwnershipError: posicionar a mano un texto gestionado, o una transición entre escenas o propietarios incompatibles.