Paragraphs and text regions
Paragraph alignment affects ordinary paragraphs only. Lists remain left-aligned, including nested items. Markers reserve a half-em gap before their text. Checklist markers are clickable and use the same resolved geometry as painting.
Text inside any shape
Named ShapeTextLayerDefinition slots let a card have separate title and body regions while keeping one canonical story. A singleLine: true region accepts inline formatting and exits editing on Enter. A multiline region supports paragraphs, nested ordered/unordered lists and checklists. Default line height is 1.2; empty paragraphs retain line and caret geometry.
The platform keyboard follows the active region: single-line slots request a text keyboard with a Done action; multiline slots request a multiline keyboard with a Newline action. Done exits a single-line slot without inserting a paragraph. Moving between slots updates the existing IME connection; ordinary caret movement does not reconfigure it. These platform-contract checks do not certify real-device mobile selection, keyboard viewport handling or IME behavior.
Inside an active text region, touch-and-hold selects a word; dragging after the hold extends selection by whole words using the same resolved geometry as mouse selection. A finger move before the hold threshold does not perform mouse-style drag selection. A second finger transfers control to canvas navigation and exits text editing through the shared lifecycle. Selection handles, a mobile clipboard menu and keyboard occlusion remain open; touch navigation and text selection still need real-device validation.
Text reflows in each declared region. The definition's text sizing policy decides whether the owner grows or retains its bounds. Display, selection and editing consume the same resolved lines rather than measuring a separate text widget.
DiagramTextRun
dart
DiagramTextRun(
String text, {
Set<DiagramTextMark> marks = const {},
String? fontFamily,
double? fontSize,
DiagramColor? color,
})| Property | Default | Meaning |
|---|---|---|
text | Required | Inline text, with UTF-16 offsets used by editing commands. |
marks | Empty set | Immutable inline formatting marks. |
fontFamily | null | Inherits the renderer's font family. |
fontSize | null | Uses the standard diagram font size. |
color | null | Inherits the text region foreground; an explicit color paints the glyphs, not the owner rectangle. |
copyWith preserves omitted properties. Explicit null clears fontFamily, fontSize, or color back to inheritance. Colors persist through JSON, text splitting, merging, and paragraph editing. Clear formatting also clears the explicit color. The inspector's Text color field currently applies to the whole story.
Standalone text
TextElement owns rich text without a shape surface. It extends FramedElement, sharing rectangle-based placement and transformation mechanics with shapes. It has no style, ports, path, image, or child-clipping properties. Text inside a card remains a story in that card's declared regions; both owners use the same paragraph commands, resolved lines, selection, and IME pipeline.
dart
final text = TextElement(
id: 'caption',
frame: const DiagramRect.fromLTWH(40, 60, 220, 80),
textStory: DiagramTextStory(
id: 'caption-story',
blocks: [
DiagramTextBlock(
id: 'caption-body',
kind: DiagramTextBlockKind.paragraph,
runs: [DiagramTextRun('A little room for ideas')],
),
],
),
);
session.dispatch(DiagramTransaction(
label: 'Insert caption',
steps: [InsertElementsStep([text])],
));| Constructor field | Default | Meaning |
|---|---|---|
id | Required | Stable element identity. |
frame | Required | Rectangle in the owner's coordinate system. |
textStory | Required | Canonical paragraphs, lists, runs, and formatting. |
rotation | 0 | Rotation in radians. |
parentId | null | Optional containing element; text cannot own children itself. |
zIndex | 0 | Stacking order. |
textSizingMode | DiagramTextSizingMode.fixedWidth | Wrapped fixed width or intrinsic autoWidth. |
contentScrollOffset | 0 | Authored text content scroll offset. |
type is a getter returning BuiltInShapeType.text, not a constructor argument. copyWith preserves identity and omitted fields; explicit parentId: null removes its parent. DiagramDocument.textById retrieves standalone text, while framedElementById retrieves either text or a shape.
The text tool starts a clicked box in auto-width mode; dragging creates a wrapping box. Auto-width does not limit paragraph count. Both modes support Enter-created paragraphs and use the configured sizing policy for height. Configure standalone text with registry.text / TextElementDefinition; see registry configuration.
Text commands
session.text (or store.text in pure Dart) exposes DiagramStoryCommands. It edits the existing story in a shape, standalone text element, or connector label. The canvas paragraph editor and checklist interaction use these same commands. Text layout comes from the store's resolver; no canvas is required.
dart
final before = DiagramTextTarget.resolve(
session.state.document.elementById(elementId),
storyId,
)!.story;
final after = DiagramTextCommands.replaceText(
story: before,
blockIndex: 0,
start: 0,
end: before.blocks.first.plainText.length,
text: 'Updated by the host',
);
final decision = session.text.replace(
elementId: elementId,
before: before,
after: after,
);replace(...) → DiagramPolicyDecision
| Argument | Type | Required / default | Meaning |
|---|---|---|---|
elementId | String | Required | Owning shape, text element or connector ID. |
before | DiagramTextStory | Required | Expected current story. A stale value throws before publication. |
after | DiagramTextStory | Required | Replacement with the same story ID; registry and document validation still apply. |
label | String | 'Edit text' | History and change-event description. |
selectionAfter | DiagramTextSelection? | null | Optional selection in this same owner/story; otherwise preserve current selection. |
historyGroup | DiagramHistoryGroup? | null | Existing store history lease, used by composition input. Ordinary host edits can omit it. |
Missing or non-editable targets return a denied decision. Locks and host admission remain effective. A changed story ID or selection targeting a different story throws ArgumentError. Successful edits publish state and resolved geometry atomically and participate in undo/redo. Retained commands reject access after the owning store is disposed.
toggleChecklist(...) → DiagramPolicyDecision
| Argument | Type | Meaning |
|---|---|---|
elementId | String | Required text owner ID. |
storyId | String | Required story ID within that owner. |
blockId | String | Required checklist-item ID, independent of line wrapping or block index. |
Toggles the item's checked state as one undoable edit. Missing, non-editable or non-checklist targets return a denied decision. This command does not convert paragraphs into lists; use the pure DiagramTextCommands story operations and publish their result with replace for structural or run-formatting edits.
Baseline geometry
DiagramMeasuredTextLine requires a finite, nonnegative baseline, measured from the visual line's top. Custom DiagramParagraphTextLayoutEngine shapers must provide it along with the line's width, height and offsets. Flutter obtains it from the shaped line metrics; the fixed-metrics bootstrap uses a synthetic baseline and does not promise font-accurate output.
| Resolved property | Meaning |
|---|---|
ResolvedTextLine.baseline | Retained alphabetic baseline offset from bounds.top. |
ResolvedTextLine.baselineY | Baseline position in the story owner's coordinate system. |
ResolvedTextMarker.baseline | Alphabetic baseline offset within the marker box. |
ResolvedTextMarker.baselineY | Marker baseline, aligned to its first content line. |
Translations preserve the baseline offset and move its position with the line. A tall marker can reserve space above or below its paragraph; it cannot insert extra spacing between that paragraph's already-shaped visual lines. Empty paragraphs also retain a baseline. Export adapters must consume captured metrics and resources rather than infer baselines from font size. SVG/PDF exporters are not yet provided.
Custom line measurement admission
Content lines and independently shaped list markers pass through the same metric validation before a scene can publish.
DiagramMeasuredTextLine field | Requirement |
|---|---|
start, end | Ordered UTF-16 offsets within the measured text; lines cannot overlap earlier ranges. |
left | Finite offset within the line layout coordinate system. |
width, intrinsicWidth | Finite and nonnegative. A list marker requires positive intrinsic width. |
height | Finite and strictly positive. |
baseline | Finite and nonnegative, relative to the line top. |
fontSize | Finite and strictly positive. |
geometry | Optional retained interaction artifact. Its height must be finite, positive, and agree with height within 0.01 units. |
A marker must resolve exactly one line covering its complete marker string. Malformed measurements throw before publication: the previous document, scene, selection, history and observers remain unchanged. A host can correct its shaping provider and retry; it must also follow the font/layout revision contract when changing already-cached measurements.