Threading and overset
How one story flows across several frames in a chain, and why text that doesn't fit at the end is overset and dropped from the rendered page.
Threading is how one story flows across a chain of frames; overset is the text that still doesn't fit at the end.
In short: A story holds text and a frame shows it, so when a story is longer
than its frame the text has to go somewhere. IDML's answer is threading: frames
link in a chain via NextTextFrame / PreviousTextFrame, and the story is composed
once then poured into them in order. Text that still doesn't fit at the end of the
chain is overset — a layout outcome with no attribute of its own. This page explains
how the chain is built and what our renderer does with the leftover lines.
A story holds text; a frame shows it. When a story is longer than its frame, the text has to go somewhere — IDML's answer is threading: the story continues in the next frame in a chain. The text that still doesn't fit at the end of the chain is overset.
A story is independent of its frames
The text lives in one Stories/Story_*.xml part regardless of how many frames
display it. Frames don't contain text; they reference a story through their
ParentStory attribute and contribute a box for it to flow into. So "one story in
three frames" is one Story part and three TextFrame elements that all name the
same ParentStory. The story is composed once, as a single continuous column,
then poured into the frames in chain order.
The chain: NextTextFrame and PreviousTextFrame
Each frame in a thread points forward to the next with NextTextFrame (the Self
id of the following frame) and back with PreviousTextFrame. A frame at the head
of the chain has no incoming link; a frame at the tail has NextTextFrame="n"
(the IDML "none" sentinel).
To build the chain for a story, our scene layer collects every frame whose
ParentStory matches, picks the head — the one frame that is not named as
any other frame's NextTextFrame — and follows NextTextFrame links until they
run out. (PreviousTextFrame is the document's back-pointer; the walk itself only
needs the forward links, and is bounded so a malformed cyclic document can't hang.)
This is Document::frame_chain in paged-scene/src/lib.rs:253. The forward link
is read off each frame's NextTextFrame attribute in paged-parse/src/spread.rs:1714.
The example below is exactly that: one story ustory, two frames. Frame A is too
short to hold the whole story, so it names frame B as its NextTextFrame; frame B
names frame A as its PreviousTextFrame; both carry ParentStory="ustory".
One story, two frames: frame A’s NextTextFrame points at frame B, which points back.
Spreads/Spread_uspread.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<idPkg:Spread xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging" DOMVersion="20.0">
<Spread Self="uspread" PageCount="1" BindingLocation="0" ShowMasterItems="true" AllowPageShuffle="true" ItemTransform="1 0 0 1 0 0">
<Page Self="upage" Name="1" AppliedMaster="umaster" ItemTransform="1 0 0 1 0 0" GeometricBounds="0 0 841.89 595.276" MasterPageTransform="1 0 0 1 0 0"/>
<TextFrame Self="uframeA" ParentStory="ustory" PreviousTextFrame="n" NextTextFrame="uframeB" ContentType="TextType" AppliedObjectStyle="ObjectStyle/$ID/[None]" Visible="true" Name="$ID/" ItemTransform="1 0 0 1 57.638 145.8237" FillColor="Swatch/None" StrokeColor="Swatch/None" StrokeWeight="0">
<Properties>
<PathGeometry>
<GeometryPathType PathOpen="false">
<PathPointArray>
<PathPointType Anchor="0 0" LeftDirection="0 0" RightDirection="0 0"/>
<PathPointType Anchor="0 60" LeftDirection="0 60" RightDirection="0 60"/>
<PathPointType Anchor="220 60" LeftDirection="220 60" RightDirection="220 60"/>
<PathPointType Anchor="220 0" LeftDirection="220 0" RightDirection="220 0"/>
</PathPointArray>
</GeometryPathType>
</PathGeometry>
</Properties>
</TextFrame>
<TextFrame Self="uframeB" ParentStory="ustory" PreviousTextFrame="uframeA" NextTextFrame="n" ContentType="TextType" AppliedObjectStyle="ObjectStyle/$ID/[None]" Visible="true" Name="$ID/" ItemTransform="1 0 0 1 317.638 145.8237" FillColor="Swatch/None" StrokeColor="Swatch/None" StrokeWeight="0">
<Properties>
<PathGeometry>
<GeometryPathType PathOpen="false">
<PathPointArray>
<PathPointType Anchor="0 0" LeftDirection="0 0" RightDirection="0 0"/>
<PathPointType Anchor="0 400" LeftDirection="0 400" RightDirection="0 400"/>
<PathPointType Anchor="220 400" LeftDirection="220 400" RightDirection="220 400"/>
<PathPointType Anchor="220 0" LeftDirection="220 0" RightDirection="220 0"/>
</PathPointArray>
</GeometryPathType>
</PathGeometry>
</Properties>
</TextFrame>
</Spread>
</idPkg:Spread>
Overset: the text that doesn't fit
When the composed story is taller than the chain can hold, the leftover lines at the bottom of the last frame are overset. In InDesign this is the red plus sign on a frame's out-port; in the IDML there is nothing to mark it — overset is a layout outcome, not an attribute.
Our renderer composes the whole story and lays lines into the chain frame by
frame. At the last frame, lines whose baseline falls past the frame's bottom are
dropped rather than spilled past the frame with no clip — see
paged-renderer/src/pipeline.rs:3909. That matches how InDesign's exported PDF
hides overset behind the frame's clip, which keeps the rendered page faithful, but
it means:
The text is still fully present in the story part — extraction (see extract all text) returns it all. Only the rendered page omits the lines that didn't fit. There is no diagnostic that says "this story overset"; if you need to detect it, compare the composed line count to the placed line count yourself.
A related gap: conditional and metadata runs
Threading decides where text goes; a couple of other constructs decide whether a run is laid out at all, and these don't all behave the same:
- Conditional text is enforced. A
CharacterStyleRangewithAppliedConditionsis dropped when any referencedConditionresolves toVisible="false"; runs with no conditions, or whose conditions are all visible, are laid out normally. Supportedhidden conditional runs are filtered before layout — paged-renderer/src/pipeline.rs:2765 - Hidden and note text never flows.
HiddenText,Note, and index-marker subtrees are suppressed by the parser, so they never reach layout. Supportedsuppressed during story parse — paged-parse/src/story.rs:994
Both are different from overset: those runs are removed because of what they are, whereas overset text is dropped only because of where it ended up.
Frequently asked questions
What is text threading in IDML?
Threading is how a single story flows across more than one frame. Each frame names
the next with NextTextFrame and the previous with PreviousTextFrame, forming a
chain; the story is composed once and poured into the frames in chain order.
What is overset text? Overset is the text that still doesn't fit after the last frame in the chain is full. It is a layout outcome rather than an IDML attribute — InDesign shows it as the red plus on a frame's out-port, but nothing in the IDML marks it.
Does overset text get lost when Paged renders a page? The text is never lost from the story part — extraction still returns all of it. Only the rendered page omits it: at the last frame, lines whose baseline falls past the frame's bottom are dropped rather than spilled, matching how InDesign's exported PDF clips overset. There is no "this story overset" diagnostic; to detect it, compare the composed line count to the placed line count yourself.
Do all frames that show a story contain their own copy of the text?
No. The text lives in one Stories/Story_*.xml part regardless of how many frames
display it; frames only reference the story through ParentStory and contribute a
box for it to flow into.
Story structure
The element model of a story — the Story → ParagraphStyleRange → CharacterStyleRange → Content nesting, and the attributes the renderer reads.
Text variables
How TextVariableInstance and auto-page-number markers become characters in a run — frozen ResultText snapshots and private-use placeholder markers.