> ## Documentation Index
> Fetch the complete documentation index at: https://docs.teriyakigaming.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Runtime documents

> Build and mutate Ink Page Documents from Blueprint while the game runs — the page and layer API, writing the back of a sheet, the markup format, a worked generated note, and what a bake actually costs.

Documents are usually authored content, but nothing stops you from writing them in play: a
journal that gains an entry per chapter, a note that names the player's last town, a
manifest generated from inventory, or a whole document constructed from nothing. The
document asset exposes a Blueprint mutation API built for exactly that.

## Why every write goes through a function

`UInkPageDocument::Pages` is `BlueprintReadOnly` on purpose. It is not an oversight and
there is no node to write it directly.

Every page a display shows is **baked**: the page's visible layers are built into a Slate
widget and painted into a render target, which the page material multiplies over the paper.
That bake is cached per world, keyed on **document path + page index + content revision**
(a page's back is cached under its own key beside the front's). `ContentRevision` is a
transient counter on the document, bumped by every mutation.

If the `Pages` array were writable from Blueprint, a graph could change a page without the
revision moving, and every display would keep showing the stale bake forever with no way to
tell that it was stale. So the array is read-only, and the mutation functions each call
**Bump Revision** for you. Change a page through one of them and the next bake request
fails to match any cached key and re-bakes.

Two consequences worth internalizing:

1. **The revision is never saved.** It is `Transient`. It exists to invalidate caches
   within a session, not to version an asset.
2. **Displays do not watch their document.** Invalidating the cache does not repaint
   anything already on screen — the material is still holding the render target it was
   handed. After mutating, call **Refresh** on whatever is showing the document.

| Showing the document on                                      | Call                                                    |
| ------------------------------------------------------------ | ------------------------------------------------------- |
| [Ink Page Display](/plugins/inkwell/components/page-display) | **Refresh** — re-bakes and re-applies the current page  |
| [Ink Page Stack](/plugins/inkwell/components/page-stack)     | **Refresh** — re-deals the pile and re-bakes its sheets |
| [Ink Page](/plugins/inkwell/components/page-actor) actor     | **Refresh** — forwards to its display component         |
| [Ink Page](/plugins/inkwell/components/page-widget) widget   | **Refresh** — rebuilds the page widget                  |

**Set Document** already refreshes, so assigning a freshly built document needs no extra
call.

## The page API

Everything here is on the **Ink Page Document** asset itself, category *Inkwell | Page
Document*. Drag off a document reference to find them.

| Node                 | Signature                                    | What it does                                                                                                                                          |
| -------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Add Page**         | `(Ink Page Spec) → Integer`                  | Appends a page and returns its index. Normalizes the page as it goes in (see below).                                                                  |
| **Remove Page**      | `(Integer) → Boolean`                        | Removes a page. False when the index is out of range.                                                                                                 |
| **Set Page**         | `(Integer, Ink Page Spec) → Boolean`         | Replaces one page wholesale, then normalizes it. False when the index is out of range.                                                                |
| **Set Page Markup**  | `(Integer, String) → Boolean`                | Replaces the text of the page's **first front Text layer**. False when the index is out of range.                                                     |
| **Set Layer Markup** | `(Integer, Integer, String, Side) → Boolean` | Replaces the text of one specific Text layer, on either side of the sheet. False when either index is out of range, or the layer is not a Text layer. |
| **Get Page**         | `(Integer) → Ink Page Spec`                  | A **copy** of one page's data, both sides included. Out of range returns a default-constructed page.                                                  |
| **Get Page Count**   | `() → Integer`                               | Number of pages.                                                                                                                                      |
| **Bump Revision**    | `()`                                         | Invalidates every cached bake of this document. Every mutation above already calls it; the reads do not need it.                                      |

**Normalization.** **Add Page**, **Set Page**, and **Set Page Layers** run the page through
the same repair pass the loader uses: if the page's front has no Text layer, one is
appended. This is what makes `Make Ink Page Spec` → **Add Page** → **Set Page Markup**
work — a default-constructed spec has no layers at all, and a page with no Text layer can
never show markup. **Set Page Markup** runs the same pass, so it also repairs a page whose
text layer was removed. The repair applies to the **front only** — see the back-of-sheet
section below for why.

## The layer API

A page side is an ordered back-to-front stack of layers: index 0 is drawn first (the
bottom), the last entry is drawn last (the top). The full model is in
[Layers](/plugins/inkwell/authoring/layers); these are the runtime entry points. Every one takes a **Side**
input (default *Front*) selecting which face of the sheet it operates on.

