Skip to content

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,
})
PropertyDefaultMeaning
textRequiredInline text, with UTF-16 offsets used by editing commands.
marksEmpty setImmutable inline formatting marks.
fontFamilynullInherits the renderer's font family.
fontSizenullUses the standard diagram font size.
colornullInherits 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 fieldDefaultMeaning
idRequiredStable element identity.
frameRequiredRectangle in the owner's coordinate system.
textStoryRequiredCanonical paragraphs, lists, runs, and formatting.
rotation0Rotation in radians.
parentIdnullOptional containing element; text cannot own children itself.
zIndex0Stacking order.
textSizingModeDiagramTextSizingMode.fixedWidthWrapped fixed width or intrinsic autoWidth.
contentScrollOffset0Authored 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

ArgumentTypeRequired / defaultMeaning
elementIdStringRequiredOwning shape, text element or connector ID.
beforeDiagramTextStoryRequiredExpected current story. A stale value throws before publication.
afterDiagramTextStoryRequiredReplacement with the same story ID; registry and document validation still apply.
labelString'Edit text'History and change-event description.
selectionAfterDiagramTextSelection?nullOptional selection in this same owner/story; otherwise preserve current selection.
historyGroupDiagramHistoryGroup?nullExisting 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

ArgumentTypeMeaning
elementIdStringRequired text owner ID.
storyIdStringRequired story ID within that owner.
blockIdStringRequired 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 propertyMeaning
ResolvedTextLine.baselineRetained alphabetic baseline offset from bounds.top.
ResolvedTextLine.baselineYBaseline position in the story owner's coordinate system.
ResolvedTextMarker.baselineAlphabetic baseline offset within the marker box.
ResolvedTextMarker.baselineYMarker 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 fieldRequirement
start, endOrdered UTF-16 offsets within the measured text; lines cannot overlap earlier ranges.
leftFinite offset within the line layout coordinate system.
width, intrinsicWidthFinite and nonnegative. A list marker requires positive intrinsic width.
heightFinite and strictly positive.
baselineFinite and nonnegative, relative to the line top.
fontSizeFinite and strictly positive.
geometryOptional 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.