Story structure
The element model of a story — the Story → ParagraphStyleRange → CharacterStyleRange → Content nesting, and the attributes the renderer reads.
A story is a tree four levels deep: Story → ParagraphStyleRange → CharacterStyleRange → Content.
In short: Every IDML story nests the same four levels. The outer Story
element carries the id a frame points at; each paragraph is a ParagraphStyleRange;
each run of uniform character formatting is a CharacterStyleRange; and the literal
text lives in Content elements, interrupted by Br and Tab markers. This page is
the reference for that model — the elements, the attributes our parser reads, and the
constructs it reads but doesn't yet fully place.
A story is a tree four levels deep. The outer Story element carries the id a
frame points at; inside it, each paragraph is a ParagraphStyleRange; inside
each of those, one or more CharacterStyleRange elements hold runs of uniform
character formatting; and inside those, the literal text lives in Content
elements, interrupted by Br and Tab markers. The parser reads exactly this
shape — every other story attribute it doesn't yet act on is read past.
Story
└── ParagraphStyleRange one paragraph
└── CharacterStyleRange one run of uniform character formatting
├── Content literal text
├── Br forced line break
└── Tab tab characterThe example below is one story with two paragraphs. Each paragraph is its own
ParagraphStyleRange; the second carries a local SpaceBefore override. The
renderer reports paragraphs: 2, runs: 2 for it.
Two ParagraphStyleRange blocks in one story — two paragraphs, each with one run.
Stories/Story_ustory.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<idPkg:Story xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging" DOMVersion="20.0">
<Story Self="ustory">
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/$ID/[No paragraph style]">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
<Content>The first paragraph stands on its own.</Content>
</CharacterStyleRange>
</ParagraphStyleRange>
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/$ID/[No paragraph style]" SpaceBefore="6">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
<Content>The second paragraph follows it.</Content>
</CharacterStyleRange>
</ParagraphStyleRange>
</Story>
</idPkg:Story>
Story
The root element of a Stories/Story_*.xml part. Its Self id is the value a
text frame names in ParentStory, and the value the design map lists in
StoryList. The parser surfaces the writing direction today and reads the rest
past — additional story-level attributes land in later parser work.
| Attribute · Story | Type / values | Support | Notes |
|---|---|---|---|
| Self | string id | Supported | The story id a frame points at via ParentStory. |
| StoryDirection | LeftToRightDirection | RightToLeftDirection | Supported | Writing direction for the whole story. |
| AppliedTOCStyle | string ref | Not yet parsed | Read past; not acted on. |
| TrackChanges | boolean | Not yet parsed | Read past; tracked changes are not surfaced. |
A Story may also hold a StoryPreference child; the parser reads
OpticalMarginAlignment and OpticalMarginSize from it.
ParagraphStyleRange
One paragraph. A new ParagraphStyleRange sibling starts a new paragraph — that
is what makes paragraph count equal to the number of these blocks, not a count of
line breaks. AppliedParagraphStyle names the style; any attribute set directly
on the element is a local override that wins over the style for this paragraph.
| Attribute · ParagraphStyleRange | Type / values | Support | Notes |
|---|---|---|---|
| AppliedParagraphStyle | string ref (ParagraphStyle/…) | Supported | The paragraph style applied; resolved against Styles.xml. |
| Justification | LeftAlign | CenterAlign | RightAlign | LeftJustified | CenterJustified | RightJustified | FullyJustified | ToBindingSide | AwayFromBindingSide | Supported | Alignment override. FullyJustified is composed as LeftJustified; the binding-aware values fall back to Left/Right until binding side is plumbed through. |
| SpaceBefore | double (pt) | Supported | Space added above the paragraph. |
| SpaceAfter | double (pt) | Supported | Space added below the paragraph. |
| FirstLineIndent | double (pt) | Supported | Indent of the paragraph’s first line. |
| DropCapCharacters | short (0–150) | Supported | Number of leading characters to set as a drop cap. |
| DropCapLines | short (0–25) | Supported | How many lines tall the drop cap is. |
| DropCapDetail | int | Supported | Drop-cap sizing/positioning detail; read alongside the two counts. |
| BulletsAndNumberingListType | NoList | BulletList | NumberedList | Supported | Whether the paragraph carries a bullet or a number. |
| OverprintFill | boolean | Supported | Paragraph-level fill overprint; inherits from the cascade when absent. |
A ParagraphStyleRange can also carry a TabList of TabStop children and (when
the paragraph hosts one) a Table; tabs are covered alongside the run model
below.
CharacterStyleRange
One run of uniform character formatting inside a paragraph. A new
CharacterStyleRange starts wherever the character formatting changes — bold to
regular, one size to another. Like paragraphs, the count of runs equals the count
of these blocks. AppliedCharacterStyle names the style; direct attributes are
local overrides. Some character properties (AppliedFont, Leading) are carried
as typed children inside a Properties element rather than as attributes, and the
parser reads both forms.
| Attribute · CharacterStyleRange | Type / values | Support | Notes |
|---|---|---|---|
| AppliedCharacterStyle | string ref (CharacterStyle/…) | Supported | The character style applied; resolved against Styles.xml. |
| PointSize | double (pt) | Supported | Type size for the run. |
| AppliedFont | string (font family) — Properties child | Supported | Font family. Carried as a typed child of Properties; FontStyle names the face. |
| FontStyle | string | Supported | The named face within the family (e.g. "Bold"). |
| FillColor | string ref (Color/… or Swatch/None) | Supported | Glyph fill swatch; resolved against Graphic.xml. |
| FillTint | double (0–100, or -1) | Supported | Tint percentage of the fill; -1 / absent means use the swatch as-is. |
| Tracking | double (1/1000 em) | Supported | Uniform spacing added to every glyph’s advance. |
| Leading | double (pt) — Properties child | Supported | Line leading. Absent means Auto (point size × 1.2). |
| Underline | boolean | Supported | Draws an underline under the run. |
| StrikeThru | boolean | Supported | Draws a strikethrough through the run. |
| BaselineShift | double (pt) | Supported | Raises (+) or lowers (−) glyphs from the baseline. |
| AppliedConditions | space-separated Condition/… refs | Supported | When all referenced conditions resolve to Visible, the run is laid out; if any is hidden, the run is dropped. |
Content, Br, Tab
The leaves of the tree. Content holds literal text and is the only place text
lives. Br is a forced line break inside the paragraph; the parser turns it into
a newline in the run. Tab is a tab character. A run can hold any number of these
in sequence — several Content chunks split by Br/Tab collapse into one
run string with \n / \t in place.
| Attribute · Content / Br / Tab | Type / values | Support | Notes |
|---|---|---|---|
| Content | element with text body | Supported | The literal characters. U+2028 / U+2029 inside it are normalised to newlines. |
| Br | empty element | Supported | Forced line break — becomes a newline in the run text. |
| Tab | empty element | Supported | Tab character — becomes a tab in the run text. TabStop positions live on the paragraph’s TabList. |
What the story tree carries but the renderer doesn't fully place
A CharacterStyleRange is also where IDML hangs several constructs the parser
reads but the renderer does not yet fully lay out. They are detailed on the
threading and overset and
text variables pages; in short:
- Footnotes are parsed and distributed to the page that hosts their anchor, but the footnote bodies are not yet drawn. Parsed, not yet renderedfootnote bodies not yet drawn — paged-renderer/src/pipeline.rs:356
- Ruby (
RubyFlag/RubyString/RubyType) is parsed and a GroupRuby MVP is drawn above the base run; per-character distribution is not yet exact. Parsed, not yet renderedGroupRuby MVP only — paged-renderer/src/pipeline.rs:4208 - Hyperlinks inside a story (
HyperlinkTextSource) are not wired through to the run, so link regions aren't emitted. Not yet parsedin-story hyperlink sources not surfaced on runs — paged-parse/src/story.rs
Frequently asked questions
How many paragraphs does a story have?
Exactly as many as it has ParagraphStyleRange blocks — a new sibling block starts a
new paragraph. Paragraph count is the count of those blocks, not a count of line
breaks; a Br is a forced line break within a paragraph and does not start a new
one.
What is the difference between a ParagraphStyleRange and a CharacterStyleRange?
A ParagraphStyleRange is one paragraph and applies a paragraph style; a
CharacterStyleRange sits inside it and is one run of uniform character formatting,
applying a character style. A new CharacterStyleRange begins wherever the character
formatting changes — bold to regular, one size to another.
Where does the literal text live?
Only in Content elements, the leaves of the tree. Br and Tab are empty markers
that the parser folds into the run text as \n and \t, so several Content chunks
split by markers collapse into one run string with the breaks in place.
Does the renderer act on every story attribute? No. The parser reads exactly the four-level shape and the attributes documented here; other story attributes it doesn't yet act on are read past. Some constructs — footnotes, ruby, in-story hyperlinks — are parsed but not yet fully laid out, as noted above.
Your first story
The smallest story that renders one line of text — three nested ranges around a single line of Content, walked from the inside out.
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.