Text Rendering: Plain, Markdown, and Rich Text in Paper
Learn how to display plain text, parsed CommonMark Markdown, and animated rich text in Paper, plus how to measure text and use interactive input controls.
Use this file to discover all available pages before exploring further.
Paper offers three distinct text rendering modes that you can attach to any element: plain text for static labels, Markdown for content-driven prose, and rich text for dynamic, in-game styled strings with built-in animations. All three modes share the same pool of typographic style properties, so you can mix font size, color, spacing, and wrapping regardless of which mode you choose.
The simplest way to display text is the .Text() method. It binds a string and a font file to the element and renders using the current typographic style properties.
.Wrap(TextWrapMode) controls how text behaves when it overflows the element’s width. The available values mirror Scribe’s TextWrapMode enum — use NoWrap for single-line truncation or Wrap to reflow across multiple lines.
.Markdown() accepts a CommonMark string and parses it into a laid-out document before rendering. Supply separate FontFile references for each weight/style combination — Paper uses them when it encounters the corresponding Markdown syntax.
string content = @"# Hello PaperThis is **bold** and this is *italic*.- Item one- Item two";paper.Box("Article") .Width(400).Height(paper.Auto) .Markdown( content, font: regularFont, bold: boldFont, italic: italicFont, boldItalic: boldItalicFont, mono: monoFont ) .FontSize(16) .TextColor(Color.White);
Markdown layout is recalculated whenever the available width changes. Placing a Markdown element inside a container that resizes frequently may have a CPU cost; cache the string reference so Paper can detect unchanged content.
Rich text enables per-character inline styling and live animations via XML-style tags embedded directly in the string. Like Markdown, it requires one FontFile per variant.
Animation tags make characters move or change over time without any manual update code.
Tag
Behaviour
<shake>
Random per-character positional jitter
<wave>
Vertical sine-wave ripple across characters
<rainbow>
Cycles through the full hue spectrum
<pulse>
Oscillates opacity between full and translucent
<fade>
Fades characters in from transparent
<jitter>
High-frequency positional noise (faster than shake)
<typewriter>
Reveals characters one at a time left to right
Rich text layouts are cached per element across frames so that <typewriter> continues from where it left off even after layout recalculates. Call paper.ResetRichText(handle) to replay all animations from scratch — useful when a dialogue line changes.
// Replay typewriter and animations from the beginningpaper.ResetRichText(dialogueHandle);
You can query text dimensions without creating a visible element. This is useful for dynamically sizing containers or laying out custom-drawn elements.
// Quick measurement — returns a Float2(width, height) in logical unitsFloat2 size = paper.MeasureText("Score: 12345", 24f, scoreFont);// Measurement with full layout settingsvar settings = TextLayoutSettings.Default;settings.Font = myFont;settings.PixelSize = 18;settings.MaxWidth = 300;settings.WrapMode = TextWrapMode.Wrap;settings.LineHeight = 1.5f;Float2 wrapped = paper.MeasureText(myLongString, settings);// Full layout object — lets you query line widths, cursor positions, etc.TextLayout layout = paper.CreateLayout(myLongString, settings);Float2 cursorPos = layout.GetCursorPosition(caretIndex);int hitIndex = layout.GetCursorIndex(new Float2(clickX, clickY));
MeasureText and CreateLayout internally scale by DisplayFramebufferScale for HiDPI accuracy. The returned sizes and cursor positions are already back-converted to logical units, so you can use them directly in layout calculations.
For full control over both widgets, pass a TextInputSettings struct instead of the simple font/callback overload.
var fieldSettings = new ElementBuilder.TextInputSettings{ Font = inputFont, TextColor = Color.White, Placeholder = "Enter your name…", PlaceholderColor = Color.FromArgb(120, 200, 200, 200), MaxLength = 32, ReadOnly = false, MaskChar = null, // Set to '*' for password fields SelectAllOnFocus = true, CharFilter = (ch, current) => char.IsLetterOrDigit(ch) || ch == '_', ForceValue = null, // Push a value programmatically this frame};paper.Box("UsernameField") .Width(240).Height(36) .Rounded(4) .Padding(8, 0) .TextField(playerName, fieldSettings, newValue => playerName = newValue);
When MaskChar is set the field suppresses clipboard copy and cut operations to prevent masked content (e.g. passwords) from being extracted via keyboard shortcuts.
Setting
Description
Placeholder
Ghost text shown when the field is empty
PlaceholderColor
Colour of the placeholder text
MaxLength
Maximum character count (0 = unlimited)
ReadOnly
Prevents editing while still allowing selection
MaskChar
Replaces every visible character (use for passwords)
CharFilter
Func<char, string, bool> — return false to reject a character
SelectAllOnFocus
Selects all content when the field gains focus
ForceValue
Override internal state this frame (autocomplete, undo)
ForceSelectAll
When set alongside ForceValue, selects the pushed text