Google Summer of Code 2026 · Synfig Studio
A Text Group previously moved as one rigid block. This project gives every glyph inside it independent rotation, scale, and timing — and a mechanism for one glyph's animation to be shared with the rest, delayed, reordered, or reshuffled by a repeatable seed.
Each letter is its own layer with rotation, scale, and animation offset.
One glyph's animation can be reused across the whole word.
Choreograph forward, reverse, or random sequencing.
A repeatable seed reshuffles the sequence without touching the animation.
A dedicated parameter selector for choosing what to share.
Sharing configuration is part of the document, not a session setting.
Sharing and unsharing route through Synfig's action system.
status — reviewed September 2026
The feature is implemented as an end-to-end pipeline within Synfig. Layer_TextGroup converts shaped text into reusable Layer_GlyphShape layers, maintains glyph identity and layout, and manages shared animation relationships. AnimShare and ValueNode_AnimShareList store sharing configuration, while ValueNode_TimeOffset provides the per-glyph temporal offset used for staggered playback. Studio widgets and Synfig actions provide the editing and undo/redo interface, and the complete state is serialized with the canvas.
Primary repository — XYuktaaa/synfig, branch GSoC26-text-animation, pull request #3742.
Types introduced or modified: Layer_TextGroup, Layer_GlyphShape, AnimShare, ValueNode_AnimShareList, and ValueNode_TimeOffset.
for users
A Text Group turns typed text into a managed set of glyph layers, each one still under the Text Group's direction. What follows is how to give those glyphs their own motion, and how to hand that motion to one another.
One text object becomes a managed collection of glyph layers, while a single Text Group parent still controls layout, font, and color. The glyphs live inside its subcanvas as implementation children — you don't create them directly, and they aren't meant to be treated as ordinary top‑level layers.
one shared animation, five glyphs, each sampling it a little later than the last
A source animation lives on one glyph's parameter. When you share it, the Text Group resolves that animated value node and connects it — through a per‑glyph time wrapper — to the same parameter on other glyphs. Delay is how far apart each glyph samples it; order is who goes first. A positive delay should always mean a later glyph starts later, not earlier — a wave forming rather than every glyph twitching at once.
Proceeds through the glyph sequence, first to last.
Starts from the last glyph and moves backward.
Uses the Text Group's stagger seed for a repeatable shuffle.
The seed changes the random ordering without touching the animation itself — a way to try several controlled variations of the same effect. Change the seed, and the order reshuffles; keep everything else the same.
In text Group animation, any glyph parameter that supports animation through waypoints can participate in a shared animation relationship. This includes properties such as Scale, Rotation, Opacity, Color, Z Depth, and other animatable parameters exposed by the glyph layer.
When a parameter is shared, one glyph provides the animation and the participating glyphs evaluate that animation through their own animation timing. The shared parameter therefore moves together as a coordinated animation, while each glyph can still have its own transform and timing context.
The practical rule is simple: if a glyph parameter is animatable and can be represented by the value-node system, it can be considered a candidate for sharing. The exact parameters available depend on the glyph layer and the parameter editor.
Three practical starting points cover common reveal, pulse, and randomized-sequence effects.
Letters appear left to right, like a typewriter finding its rhythm.
A ripple moves through the word, rather than a single beat.
Unpredictable, but repeatable — change the seed to reshuffle it entirely.
Adding, editing, or removing a sharing entry is handled through Synfig's action system. These operations therefore participate in Synfig's normal undo/redo mechanism, including creation and removal of the first sharing entry in a Text Group.
When a sharing relationship is removed, glyphs that already had their own animation before sharing can have that original animation restored. A glyph that had no animation before sharing has no independent animation to restore; its parameter state is instead reconstructed from the current sharing state at the time of the operation.
In other words, an existing per-glyph animation has something concrete to restore, while a previously static glyph must derive its state from the point in time at which the sharing relationship is removed.
Stagger adds time to the sequence. If the source animation lasts T and there are N glyphs with a per‑step delay of d, the last glyph can need roughly T + (N−1)d to finish. Leave enough timeline after the source animation for the whole staggered sequence to land — the final letter is often still moving after the first one has stopped.
how it fits together
Five high-level views explain the system from authoring through persistence; the implementation workflows below trace the concrete paths through Layer_TextGroup.
How an User's action becomes a structural change.
Turning a string into shapes.
One glyph's motion, sampled by many.
What runs when something changes.
What survives a save and reload.
implementation workflows
These are the four paths that matter most when reading or maintaining Layer_TextGroup. They complement the high-level cards above rather than replacing them.
sync_glyphs)Text edits rebuild only what has to change.
When text or font parameters change, sync_glyphs() loads the font, shapes the text (using HarfBuzz when available), converts each outline to contours, and computes glyph placement. The important preservation step is an LCS diff between old and new glyph identities: matching Layer_GlyphShape children are reused, so attached animation is not discarded just because the text changed. Glyph descriptions carry an identity key of the form cluster_<c>_glyph_<g>, which can be parsed back during synchronization.
One source animation becomes many time-shifted samples.
The Studio editor records an AnimShare entry in the share_animations list. Each entry contains the selected glyph parameter, delay, and order. The source glyph's animated node is resolved and reused, while each participating glyph receives its own ValueNode_TimeOffset wrapper around that shared node.
For RANDOM order, rebuild_stagger_permutation() ranks glyphs from a hash of (cluster, glyph_index) mixed with a per-layer salt. Sorting those ranks produces stagger_perm_, so the order looks random but remains reproducible for the document.
The relationship survives closing and reopening the document.
Sharing metadata is stored as the structural ValueNode_AnimShareList. Each entry records the target parameter, delay, and order. On load or parameter change, the Text Group reconstructs sharing from the raw list rather than from a time-evaluated list, then reattaches the current set of relationships.
set_time_vfunc)Staggering is evaluated as time, not duplicated keyframes.
set_time_vfunc() first applies the Text Group's own time dilation and offset, then propagates that base_time to glyph layers in the sub-canvas. The per-glyph ValueNode_TimeOffset wrappers shift sampling by the stagger delay, which is why the implementation can reuse one underlying animation rather than copying its keyframes for every glyph.
for maintainers
Ownership, method-level responsibility, two real bugs and their fixes, and what to check before calling any of this done.
Layer_TextGroup owns text‑level state and the glyph subcanvas — when text or layout changes require synchronization, sharing bookkeeping, and applying shared relationships. Layer_GlyphShape owns the glyph's contour data and its per‑glyph parameters; it's an implementation layer, not meant for direct top‑level use. AnimShare / ValueNode_AnimShareList own the persistent sharing configuration. ValueNode_TimeOffset owns temporal sampling. Studio owns UI and actions.
on_canvas_set()Initializes or reconnects the share_animations parameter and rebuilds sharing state when the layer attaches to a canvas.
set_param()Separates structural text/layout changes (full resync) from lighter ones — kerning triggers a glyph sync, color updates child glyphs without rebuilding geometry, and a stagger‑seed change rebuilds the permutation.
sync_glyphs()The central structural synchronization path — where text and layout changes become actual glyph layers.
get_shareable_params()Reads the source glyph's dynamic parameter list — the basis for the Studio selector and for validating a requested share.
share_param()Validates a parameter, resolves and exports its source node, creates a SharedEntry, and attaches wrappers.
unshare_param()Removes the bookkeeping entry before detaching glyph wiring — ordering matters, since detach logic can re‑trigger attachment.
resolve_and_export_node()Reuses an already‑wired shared node when one exists; otherwise exports the source glyph's node into the subcanvas under a persistent ID.
attach_shared_entries()Applies every sharing entry to every glyph — computing the ordered time offset, reusing cached wrappers, and preserving pre‑share nodes.
find_wired_shared_node()Finds a TimeOffset‑wrapped source node already wired to a glyph — used when reconstructing sharing from a loaded document.
apply_shared_entries_from_items()Converts serialized AnimShare items into internal bookkeeping, reusing old caches where possible and detaching entries that disappeared.
The early prototype cleared the entire glyph subcanvas on every text change — destroying any hand‑made animation or connection in the process. The current model tracks existing glyphs and reuses them instead. Before a parameter is replaced with a TimeOffset wrapper, its original node is captured in pre_share_nodes, so unsharing can restore it. If the shared source node itself disappears mid‑edit, the affected entry is marked invalid rather than silently left dangling.
Widget_ValueBase recognizes the anim_share_param hint and delegates to Widget_Sublayer, which builds the selector from the surrounding layer context. CanvasTreeStore was adjusted to preserve parent ValueDesc context so that selector can find the Text Group and its glyph parameters. Sharing edits route through Synfig's action system rather than direct mutation — direct mutation alone doesn't produce a coherent undo step. Unshare ordering matters here too: remove the bookkeeping entry first, or a reattach can immediately recreate what was just removed.
What happened — Inserting the first AnimShare entry while Animate mode was active crashed. The smart dynamic‑list insertion path prepared an ActivepointSetOff against a list that was still empty, because the insertion action hadn't executed yet — index 0 pointed at nothing.
The fix — AnimShareList membership is structural, so it bypasses the normal animated‑list activepoint handling. ValueNodeDynamicListInsertSmart gained a narrow special path that performs the ordinary structural insertion and returns before the generic activepoint logic runs — deliberately narrow, since other dynamic‑list types still need the normal animated behavior.
What happened — An earlier implementation reconstructed sharing by evaluating the dynamic list at the current animation time. A structural sharing entry could vanish simply because the current frame didn't consider it "active."
The fix — Sharing membership is structural, not time‑dependent — reconstruction now reads the raw AnimShareList entries directly instead of asking the animated list for its time‑dependent active subset. New entries carry empty timing information on creation, so a share behaves identically whether the user is on frame 0 or frame 400 when the list is edited.
| Area | What to check |
|---|---|
| Functional | Create Text Group → glyphs → animate one glyph → share Scale → all glyphs follow, with delay. |
| Ordering | Forward, Reverse, Random — changing the seed changes the order. |
| Timing | A positive delay produces a later start, never an earlier one. |
| Frame independence | Share created at frame 0 and at a non‑zero frame both persist and behave identically. |
| Serialization | Save/reopen preserves Text Group, glyphs, list, delay/order, node IDs, and the resulting animation. |
| Undo/redo | Add/remove/edit sharing entries across multiple undo/redo cycles — especially the first entry. |
| Editing | Scale editable in canvas and value editor; Animation Offset through its Vector/Composite path. |
| Text mutation | Changing text after animation exists preserves glyph data while updating count/layout. |
| Source deletion | Deleting/invalidating the shared source node leaves no dangling live wrapper. |
how it got here
A milestone timeline showing how the feature evolved from its foundation to the final Studio workflow.
Layer_TextGroup was introduced to decompose text into individually renderable glyph shapes using the existing FreeType pipeline.
Layout controls, per-glyph offsets, stagger timing, and the initial animation primitives were added.
Text shaping was upgraded with HarfBuzz, including glyph advances, offsets, and bidirectional text support.
Shared animation evolved from an initial broadcast prototype into a structured model with glyph identity preservation and configurable stagger ordering.
ValueNode_TimeOffset, SharedEntry, and the typed AnimShareList became the core of shared animation and staggered timing.
Undoable sharing, frame-independent reconstruction, Studio editing, serialization, and final UI and cleanup work completed the workflow.
the storyboard
One continuous Spider-Man title animation, built progressively. Each stage shows the resulting animation together with the exact keyframes and sharing configuration used to create it.
The first pass introduces a shared scale animation to the individual glyphs. The animation starts small, overshoots, settles, and finishes with a short pulse later in the sequence.
Frame 0 → (0.10, 0.10)
Frame 6 → (1.37, 1.37)
Frame 12 → (0.90, 0.90)
Frame 18 → (1.07, 1.07)
Frame 24 → (1.00, 1.00)
Frame 48 → (1.00, 1.00)
Frame 52 → (1.06, 1.06)
Frame 56 → (0.98, 0.98)
Frame 60 → (1.00, 1.00)
The same animation is now shared through the animation offset. Each glyph evaluates the source animation at a different time, producing a controlled temporal stagger.
Frame 0 → (0, -180)
Frame 8 → (0, 25)
Frame 16 → (0, -8)
Frame 24 → (0, 0)
Rotation is added to the existing scale and animation-offset motion. It uses its own stagger delay and ordering, so the movement of each parameter can follow a different sequence.
Frame 0 → -180°
Frame 8 → 25°
Frame 16 → -6°
Frame 24 → 0°
The final sequence combines the three independently configured animation parameters into a single per-character title animation.
what is happening underneath
The glyphs do not receive independent copies of each animation. Shared parameters reference the same underlying animation while per-glyph time offsets determine when each character evaluates it.
Each shared parameter can have its own delay and ordering. In this example, Scale uses Center Out ordering, Animation Offset uses Forward ordering, and Rotation uses Reverse ordering.
PR #3742 · github.com/synfig/synfig
honest accounting
Claims this documentation deliberately avoids making until they have been verified.
source & further reading