| Node                      | Signature                                     | What it does                                                                                                                                                |
| ------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Get Page Layers**       | `(Integer, Side) → Array of Ink Page Layer`   | A copy of that side's stack, bottom to top. Empty for an out-of-range page.                                                                                 |
| **Set Page Layers**       | `(Integer, Array, Side) → Boolean`            | Replaces the whole stack, then (front only) normalizes. False when the index is out of range.                                                               |
| **Add Page Layer**        | `(Integer, Ink Page Layer, Side) → Integer`   | Appends a layer to the **top** of that side and returns its index. -1 when the page index is out of range.                                                  |
| **Remove Page Layer**     | `(Integer, Integer, Side) → Boolean`          | Removes one layer. False when either index is out of range.                                                                                                 |
| **Move Page Layer**       | `(Integer, Integer, Integer, Side) → Integer` | Moves a layer through the stack. **Delta is positive toward the top.** Returns the new index, or -1 when nothing moved (clamped at either end, or Delta 0). |
| **Find Text Layer Index** | `(Integer, Side) → Integer`                   | Index of that side's **first** Text layer, or -1 when it has none.                                                                                          |

Reads that are useful alongside them: **Get Page Size Cm** (the page's physical size —
**Page Width Cm** with the height derived from the **Page Size Pixels** aspect), **Get
Paper For Page** (the texture that page's given side will actually use, including a
per-page override or generated paper), and **Has Back** (below).

<Warning>
  **Remove Page Layer** will happily remove the only Text layer on a page's front. Nothing
  repairs it until something normalizes that page again, and until then the page's markup has
  nowhere to draw. Use **Find Text Layer Index** before removing, or follow up with **Set
  Page Markup**, which re-adds a Text layer as part of its own repair pass.
</Warning>

### Writing the back of a sheet

A page has two layer stacks: `Layers` (the front) and `BackLayers` (the back of the same
physical sheet). **An empty back means the back was never authored**: the sheet renders
bare paper there, and costs no bake and no render target. That is why the normalization
pass never touches the back — repairing a Text layer into it would give every page a
phantom back that starts paying for render targets.

So building a back at runtime is explicit, and the order matters:

1. **Make Ink Page Layer** — Type = **Text** (fill its **Text** payload, or leave the
   markup for step 3).
2. **Add Page Layer** — Page Index, that layer, **Side = Back**. Note the returned index.
3. **Set Layer Markup** — Page Index, that index, your string, **Side = Back**.

**Has Back** tells you whether a page's back has any layers; the
[reader](/plugins/inkwell/components/reader) uses the same test to decide whether **Next Page** turns the
sheet over (book order) and whether **Flip Page** has anything to show. The back's paper
can differ from the front's too: the document's **Back Paper** settings are authoring-time
like the rest of the document-level look (see the note below), while the per-page
**Back Paper Override** rides in the page spec, so a runtime-built page can carry its own.

## The structs

Blueprint sees these as ordinary structs — build them with **Make Ink Page Spec**, **Make
Ink Page Layer**, and so on, and read them apart with **Break**.

**Ink Page Spec** (`FInkPageSpec`) — one page (one physical sheet):

| Field               | Type                    | Default | What it does                                                                          |
| ------------------- | ----------------------- | ------- | ------------------------------------------------------------------------------------- |
| Layers              | Array of Ink Page Layer | empty   | Everything drawn on the front, bottom to top.                                         |
| Back Layers         | Array of Ink Page Layer | empty   | Everything drawn on the back. **Empty = no authored back**, which costs nothing.      |
| Paper Override      | Texture 2D              | None    | Overrides the document's paper for this page's front only. None = use the document's. |
| Back Paper Override | Texture 2D              | None    | The back-side twin of Paper Override.                                                 |

**Ink Page Layer** (`FInkPageLayer`) — one entry in a stack:

| Field   | Type           | Default | What it does                                                                             |
| ------- | -------------- | ------- | ---------------------------------------------------------------------------------------- |
| Type    | Ink Layer Type | Image   | `Text`, `Image`, or `Drawing`. Decides which payload below is drawn.                     |
| Name    | String         | empty   | Label in the Page Editor's layer list. Blank falls back to a name derived from the type. |
| Visible | Bool           | true    | Hidden layers are skipped by the bake and the editor preview alike.                      |
| Locked  | Bool           | false   | Cannot be selected or dragged in the Page Editor. Authoring aid only.                    |
| Opacity | Float          | 1.0     | Multiplies the whole layer's alpha.                                                      |
| Text    | Ink Page Text  | —       | Payload when Type is `Text`.                                                             |
| Image   | Ink Page Image | —       | Payload when Type is `Image`.                                                            |
| Drawing | Ink Drawing    | —       | Payload when Type is `Drawing`.                                                          |

A layer carries all three payloads and draws only the one matching `Type`, so switching a
layer's type throws nothing away.

**Ink Page Text** (`FInkPageText`) — an independent text box:

| Field                  | Type         | Default | What it does                                                                                                                                                    |
| ---------------------- | ------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Markup                 | String       | empty   | The rich text. See [the markup format](#the-markup-format).                                                                                                     |
| Justification          | Text Justify | Left    | Alignment of this box alone.                                                                                                                                    |
| Position               | Vector 2D    | (0, 0)  | Top-left of the box, normalized (0,0 = page top-left). Ignored while the box fills the page.                                                                    |
| Size                   | Vector 2D    | (0, 0)  | Normalized box size. **Zero or negative on either axis means the layer fills the page inside the document's margins.**                                          |
| Line Height Percentage | Float        | 0.0     | Line spacing for this box, as a multiple of the font's own line height. **0 = inherit the document's Line Height Percentage**, which is where every box starts. |

**Ink Page Image** (`FInkPageImage`) — a placed image, all coordinates normalized 0–1:

| Field            | Type       | Default    | What it does                                                      |
| ---------------- | ---------- | ---------- | ----------------------------------------------------------------- |
| Texture          | Texture 2D | None       | The image to draw.                                                |
| Position         | Vector 2D  | (0.1, 0.1) | Top-left corner of the image box.                                 |
| Size             | Vector 2D  | (0.3, 0.0) | Box size. A height of 0 or less keeps the texture's aspect ratio. |
| Rotation Degrees | Float      | 0.0        | Rotation about the box.                                           |
| Opacity          | Float      | 1.0        | Image opacity, multiplied by the layer's own.                     |

**Ink Drawing** (`FInkDrawing`) holds an array of **Ink Stroke** (`FInkStroke`: `Brush`,
`Color`, `Width Px` 6.0, `Opacity` 1.0, and an array of **Ink Stroke Point** with a
normalized `Position` and a 0–1 `Pressure`). Strokes are drawn oldest first. Generating
strokes procedurally works, but it is a niche use — see [Drawing](/plugins/inkwell/authoring/drawing) for
what each brush does.

<Note>
  Document-level look and layout — paper mode and its settings, paper texture and material,
  back paper, tints, ink strength, default font, typeface, size, color, page size in pixels,
  margins, line height — are all `BlueprintReadOnly`. They are authoring decisions, and
  Blueprint can read them but not write them. The one exception is **Page Width Cm**, which
  is writable. The per-page escape hatches are **Paper Override** / **Back Paper Override**
  on the spec and per-run styling in the markup.
</Note>

## Worked example: a note whose text comes from gameplay

The scenario: a body in an alley carries a note that has to name the district the player is
in and the number of witnesses they have already found.

The best-looking result comes from authoring the *look* and generating only the *words*.

<Steps>
  <Step title="Author the template">
    Create an Ink Page Document — `PD_CoronerNote` — in the Page Editor. Set its paper,
    default font, size, color, and margins, and type a placeholder body so you can see the
    layout. One page, one text layer, filling the page.
  </Step>

  <Step title="Point a display at it">
    On the note actor, add an [Ink Page Display](/plugins/inkwell/components/page-display) and set
    **Document** to `PD_CoronerNote`. Nothing about the actor is special — see
    [Integration](/plugins/inkwell/guides/integration).
  </Step>

  <Step title="Build the string">
    In the actor's Event Graph (Begin Play, or whenever the facts are known), assemble the
    text with **Format Text** or **Append**:

    `The body was found in {District}. {Count} others saw it happen.`

    Plain text is fine and is what you want here: a markup string with no tags renders
    entirely in the document's default text style. Newlines in the string become new lines
    on the page.
  </Step>

  <Step title="Write it into the page">
    Drag off the Ink Page Display component, get its **Document**, then call **Set Page
    Markup** with Page Index 0 and your string. The return value is false only if the page
    index is out of range — worth a **Branch** while you are wiring it up.
  </Step>

  <Step title="Refresh the display">
    Call **Refresh** on the Ink Page Display. The page re-bakes with the new text and the
    material picks it up the same frame.
  </Step>
</Steps>

<Warning>
  Step 4 mutates the **authored asset**, in memory, for the whole session. Every display
  pointed at `PD_CoronerNote` shows the new text, and the change survives until the asset is
  reloaded. That is usually what you want for a one-per-level note. It is wrong when several
  actors must show *different* generated text from the same template — for that, build a
  document per actor, below.

  In a game world (PIE or packaged), the mutation deliberately does **not** dirty the asset's
  package, so the editor will never offer to save your play-session text into the `.uasset`.
  Mutations from editor tooling and Editor Utility Blueprints *do* mark the package dirty, so
  your tooling's changes can be saved.
</Warning>

### A document per actor

**Ink Page Document** is a `BlueprintType` object, so you can build one in play:

<Steps>
  <Step title="Construct it">
    **Construct Object from Class**, Class = **Ink Page Document**, Outer = **Self**. Store
    the result in a variable.
  </Step>

  <Step title="Fill page 0">
    A newly constructed document is **not empty** — its constructor gives it one page.
    Call **Set Page Markup** (index 0): its repair pass gives that page a page-filling
    Text layer and the text lands on it.
  </Step>

  <Step title="Add more pages">
    **Make Ink Page Spec** (Layers empty is fine) → **Add Page** → note the returned index →
    **Set Page Markup** with it. Repeat per page.
  </Step>

  <Step title="Assign it">
    **Set Document** on the Ink Page Display, Stack, actor, or widget. No **Refresh** is
    needed: **Set Document** re-bakes on its own, and it broadcasts **On Page Changed** as
    well.
  </Step>
</Steps>

<Note>
  A document built this way carries the **class defaults**, not the defaults the *asset
  factory* applies. Creating a Page Document asset in the Content Browser seeds its page size,
  paper texture, and default font from *Project Settings → Plugins → Inkwell*;
  **Construct Object from Class** does not. So a runtime document starts at 1024 × 1400 px,
  21 cm wide, 90 px margins, line height 1.18, 28 pt warm near-black text, paper mode
  *Texture* with **no paper texture** (the page material's flat paper color) and **no default
  font** — which falls through to the project's Inkwell default font, and to the engine's
  Roboto if that is unset. Give each page a **Paper Override** in its spec if you want real
  paper on a runtime-built document.
</Note>

### Adding a second text box

To put a heading in its own box above a body that fills the page:

1. **Make Ink Page Text** — Markup = your heading string, Justification = *Center*,
   Position = (0.1, 0.06), Size = (0.8, 0.12).
2. **Make Ink Page Layer** — Type = **Text**, Name = `Heading`, Visible = true, Opacity =
   1.0, Text = the struct from step 1.
3. **Add Page Layer** — Page Index 0, Layer = the struct from step 2, Side = *Front*. It
   lands at the top of the stack and returns its index.

A text layer with a non-zero **Size** wraps at its own box width and grows down from the top
of the box. A layer whose **Size** is zero on either axis ignores Position entirely and
fills the page inside the document's margins — which is what **Set Page Markup** writes into
by default.

## The markup format

Markup is the standard UE rich-text format with one tag, `TextStyle`. The Page Editor writes
it; you can write it too.

**Plain text.** A string with no tags renders entirely in the document's default style —
default font, typeface, size, and color. For generated content, localized strings, and
journal entries, this is normally all you need.

**Styled runs.** A styled run looks like this, and closes with the rich-text short close
tag:

```xml theme={null}
<TextStyle Font="/Inkwell/Fonts/Font_Kalam.Font_Kalam" Face="Bold" Size="30" Color="(R=0.020000,G=0.017000,B=0.025000,A=1.000000)" U="1" S="1">the words</>
```

| Attribute | Meaning                                                                                                   |
| --------- | --------------------------------------------------------------------------------------------------------- |
| `Font`    | Object path of a composite `UFont` asset. Omit it to inherit the document's default font.                 |
| `Face`    | Typeface entry inside that composite font — `Regular`, `Bold`, `Italic`, `BoldItalic`, … Omit to inherit. |
| `Size`    | Point size.                                                                                               |
| `Color`   | Linear color tuple, `(R=…,G=…,B=…,A=…)`.                                                                  |
| `U`       | `"1"` for underline. Omit for none — never inherited from the defaults.                                   |
| `S`       | `"1"` for strikethrough. Omit for none — never inherited.                                                 |

Runs sit side by side to mix styles inside a paragraph; any attribute a run omits falls back
to the document's defaults. These attribute keys are the on-disk format and are frozen — the
same markup is read by the in-game bake and by the Page Editor's WYSIWYG box, which is what
keeps the two pixel-identical.

The fastest way to get a styled string right is to style one in the Page Editor first and
copy what it produced.

<Warning>
  **Never splice player- or network-supplied strings into tag attributes.** The markup writer
  does not escape attribute values — in authored documents every value is machine-generated
  (asset paths, numbers, color tuples), so escaping was unnecessary. A quote character in a
  spliced-in value breaks the run, and everything after it in that string is at the mercy of
  the parser. Generated *body* text between the tags is fine, but strip `<` from it, or it may
  be read as the start of a tag.
</Warning>

<Warning>
  **Fonts referenced only by markup are invisible to the cooker.** Markup stores fonts as
  string paths, so the Page Editor maintains the document's hidden `FontReferences` list on
  save to keep them cooked and loadable. If you generate styled markup at runtime that names a
  font by path, make sure something else in your project hard-references that font — or stay
  on plain text, which uses the document's own (hard-referenced) default font.
</Warning>

## Performance, caching, and cost

**What a bake costs.** Baking one page side builds a Slate widget for its visible layers
and paints it into a render target — **twice**. (Slate's auto-wrap measures against the
geometry cached by the previous paint, and an offscreen widget has no previous paint; the
first pass primes it, the second draws the correctly wrapped layout.) It runs on the game
thread, in linear space, at the document's page size clamped proportionally to **Max Page
Bake Dimension** (*Project Settings → Plugins → Inkwell*, default 4096). At the default
1024 × 1400 that is roughly 5.7 MB of render target per distinct page side.

**What is cached, and on what.** Bakes live in a per-world subsystem keyed on
`document path | page index | content revision`, with a page's back cached under its own
key beside the front. Game and PIE worlds have it; editor worlds do not, and bake uncached
through the tools path instead. Twenty sheets showing the same page of the same document
cost one bake. **A page with no authored back never bakes a back and never allocates a
render target for one** — an unauthored back is free by design.

**What invalidates.** Any mutation function, **Bump Revision**, and any editor-side edit to
the document. Nothing else — a page turn, a flip, a **Refresh**, or a new display of an
already-baked page all hit the cache.

<Warning>
  **A bump does not free the old bake.** The cache gains a new key; the previous render target
  stays in the map for the life of the world. Mutating a document in a loop — a page rewritten
  every frame, or per second — leaks a render target per revision. If you mutate repeatedly,
  call **Clear Page Cache** (Ink Page Render Library, takes the document) or the cache
  subsystem's **Clear Cache** (no document = drop everything) to reclaim them. Both cover
  front and back bakes alike.
</Warning>

**Pre-warming.** Bakes happen on demand, on the game thread, so the first display of a long
generated document can hitch. Call **Get Page Ink Texture** for the pages you expect during
a load screen or a fade — once per side you will show, via its **Side** input — and the
cache will already hold them when the player opens the note.

**Procedural paper is cached separately.** Generated paper is rasterized once per distinct
set of procedural settings and held on the document itself, keyed on a hash of those
settings plus the clamped page size — not on the content revision. A back that generates
its own sheet is cached the same way. Twenty pages sharing a document share one sheet of
paper, and mutating page text never regenerates it.

**Runtime pages are not save data.** The revision is transient and the pages you write live
only in memory. If generated pages must survive a reload, save the source facts — the strings
you built, the district name, the entry list — in your own save game and rebuild the document
on load. Reference documents from save data by soft object path, never by hard pointer.

**Multiplayer.** Documents are not replicated, and a bake needs a Slate renderer, so a
dedicated server bakes nothing. Treat pages as a client-side visual: replicate the facts, and
build the page locally on each client that needs to read it.

## Next

<CardGroup cols={3}>
  <Card title="Blueprint API" icon="code" href="/plugins/inkwell/reference/blueprint-api">
    Every Inkwell node in one place, grouped by the class it lives on.
  </Card>

  <Card title="Layers" icon="clone" href="/plugins/inkwell/authoring/layers">
    The stacking model these layer functions operate on, and the Layers panel.
  </Card>

  <Card title="Rendering" icon="gears" href="/plugins/inkwell/reference/rendering">
    The bake pipeline, the page material, and its parameters end to end.
  </Card>
</CardGroup>
