Reference

API reference

Every class, function, option and type the library exports, generated from its types and grouped by what you use them for. Version 1.0.1.

You won't need most of this. A typical app uses a few names: configureRichText, enableRichText (or createCanvasRichText on a plain canvas), RichText boxes, the formatting commands, and the formatting report for its toolbar.

Start with Getting started or the demo, where every feature shows its code. Come back here to look up an exact name or option.

@mycreativeshop/rich-text

Rich text on a plain HTML canvas, no Konva: createCanvasRichText and CanvasTextBox, settings, fonts and documents.

@mycreativeshop/rich-text/konva

Rich text for Konva: the RichText node, enableRichText, export, and the same document and settings types as the plain canvas.

Setup

Settings for every text box, and rich text on a Konva stage or a plain canvas.

configureRichText

function Konva or canvas
configureRichText(settings: RichTextSettings): void

Set where the library fetches fonts, fallback fonts, the dictionary and emoji from.

ParameterTypeDescription
settings RichTextSettings

RichTextSettings

interface Konva or canvas
interface RichTextSettings

The library's settings, given to configureRichText: where fonts come from (and a lookup for your own), what to do when one can't load, fallback fonts, spellcheck dictionaries, emoji, the render mode and diagnostics. Each call changes only what it names.

emoji?: null | EmojiSource

Emoji images (vector mode). null = the browser draws emoji. Default: our Noto set (DEFAULT_EMOJI_BASE_URL).

fallbackFonts?: false | { cssUrl: (request: FallbackFontRequest) => string }

Fonts for characters a design font lacks (vector mode). false turns them off: those characters are left to the browser. Default: Google's Noto fonts, only the missing characters.

fonts?: { cssUrl?: (face: FontFaceRequest) => null | string; lookup?: null | FontLookup; onFontError?: null | (error: FontError) => void }

Where fonts come from: the address for a family's faces, a lookup for your own fonts, and a callback when one can't load.

licenseKey?: null | string

Your license key, from your licenses page at mycreativeshop.com. Without one, or on a domain the license does not cover, everything still works and each text box shows a small mark: "Development" on localhost, staging and other domains not on your license, "Unlicensed" on any other site. Can be set at any time; null removes it.

renderMode?: TextRenderingMode

How glyphs are drawn: "native" (the browser's text drawing) or "vector" (HarfBuzz outlines — identical on screen, in print and on a server). Default "vector". Fixed once the first text box exists: set it before creating any (a later change is ignored, with a console warning).

spellcheck?: { aff: string; dic: string }

Hunspell dictionary files for spellcheck, fetched from the page. Default: the US English dictionary bundled with the library. Loaded once, when spellcheck starts.

enableRichText

function Plain canvas
enableRichText(stage: Stage, options?: EnableRichTextOptions): RichTextHandle

Turns on rich text editing for a Konva stage in one call.

Sets up the editing manager (caret, keyboard, clipboard, spellcheck, undo) and the handler for text inside Konva groups. Defaults: the text lives on the stage's first layer, and any Konva.Transformer on the stage is treated as the selection transformer.

Text boxes attach on their own: add a RichText anywhere on the stage (a layer, a group, a group added later) and it becomes editable; remove or destroy it and it is released. Selection follows your own transformer: when you set its nodes (tr.nodes([...])), the library sees the change, so there is nothing else to call.

Pass { interactive: false } for a render-only stage (an offscreen copy, a preview, print): text lays out and draws, and API calls work, but nothing listens to the pointer or keyboard.

ParameterTypeDescription
stage Stage

The Konva stage. It must already have at least one layer, unless you pass layer.

options EnableRichTextOptions (optional)

Optional settings; see EnableRichTextOptions.

Returns RichTextHandle

A RichTextHandle: manager for formatting, undo/redo and clipboard commands, and destroy() to remove everything this call added.

import Konva from "konva";
import { enableRichText, RichText, createDocumentFromText } from "@mycreativeshop/rich-text/konva";

const stage = new Konva.Stage({ container: "editor", width: 800, height: 600 });
const layer = new Konva.Layer();
stage.add(layer);
const tr = new Konva.Transformer();
layer.add(tr);

const rt = enableRichText(stage);

const box = new RichText({ x: 50, y: 50, width: 300, document: createDocumentFromText("Hello") });
layer.add(box);   // editable now

// Your own selection: put the clicked box on the transformer
stage.on("click tap", (e) => {
  tr.nodes(e.target === stage ? [] : [e.target]);
});

// A toolbar button: bold the selected box, or the selected words while typing
boldButton.onclick = () => rt.manager.toggleBold();

EnableRichTextOptions

interface Plain canvas
interface EnableRichTextOptions extends Omit<RichTextManagerConfig, "stage" | "layer" | "transformerSelector" | "groupedTransformerSelector">

Options for enableRichText. Every option is optional.

Spellcheck, the context menu, the editing callbacks and the rest, plus layer and transformers below.

context?: RichTextContext<RichText>

The editor session this manager belongs to (createRichTextContext()): managers in one context share undo and editing state (an editor with several pages, one manager per page); managers in different contexts never see each other (an offscreen copy, a preview). Default: the page-wide default context.

disableInternalUndoRedoShortcuts?: boolean

"library" history mode only: true = the library does not act on Ctrl+Z / Ctrl+Y (Cmd on Mac) while a box is being edited; the app binds those keys and calls manager.undo() / redo().

Not needed in "host" mode: there the library keeps no list, so its keys change nothing and the app's own undo (which restores steps with context.restore) is the only one.

enableContextMenu?: boolean

Show the library's right-click menu on the box being edited: spelling suggestions, Add to dictionary and Ignore (on a misspelled word), then Cut, Copy and Paste. Set to false to handle right-clicks yourself; right-clicks anywhere else are always left to your app.

enableSpellcheck?: boolean

Underline misspelled words while editing, with suggestions in the right-click menu. The English dictionary starts downloading in the background as soon as the manager is created.

interactive?: boolean

false = render-only: boxes lay out, draw and take API calls (setText, formatting…), but nothing listens to the pointer or keyboard — no stage, window or box handlers, no edit mode. For offscreen copies, previews and print. Default true.

layer?: Layer

The layer the text lives on. Default: the stage's first layer.

onBeforeMouseDown?: (event: KonvaEventObject<MouseEvent | TouchEvent>, richText: RichText) => boolean

Callback to intercept mousedown events on RichText nodes BEFORE the library processes them. Return true to prevent the library from handling the event (e.g., to prevent exit mode). Return false to allow normal library processing.

This is useful when you want to prevent edit mode from exiting under specific conditions, such as when clicking on custom controls or specific areas within a RichText node.

const manager = new RichTextManager({
  // ... other config
  onBeforeMouseDown: (e, richText) => {
    const pos = richText.getRelativePointerPosition();

    // Prevent exit if clicking in top-left control area
    if (pos.x < 50 && pos.y < 50) {
      console.log('Clicked control area - staying in edit mode');
      return true; // Prevent library from processing
    }

    return false; // Allow normal processing
  }
});
onClipboardAccessDenied?: () => void

Callback fired when clipboard access is denied during a paste operation. This typically happens when the user blocks clipboard permission in the browser, or when pasting via context menu without granted permission.

Use this to show a user-friendly message suggesting Ctrl+V or updating permissions.

Note: This callback is NOT fired if there is content in the internal clipboard (content copied from within the editor), since that content will be pasted instead.

const manager = new RichTextManager({
  // ... other config
  onClipboardAccessDenied: () => {
    showToast("Clipboard access denied. Use Ctrl+V to paste external content.");
  }
});
onDocumentChange?: (node: RichText) => void

A box's text or formatting changed - for autosave or an "unsaved changes" mark. Called after every change people make (typing, formatting, a resize, Story Mode) and every undo and redo, once per change: each keystroke too, so debounce a save. Changes your own code makes outside the library's commands are not reported. Every manager hears its own boxes; context.onDocumentChange hears every box of a context.

onEmptyBlur?: (node: RichText) => void

Callback fired when a user exits a text box that is empty (contains only whitespace/line breaks). The consuming application can decide how to handle this (e.g., remove the node, detach from transformer).

The node's getLastNonEmptyDocument() method can be used to retrieve the last meaningful content for undo.

const manager = new RichTextManager({
  // ... other config
  onEmptyBlur: (node) => {
    // Get last non-empty content for undo
    const lastDoc = node.getLastNonEmptyDocument();
    if (lastDoc) {
      myUndoStack.push({
        type: 'delete',
        document: lastDoc,
        x: node.x(),
        y: node.y(),
      });
    }

    // Detach from manager (cleans up internal state)
    manager.detachRichText(node);

    // Remove from transformer and layer
    const transformer = stage.findOne('#myTransformer') as Konva.Transformer;
    const currentNodes = transformer.nodes().filter(n => n !== node);
    transformer.nodes(currentNodes);
    node.destroy();
    layer.draw();
  }
});
onEnterEditMode?: (node: RichText) => void

Callback fired when a RichText node enters edit mode. Useful for showing formatting toolbars, tracking analytics, or updating UI state.

const manager = new RichTextManager({
  // ... other config
  onEnterEditMode: (node) => {
    showFormattingToolbar();
    trackActivity('richtext_edit_started');
  }
});
onExitEditMode?: (node: RichText) => void

Callback fired when a RichText node exits edit mode. Useful for hiding formatting toolbars, saving changes, or updating UI state.

const manager = new RichTextManager({
  // ... other config
  onExitEditMode: (node) => {
    hideFormattingToolbar();
    saveChanges(node);
  }
});
onSelectionChange?: (selectedRichTexts: RichText[]) => void

Called whenever the selected text boxes change, with the boxes now selected (an empty array when none is). Other selected shapes are not included.

onStoryModeEnter?: (node: RichText) => void

Callback fired when Story Mode is entered. Story Mode provides a fullscreen text editing overlay, particularly useful on mobile devices. Use this to hide UI elements, pause other interactions, etc.

const manager = new RichTextManager({
  // ... other config
  onStoryModeEnter: (node) => {
    hideFormattingToolbar();
    disableCanvasInteractions();
  }
});
onStoryModeExit?: (node: RichText, wasSaved: boolean) => void

Callback fired when Story Mode is exited (either saved or cancelled). Use this to restore UI state, show toolbars again, etc.

const manager = new RichTextManager({
  // ... other config
  onStoryModeExit: (node, wasSaved) => {
    enableCanvasInteractions();
    if (wasSaved) {
      trackActivity('story_mode_edit_saved');
    }
  }
});
storyModePlacement?: (box: RichText) => undefined | StoryModePlacement

Where a box is on screen for Story Mode's overlay (default: worked out from the stage). See StoryModeConfig.placement.

storyModeScale?: (box: RichText) => number

How large a box's text looks for Story Mode's attached panel (default: from the stage). See StoryModeConfig.scale.

transformers?: { groupChild?: string; selection?: string }

Where the app's transformers are (selectors). selection: the one the app attaches to selected nodes — default "Transformer", i.e. any Konva.Transformer on the stage. groupChild: the one for a text box selected INSIDE a group — default: the selection transformer.

upgradeOlderText?: TextUpgradePolicy

When text saved by an older version of the library moves to the latest rendering (it may shift slightly): "never" (default) — it keeps rendering exactly as it was saved, edited or not; "immediately" — as it loads. New text is always the latest. See the Upgrading guide.

RichTextHandle

interface Plain canvas
interface RichTextHandle

What enableRichText returns: the manager for the stage, and a destroy() that undoes the setup.

readonly manager: RichTextManager

Editing, formatting and text operations: toggleBold(), setText(), undo(), copy() and the rest.

destroy(): void

Remove everything this added — listeners, the caret, its boxes' registrations. The stage stays.

createCanvasRichText

function Konva
createCanvasRichText(options?: CanvasRichTextOptions): CanvasRichText

Adds editable rich text to a plain HTML canvas, without Konva.

Your app keeps its own render loop and its own objects. The library supplies text boxes that paint themselves (paint), an editor that takes pointer input in your scene's coordinates and keys and paste from the window, and an overlay with the caret, the text selection and the selected box's resize handles (paintOverlay).

Forward the canvas's pointer events (fromEvent converts a DOM event), repaint when onChange asks, and await ready() before exporting an image so every font is drawn.

ParameterTypeDescription
options CanvasRichTextOptions (optional)

Optional settings; see CanvasRichTextOptions.

Returns CanvasRichText

The CanvasRichText for this canvas. Call destroy() when you are done with it.

// createCanvasRichText and createDocumentFromText: imported from @mycreativeshop/rich-text

const canvas = document.querySelector("canvas")!;
const ctx = canvas.getContext("2d")!;
const ratio = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * ratio;
canvas.height = canvas.clientHeight * ratio;

let frame = 0;
const rt = createCanvasRichText({
  onChange: () => { frame ||= requestAnimationFrame(render); },
});

function render() {
  frame = 0;
  ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
  ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
  rt.paint(ctx);        // the text boxes
  rt.paintOverlay(ctx); // caret, selection and handles, on top
}

rt.addBox({ x: 40, y: 40, width: 400, document: createDocumentFromText("Grand Opening") });

// Scene coordinates are CSS pixels here, so map canvas pixels back by the ratio
const scene = new DOMMatrix().scale(ratio);
const at = (e: MouseEvent) => rt.fromEvent(e, canvas, scene);
canvas.addEventListener("pointerdown", (e) => rt.pointerDown(at(e)));
// Move and release on the window, so a drag that leaves the canvas still ends
window.addEventListener("pointermove", (e) => {
  rt.pointerMove(at(e));
  canvas.style.cursor = rt.cursor();
});
window.addEventListener("pointerup", (e) => rt.pointerUp(at(e)));
canvas.addEventListener("click", (e) => rt.click(at(e)));
canvas.addEventListener("contextmenu", (e) => {
  if (rt.contextMenu(at(e), { x: e.clientX, y: e.clientY }).handled) e.preventDefault();
});

CanvasRichTextOptions

interface Konva
interface CanvasRichTextOptions

Options for createCanvasRichText. Every option is optional.

caretWidth?: number

The caret's width in CSS pixels (default 1.5), as Konva's.

enableContextMenu?: boolean

The right-click menu while editing (default on): spelling suggestions, then Cut / Copy / Paste. See contextMenu.

enableSpellcheck?: boolean

Spellcheck while a box is edited (default on): misspelled words get a red squiggle, and the right-click menu (contextMenu) offers suggestions, Add to dictionary and Ignore. The dictionary (US English) downloads the first time it is needed. False: no spellcheck.

history?: HistoryMode

Who keeps the undo steps: "library" (default) - undo, redo and their keys work by themselves; "host" - every step goes to onHistoryStep, for your app's own undo stack beside its other changes, and restore applies one; "none" - no history (previews, print).

objectControls?: boolean | { fill?: string; size?: number; stroke?: string }

The object controls (default on): a click selects the box it lands on (a second click edits it), a drag moves it, and handles resize it - corners scale the type uniformly (Alt: about the center), sides change the width, never narrower than the longest word. The look is yours (handle size and colors); the behavior is the same on every surface. False: a host with its own object UI selects (select), moves and resizes itself (the resize rules are in @mycreativeshop/rich-text/kit).

onChange?: () => void

Called whenever the text boxes need repainting: schedule your render (e.g. requestAnimationFrame).

onDocumentChange?: (box: CanvasTextBox) => void

A box's text or formatting changed - for autosave or an "unsaved changes" mark. Called after every change people make (typing, formatting, a resize, Story Mode) and every undo, redo and restore, once per change: each keystroke too, so debounce a save. Changes your own code makes outside the library's commands are not reported.

onEmptyBlur?: (box: CanvasTextBox) => void

A box was left EMPTY when editing ended (before onExitEditMode) - e.g. remove it.

onEnterEditMode?: (box: CanvasTextBox) => void

A box entered edit mode.

onExitEditMode?: (box: CanvasTextBox) => void

A box left edit mode.

onHistoryStep?: (step: HistoryStep) => void

"host" history: every step, as it happens - keep it in your app's undo stack.

onStoryModeEnter?: (box: CanvasTextBox) => void

Story Mode opened on a box (enterStoryMode).

onStoryModeExit?: (box: CanvasTextBox, wasSaved: boolean) => void

Story Mode closed: wasSaved true for exitStoryMode or its Done button, false for cancel.

pixelRatio?: number

Device pixels per CSS pixel your canvas is drawn at - the scale you apply for a sharp canvas (canvas.width = css width × ratio; ctx.scale(ratio, ratio)). Default the screen's (window.devicePixelRatio). The caret and the handles are sized in CSS pixels on screen, as on Konva.

selectionColor?: string

The selection highlight (default the Konva editor's, translucent blue).

storyModePlacement?: (box: CanvasTextBox) => undefined | StoryModePlacement

Where a box is on screen, so Story Mode's overlay opens over it on desktop: your canvas element and the box's rectangle in its CSS pixels. Without it the overlay opens centered. See StoryModeConfig.placement.

storyModeScale?: (box: CanvasTextBox) => number

How large a box's text looks on screen (your zoom), for Story Mode's attached panel. Default: 1.

upgradeOlderText?: TextUpgradePolicy

When text saved by an older version of the library moves to the latest rendering (it may shift slightly): "never" (default) - it keeps rendering exactly as it was saved, edited or not; "immediately" - as it loads. New text is always the latest. See the Upgrading guide.

CanvasPointer

interface Konva
interface CanvasPointer

A pointer event, in the host's scene coordinates (the space box x / y are in).

altKey?: boolean

Alt: a resize grows about the box's center.

button?: number

0 = the primary button (default). A touch is primary.

pointerType?: string

"mouse" | "pen" | "touch" (touch gets larger thresholds).

shiftKey?: boolean

Shift held: a click extends the text selection.

timeStamp?: number

The event's own timestamp (performance.now() clock).

x: number

Horizontal position, in scene coordinates.

y: number

Vertical position, in scene coordinates.

StoryModePlacement

interface Konva or canvas
interface StoryModePlacement

Where a box is on screen, for the overlay to open over it: the element the canvas is drawn in, and the box's rectangle inside that element, in its CSS pixels.

box: { height: number; width: number; x: number; y: number }
container: HTMLElement

Text boxes

The text box on each surface, and what you create one with.

RichText

class Plain canvas
class RichText extends Shape, BoxApi

A rich text box on a Konva stage: a Konva.Shape that holds a rich text document (paragraphs of text with mixed fonts, sizes, colors, lists and effects) and lays it out and draws it on the canvas.

You set the box's width; its height follows the text. Add it to any layer or group like another Konva node, and save it with Konva's own toJSON(): the document is stored in the node's attributes.

Once enableRichText is on for the stage, every RichText on it can be selected with your transformer and edited in place (a click on a box that is already selected starts typing). Without it, a RichText still lays out and draws, and its methods (such as setDocument) still work, which suits previews and print.

Wait for richTextReady before exporting an image or printing, so every font is drawn.

const box = new RichText({
  x: 40,
  y: 40,
  width: 400,
  document: createDocumentFromText("Grand Opening"),
});
layer.add(box);
new (config: RichTextConfig): RichText

Creates a text box. It starts loading the fonts its document uses right away (without blocking) and lays the text out once they are ready. Await fontsReady() on the box, or richTextReady for a whole stage, when you need the final layout.

ParameterTypeDescription
config RichTextConfig

Konva shape settings (x, y, width, rotation, draggable, your own attributes) plus the text box's own; see RichTextConfig.

Returns RichText

bakeScale(scaleX: number, scaleY: number): void

PUBLIC API: Manually bake scale into document attributes Useful for programmatic transforms or custom transformer setups

ParameterTypeDescription
scaleX number
scaleY number
compareWithNativeLayout(): Promise<null | NativeLayoutComparison>

In vector text rendering mode, checks whether this box would break its text into different lines in native mode (the browser's own text drawing).

The two modes differ only for characters the box's fonts lack, and for emoji: vector mode draws them from the library's own fallback fonts and emoji set, native mode from the device's fonts. A box without any such character cannot differ, and the method returns null at once. Otherwise it lays out a hidden copy the native way and compares the lines; this box does not change.

Returns Promise<null | NativeLayoutComparison>

null when not in vector mode or when the modes cannot differ; otherwise the lines in each mode, whether they differ, and the characters drawn from the library's fallback fonts (which can look different between the modes even when the lines match).

copyfitDocument(newDocument: RichTextDocument, options?: { containerHeight?: number; containerWidth?: number; maxIterations?: number; minScale?: number; tolerance?: number }): number

Copyfit a new document into this node by scaling typography to fit within fixed dimensions. The node's x, y, width, and height remain unchanged. The document is scaled to fit. Uses binary search to find the optimal scale factor.

ParameterTypeDescription
newDocument RichTextDocument

The document to fit into this node

options { containerHeight?: number; containerWidth?: number; maxIterations?: number; minScale?: number; tolerance?: number } (optional)

Optional configuration for the copyfitting algorithm

Returns number

The final scale factor that was applied (1.0 = no scaling, 0.5 = 50% scale, etc.)

const richText = new RichText({ x: 100, y: 100, width: 300, height: 200, document: oldDoc });
const scaleFactor = richText.copyfitDocument(newLargeDoc);
console.log(`Content scaled to ${scaleFactor * 100}% to fit`);
destroy(): this

Override destroy to clean up font loading state

Returns this

fontsReady(): Promise<void>

Resolves when this node's most recent async initialization (font loading

  • initial layout) has finished. Useful for consumers that must rasterize the canvas only after fonts are loaded — e.g. headless PDF/image export. load() starts initialization in the background; await this to know when it's safe to capture the stage.

Never rejects — _initializeAsync handles its own errors internally (transitions to ERROR state) so awaiters get a clean settle either way.

Returns Promise<void>

getCurveAmount(): number

How far the text bends, in degrees of arc: positive bends up, negative bends down, 180 is a half circle. Default 180. For text set on a circle of a fixed radius (DocumentCurve.arcRadius), the bend worked out from the text's width.

Returns number

getCurveEnabled(): boolean

Whether the text is drawn on a curve.

Returns boolean

getDocument(): RichTextDocument

The box's document. This is the live object the box draws, not a copy: don't change it in place. To change the text, edit a copy and pass it to setDocument.

Returns RichTextDocument

The box's current document.

getLastNonEmptyDocument(): null | RichTextDocument

Get the last non-empty document state Useful for undo operations when a text box is deleted

Returns null | RichTextDocument

The last document that contained meaningful content, or null if never had content

getLayout(): TextLayout

The box's current layout — its lines, every letter's position and metrics, its content width and text height, its longest word — as last laid out. Read-only: for hosts that measure what is on the canvas (where the ink is, which colors are drawn). Never lays out.

Returns TextLayout

getLongestWordWidth(): number

The width of the widest single word in the text, in pixels, letter spacing and list indent included: the narrowest the box can be without a word overflowing it. Useful as a minimum width when resizing. Measured with the fonts loaded so far.

Returns number

getMinimumFontSize(): null | number

The smallest font size the text is drawn at, in pixels: each run's own size, or its paragraph's where the run sets none. Useful to keep text legible, for example to stop shrinking at a minimum size.

Returns null | number

The smallest size, or null when the document has no paragraphs.

getTransformer(): null | Transformer

Get the current transformer instance

Returns null | Transformer

The current transformer or null if none is set

getUsedColors(): UsedColors

Get all unique colors used in this RichText node's document Returns both text colors and stroke colors as separate arrays All colors are deduplicated and normalized to lowercase

Returns UsedColors

Object containing arrays of unique text colors and stroke colors

getUsedFonts(): string[]

Get all unique font families used in this RichText node's document

Returns string[]

Array of font family names used in the document

layoutAt(doc: RichTextDocument, width: number): TextLayout & { box: { height: number; width: number } }

Lay doc out at width the way this box would — its fonts, its measurer, its fallback size — and return the result WITHOUT applying it: the box's document, size and lines are untouched. For trial layouts (copyfit's scales, an app's "does this fit?"). Measures with the fonts already loaded, as the box itself does.

ParameterTypeDescription
doc RichTextDocument
width number

Returns TextLayout & { box: { height: number; width: number } }

replaceColors(replacements: ColorReplacement[]): ColorReplacementResult

Replace colors in the document with new colors Supports bulk replacement operations with multiple color mappings This method modifies the document in place, triggers a redraw, and adds to history

ParameterTypeDescription
replacements ColorReplacement[]

Array of color replacement mappings

Returns ColorReplacementResult

Result object with count of replacements made

const result = richText.replaceColors([
  { replaceColor: '#fff', newColor: '#000' },
  { replaceColor: '#333', newColor: '#999' }
]);
console.log(`Replaced ${result.replacedCount} color instances`);
replaceFonts(mappings: ({ from: string; to: string })[], options?: { containerHeight?: number }): void

Replace font families in the document and optionally re-copyfit.

Follows the same atomic history pattern as replaceColors: captures a before snapshot, mutates the document in-place, applies copyfitting when a containerHeight constraint is provided, then captures an after snapshot and pushes a single history entry.

ParameterTypeDescription
mappings ({ from: string; to: string })[]

Array of {from, to} font family replacements

options { containerHeight?: number } (optional)

Optional containerHeight for copyfitting after replacement

replaceVariables(values: readonly VariableDefinition[]): boolean

Merge fields: fill this box's {{id}} markers (any case) with their values, each value in its marker's style (core/variables.ts, the one rule). Not an undo step. A character a value brings that the box's fonts lack is fetched as after any edit, and fontsReady waits for it. Returns whether anything was filled.

ParameterTypeDescription
values readonly VariableDefinition[]

Returns boolean

resize(newPageScaleX: number, newPageScaleY: number): void

Resize this RichText node for a new scale.

This method updates the node's dimensions, position, and document content to match a new page/canvas scale. Unlike setAttrs(), this properly triggers layout recalculation.

If fonts are already loaded, layout is calculated immediately (synchronous). If fonts need to be loaded, the method returns immediately and layout will be calculated automatically when fonts finish loading (triggering a redraw).

ParameterTypeDescription
newPageScaleX number

The X scale factor for the new canvas/page

newPageScaleY number

The Y scale factor for the new canvas/page

// Resize for a canvas that's 2x larger
richText.resize(2.0, 2.0);
layer.draw(); // Will show correctly even if fonts still loading
setCurveAmount(amount: number): void

Set how far the text bends, in degrees of arc, from -360 to 360: positive bends up (the text sits on top of a circle), negative bends down. It is drawn only while the curve is on (setCurveEnabled).

A box on screen lays itself out and redraws at once. Not an undo step: from a toolbar, use the editing manager's setCurveAmount, which also turns the curve on and records one.

ParameterTypeDescription
amount number
box.setCurveAmount(120);
box.setCurveEnabled(true);
setCurveEnabled(enabled: boolean): void

Turn curved text on or off. Turning it on remembers the box's width; turning it off gives the box that width back. The bend is set with setCurveAmount.

ParameterTypeDescription
enabled boolean
setDocument(document: RichTextDocument): void

Replace the box's document. When its fonts are already loaded the box is laid out and redrawn at once; otherwise the missing faces are fetched and it is laid out when they arrive. node.setAttr("document", doc) and setAttrs({ document }) come through here too.

ParameterTypeDescription
document RichTextDocument
setWidth(value: number): this

Sets the box's width, as box.width(value) does. The text wraps to the new width.

ParameterTypeDescription
value number

Returns this

RichTextConfig

interface Plain canvas
interface RichTextConfig extends ShapeConfig

A text box's settings: Konva's shape settings (x, y, width, rotation, draggable, your own attributes…) plus these.

autoHandleTransforms?: boolean

Whether the box handles resizing by a Konva.Transformer itself: a side handle changes the width and the text rewraps; a corner handle scales the text in proportion. Set to false to leave transforms to Konva's default scaling. Default true.

copyfit?: CopyfitConfig

Copyfitting configuration - auto-scale content to fit fixed dimensions. When enabled, text will be scaled to fit within the node's width/height.

document?: RichTextDocument

The text and its formatting. Build one with createDocumentFromText("..."), or pass a document you saved earlier. Default: an empty document.

documentCurve?: DocumentCurve

Curve state — stored in node.attrs for serialization

editable?: boolean

false: a read-only box - it is selected, moved and resized like any other, but a click never starts editing and Story Mode does not open on it. Change it later with box.setAttr("editable", false). Default true.

transformer?: Transformer

The Konva.Transformer that resizes this box, when you do not use enableRichText (which tracks your transformer for you).

transformerConfig?: TransformerConfig

Settings for a new Konva.Transformer created for this box, used when transformer is not given. Read it back with getTransformer() and add it to a layer yourself.

CanvasTextBox

class Konva
class CanvasTextBox extends BoxApi

A rich text box on a plain HTML canvas: fonts, layout, curved text, shadows, fills, copyfit, spellcheck and painting, at a place in your scene. Create one with createCanvasRichText().addBox(); it offers the same box methods as the Konva RichText (getDocument, setDocument, fontsReady and so on).

A box has a position, a width and a rotation, but never a scale: resizing from a corner scales the type itself (its font sizes), so the text stays sharp.

attrs: Record<string, any>

Serialized attributes (the document, the curve…), as a Konva node's attrs.

copyfitDocument(newDocument: RichTextDocument, options?: { containerHeight?: number; containerWidth?: number; maxIterations?: number; minScale?: number; tolerance?: number }): number

Copyfit a new document into this node by scaling typography to fit within fixed dimensions. The node's x, y, width, and height remain unchanged. The document is scaled to fit. Uses binary search to find the optimal scale factor.

ParameterTypeDescription
newDocument RichTextDocument

The document to fit into this node

options { containerHeight?: number; containerWidth?: number; maxIterations?: number; minScale?: number; tolerance?: number } (optional)

Optional configuration for the copyfitting algorithm

Returns number

The final scale factor that was applied (1.0 = no scaling, 0.5 = 50% scale, etc.)

const richText = new RichText({ x: 100, y: 100, width: 300, height: 200, document: oldDoc });
const scaleFactor = richText.copyfitDocument(newLargeDoc);
console.log(`Content scaled to ${scaleFactor * 100}% to fit`);
fontsReady(): Promise<void>

Resolves when this node's most recent async initialization (font loading

  • initial layout) has finished. Useful for consumers that must rasterize the canvas only after fonts are loaded — e.g. headless PDF/image export. load() starts initialization in the background; await this to know when it's safe to capture the stage.

Never rejects — _initializeAsync handles its own errors internally (transitions to ERROR state) so awaiters get a clean settle either way.

Returns Promise<void>

getCurveAmount(): number

How far the text bends, in degrees of arc: positive bends up, negative bends down, 180 is a half circle. Default 180. For text set on a circle of a fixed radius (DocumentCurve.arcRadius), the bend worked out from the text's width.

Returns number

getCurveEnabled(): boolean

Whether the text is drawn on a curve.

Returns boolean

getDocument(): RichTextDocument

The box's document. This is the live object the box draws, not a copy: don't change it in place. To change the text, edit a copy and pass it to setDocument.

Returns RichTextDocument

The box's current document.

getLastNonEmptyDocument(): null | RichTextDocument

Get the last non-empty document state Useful for undo operations when a text box is deleted

Returns null | RichTextDocument

The last document that contained meaningful content, or null if never had content

getLayout(): TextLayout

The box's current layout — its lines, every letter's position and metrics, its content width and text height, its longest word — as last laid out. Read-only: for hosts that measure what is on the canvas (where the ink is, which colors are drawn). Never lays out.

Returns TextLayout

getLongestWordWidth(): number

The width of the widest single word in the text, in pixels, letter spacing and list indent included: the narrowest the box can be without a word overflowing it. Useful as a minimum width when resizing. Measured with the fonts loaded so far.

Returns number

getMinimumFontSize(): null | number

The smallest font size the text is drawn at, in pixels: each run's own size, or its paragraph's where the run sets none. Useful to keep text legible, for example to stop shrinking at a minimum size.

Returns null | number

The smallest size, or null when the document has no paragraphs.

getUsedColors(): UsedColors

Get all unique colors used in this RichText node's document Returns both text colors and stroke colors as separate arrays All colors are deduplicated and normalized to lowercase

Returns UsedColors

Object containing arrays of unique text colors and stroke colors

getUsedFonts(): string[]

Get all unique font families used in this RichText node's document

Returns string[]

Array of font family names used in the document

height(): number

The text's height (the box sizes itself to its text - at its width as it is now).

Returns number

id(): string

The box's id: the one given in CanvasBoxConfig.id, or one made up for it.

Returns string

layoutAt(doc: RichTextDocument, width: number): TextLayout & { box: { height: number; width: number } }

Lay doc out at width the way this box would — its fonts, its measurer, its fallback size — and return the result WITHOUT applying it: the box's document, size and lines are untouched. For trial layouts (copyfit's scales, an app's "does this fit?"). Measures with the fonts already loaded, as the box itself does.

ParameterTypeDescription
doc RichTextDocument
width number

Returns TextLayout & { box: { height: number; width: number } }

paint(ctx: CanvasRenderingContext2D): void

Paint the box in its own coordinates (the caller has applied its matrix).

ParameterTypeDescription
ctx CanvasRenderingContext2D
replaceColors(replacements: ColorReplacement[]): ColorReplacementResult

Replace colors in the document with new colors Supports bulk replacement operations with multiple color mappings This method modifies the document in place, triggers a redraw, and adds to history

ParameterTypeDescription
replacements ColorReplacement[]

Array of color replacement mappings

Returns ColorReplacementResult

Result object with count of replacements made

const result = richText.replaceColors([
  { replaceColor: '#fff', newColor: '#000' },
  { replaceColor: '#333', newColor: '#999' }
]);
console.log(`Replaced ${result.replacedCount} color instances`);
replaceFonts(mappings: ({ from: string; to: string })[], options?: { containerHeight?: number }): void

Replace font families in the document and optionally re-copyfit.

Follows the same atomic history pattern as replaceColors: captures a before snapshot, mutates the document in-place, applies copyfitting when a containerHeight constraint is provided, then captures an after snapshot and pushes a single history entry.

ParameterTypeDescription
mappings ({ from: string; to: string })[]

Array of {from, to} font family replacements

options { containerHeight?: number } (optional)

Optional containerHeight for copyfitting after replacement

replaceVariables(values: readonly VariableDefinition[]): boolean

Merge fields: fill this box's {{id}} markers (any case) with their values, each value in its marker's style (core/variables.ts, the one rule). Not an undo step. A character a value brings that the box's fonts lack is fetched as after any edit, and fontsReady waits for it. Returns whether anything was filled.

ParameterTypeDescription
values readonly VariableDefinition[]

Returns boolean

rotation(): number

The rotation in degrees, clockwise, about the box's top-left corner.

Returns number

scaleX(): number

Always 1: a canvas box is never scaled (resizing scales its type instead).

Returns number

scaleY(): number

Always 1: a canvas box is never scaled (resizing scales its type instead).

Returns number

setCurveAmount(amount: number): void

Set how far the text bends, in degrees of arc, from -360 to 360: positive bends up (the text sits on top of a circle), negative bends down. It is drawn only while the curve is on (setCurveEnabled).

A box on screen lays itself out and redraws at once. Not an undo step: from a toolbar, use the editing manager's setCurveAmount, which also turns the curve on and records one.

ParameterTypeDescription
amount number
box.setCurveAmount(120);
box.setCurveEnabled(true);
setCurveEnabled(enabled: boolean): void

Turn curved text on or off. Turning it on remembers the box's width; turning it off gives the box that width back. The bend is set with setCurveAmount.

ParameterTypeDescription
enabled boolean
setDocument(document: RichTextDocument): void

Replace the box's document. When its fonts are already loaded the box is laid out and redrawn at once; otherwise the missing faces are fetched and it is laid out when they arrive. node.setAttr("document", doc) and setAttrs({ document }) come through here too.

ParameterTypeDescription
document RichTextDocument
setPosition(x: number, y: number): void

Moves the box and asks for a repaint. Not an undo step.

ParameterTypeDescription
x number

The new left edge, in scene coordinates.

y number

The new top edge, in scene coordinates.

setRotation(degrees: number): void

Rotates the box and asks for a repaint. Not an undo step.

ParameterTypeDescription
degrees number

Clockwise, about the box's top-left corner.

setWidth(width: number): void

A width DECIDED by someone (the text re-wraps); the box's own layout writes go through the same rule.

ParameterTypeDescription
width number
width(): number

The width the text wraps at, in scene units.

Returns number

x(): number

The left edge of the box, in scene coordinates.

Returns number

y(): number

The top edge of the box, in scene coordinates.

Returns number

CanvasBoxConfig

interface Konva
interface CanvasBoxConfig

Where a text box sits and how wide it is, in the host's scene coordinates.

copyfit?: CopyfitConfig

Shrink the type to fit a height when the box first loads (see copyfit).

document: RichTextDocument

The text and its formatting: createDocumentFromText("...") for plain text, or a document you saved earlier (the same format the Konva RichText uses).

documentCurve?: DocumentCurve

Text on an arc (as a saved Konva box carries it): { enabled, amount } in degrees.

editable?: boolean

false: the box is selected and moved like any other, but never edited (attrs.editable later).

id?: string

Optional id (one is made up otherwise): what undo history and events know the box by.

rotation?: number

Degrees, clockwise, about the box's top-left corner.

width: number

The width the text wraps at. The height follows the text.

x: number

The left edge of the box, in scene coordinates.

y: number

The top edge of the box, in scene coordinates.

CopyfitConfig

interface Konva or canvas
interface CopyfitConfig

Configuration for automatic copyfitting behavior. When enabled, text will be scaled to fit within the node's fixed dimensions.

containerHeight?: number

Fixed container height for copyfitting. When set, the algorithm uses this as the vertical constraint instead of the node's auto-calculated height. Essential when text content has been replaced before loading — without this, the auto-calculated height always matches the content and copyfit has no constraint to shrink against.

enabled: boolean

Enable copyfitting for this node

maxFontSize?: number

Maximum allowed font size - caps any font in the document

minFontSize?: number

Minimum allowed font size - stops shrinking if any font hits this

newText?: string

Replacement text for copyfitting pre-modified elements. When set, the node renders its original text/document first (measuring design-intent height), then builds a new document from this text and copyfits it within that height. Each \n-delimited line becomes a paragraph block, inheriting formatting from the original document.

Works for both Konva.Text and RichText source elements — the original document structure provides all formatting; only the text changes.

DocumentCurve

interface Konva or canvas
interface DocumentCurve

A box's curved-text settings, saved with the box (the Konva node's documentCurve attribute). Curved text is set on one line along a circular arc. Usually changed through setCurveEnabled and setCurveAmount.

amount: number

How far the text bends, in degrees of arc, from -360 to 360: positive bends up (the text sits on top of a circle), negative bends down (it hangs from the bottom of one). 180 is a half circle. Ignored when arcRadius is set.

arcRadius?: number

Set the text on a circle of exactly this radius, in pixels: the bend is worked out from the text's own width, and amount is ignored. For text around a round badge or sticker.

centerX?: number

With arcRadius and centerY: the x of the circle's center, in the box's parent coordinates (the same space as the box's own x). The box is moved so its arc sits on that circle when it is first laid out; after that it can be dragged freely.

centerY?: number

With arcRadius and centerX: the y of the circle's center, in the box's parent coordinates.

enabled: boolean

Whether the text is drawn on the curve. While the text is being edited it is drawn flat.

preCurveWidth?: number

The box's width before the curve was turned on, in pixels. The box returns to it when the curve is turned off. Managed by the library.

side?: "top" | "bottom"

With arcRadius: which side of the circle the text runs along. "top" bends up, "bottom" bends down. Default "top".

Commands

Formatting and editing commands for your toolbar, and the formatting report that keeps it in step. The commands are the same on both surfaces.

TextFormatting

interface Konva or canvas
interface TextFormatting<Box>

The formatting operations, the same on every surface: a toolbar calls exactly these, whether the text is on Konva or on a plain HTML canvas.

Every operation takes the boxes to change, or none for the current box: the box being edited, else the selected box. On the box being edited it changes the selected text, or with only a caret the caret's paragraph; on a box that is not being edited, all of its text. Several boxes change as one undo step. Sizes, outline widths and letter spacing are in pixels as seen: a scaled group's scale is applied.

getFormattingState(box?: null | Box): null | FormattingState

The formatting a toolbar shows: at the caret or selection while editing, else of the first letter.

ParameterTypeDescription
box null | Box (optional)

Returns null | FormattingState

getText(boxes?: null | Box[]): string

The current box's plain text (the first of boxes), paragraphs joined by newlines.

ParameterTypeDescription
boxes null | Box[] (optional)

Returns string

scaleFontSize(factor: number, boxes?: null | Box[]): void

The type times factor, in proportion (a headline stays a headline); setFontSize gives one size.

ParameterTypeDescription
factor number
boxes null | Box[] (optional)
setAlignment(alignment: "left" | "center" | "right" | "justify", boxes?: null | Box[]): void

Set the horizontal alignment. It belongs to paragraphs: every paragraph the selection touches changes as a whole.

ParameterTypeDescription
alignment "left" | "center" | "right" | "justify"
boxes null | Box[] (optional)
setBulletStyle(bulletStyle: string, boxes?: null | Box[]): void

Change the marker of the reached list items: "disc", "circle", "square", or any single character. Paragraphs that are not list items are left as they are.

ParameterTypeDescription
bulletStyle string
boxes null | Box[] (optional)
setColor(color: string, boxes?: null | Box[]): void

Set the text color, as any CSS color string. Also removes an image or gradient fill.

ParameterTypeDescription
color string
boxes null | Box[] (optional)
setCurveAmount(amount: number, boxes?: null | Box[]): void

Bend the text into an arc (degrees; turns the curve on).

ParameterTypeDescription
amount number
boxes null | Box[] (optional)
setDocument(document: RichTextDocument, boxes?: null | Box[]): void

Replace the whole document: one undo step.

ParameterTypeDescription
document RichTextDocument
boxes null | Box[] (optional)
setFill(fill: null | TextFill, boxes?: null | Box[]): void

An image or gradient painted through the letters (null: back to their colors).

ParameterTypeDescription
fill null | TextFill
boxes null | Box[] (optional)
setFontFamily(fontFamily: string, boxes?: null | Box[], options?: FontFamilyOptions): Promise<void>

Change the font family, for example "Georgia". The promise resolves once the font is loaded and the text is laid out in it; until then the boxes keep their current font. Bold, italic and weight are kept. Changing several boxes is one undo step. options: a custom font's stylesheet address and details, saved in the document with the family (see FontFamilyOptions).

ParameterTypeDescription
fontFamily string
boxes null | Box[] (optional)
options FontFamilyOptions (optional)

Returns Promise<void>

setFontSize(fontSize: number, boxes?: null | Box[]): void

The size as getFormattingState shows it (a scaled group's text: at the group's scale). Every reached letter takes it.

ParameterTypeDescription
fontSize number
boxes null | Box[] (optional)
setFontWeight(weight: number, boxes?: null | Box[]): void

A numeric weight (100–900): the source of truth over bold.

ParameterTypeDescription
weight number
boxes null | Box[] (optional)
setLetterSpacing(letterSpacing: number, boxes?: null | Box[]): void

Set the extra space between letters, in pixels as seen; 0 is the font's normal spacing. Applies to whole paragraphs.

ParameterTypeDescription
letterSpacing number
boxes null | Box[] (optional)
setLineHeight(lineHeight: number, boxes?: null | Box[]): void

Set the line height, as a multiple of the font size (1.2 is 120%). Applies to whole paragraphs.

ParameterTypeDescription
lineHeight number
boxes null | Box[] (optional)
setParagraphSpacing(paragraphSpacing: number, boxes?: null | Box[]): void

Space after each paragraph (multiples of its font size).

ParameterTypeDescription
paragraphSpacing number
boxes null | Box[] (optional)
setShadow(shadow: null | TextShadow, boxes?: null | Box[]): void

Set the drop shadow, or remove it with null. The shadow belongs to the whole box, never to a selection. It changes only how the text is painted, never its layout.

ParameterTypeDescription
shadow null | TextShadow
boxes null | Box[] (optional)
setStrokeColor(color: string, boxes?: null | Box[]): void

Set the outline color, as any CSS color string. Visible only where the outline width is above zero.

ParameterTypeDescription
color string
boxes null | Box[] (optional)
setStrokeWidth(width: number, boxes?: null | Box[]): void

Set the width of the letters' outline, in pixels as seen. 0 removes it. If none of the reached letters has an outline color yet, black is used.

ParameterTypeDescription
width number
boxes null | Box[] (optional)
setTabStops(tabStops: TabStop[], boxes?: null | Box[]): void

Set the tab stops of whole paragraphs: where the text after each tab sits ({ pos: 1, align: "right", leader: "dots" } parks a price at the right edge behind dots). [] removes them; a tab then goes to the next default stop.

ParameterTypeDescription
tabStops TabStop[]
boxes null | Box[] (optional)
setText(text: string, boxes?: null | Box[]): void

Replace the text, keeping the formatting of the first letter and paragraph.

ParameterTypeDescription
text string
boxes null | Box[] (optional)
toggleBold(boxes?: null | Box[]): void

Turn bold on or off, following what the toolbar shows: if FormattingState.bold is true it is removed, otherwise applied. Also sets the numeric weight to 700 or 400.

ParameterTypeDescription
boxes null | Box[] (optional)
toggleBulletList(bulletStyle?: string, boxes?: null | Box[]): void

Make the reached paragraphs bulleted list items, or plain paragraphs again: if any is not a list item yet, all become list items; otherwise all lose their bullets.

ParameterTypeDescription
bulletStyle string (optional)

The marker for new list items: "disc" (the default), "circle", "square", or any single character, such as "✓".

boxes null | Box[] (optional)
toggleCurve(boxes?: null | Box[]): void

Curve the box's text along an arc, or straighten it again. Applies to the whole box. Experimental.

ParameterTypeDescription
boxes null | Box[] (optional)
toggleItalic(boxes?: null | Box[]): void

Turn italic on or off, following what FormattingState.italic shows.

ParameterTypeDescription
boxes null | Box[] (optional)
toggleStrikethrough(boxes?: null | Box[]): void

Turn strikethrough on or off, following what FormattingState.strikethrough shows.

ParameterTypeDescription
boxes null | Box[] (optional)
toggleUnderline(boxes?: null | Box[]): void

Turn underline on or off, following what FormattingState.underline shows.

ParameterTypeDescription
boxes null | Box[] (optional)

RichTextManager

class Plain canvas
class RichTextManager

RichTextManager

Central manager for RichText nodes that handles:

  • Edit mode state management
  • Mouse & keyboard event handling
  • Cursor & selection management
  • Transformer setup & management
  • Undo/redo operations
  • Clipboard operations

This dramatically simplifies library integration by handling all the boilerplate that would otherwise be required in the consuming application.

The caret and the selection are painted over the layer as it draws, not added to it: the library puts no node of its own in your layers, so stage.toJSON() and your own export see only your design.

For detailed information about event binding structure and troubleshooting, see the Integration Guide documentation.

readonly context: RichTextContext<RichText>

The editor session this manager belongs to (see RichTextManagerConfig.context).

boxes(): RichText[]

The text boxes this manager edits (every RichText on its stage).

Returns RichText[]

cancelStoryMode(): void

Cancel Story Mode without saving changes. Call this from your dialog's Cancel button.

copy(): boolean

Copy selected text to clipboard. This method can be called from consuming applications that want to implement custom copy functionality (e.g., custom context menus, toolbar buttons).

Returns boolean

true if text was copied, false if no selection or not in edit mode

// In a custom context menu handler
menuButton.addEventListener('click', () => {
  manager.copy();
});
cut(): boolean

Cut selected text to clipboard. This method can be called from consuming applications that want to implement custom cut functionality (e.g., custom context menus, toolbar buttons).

Returns boolean

true if text was cut, false if no selection or not in edit mode

// In a custom context menu handler
menuButton.addEventListener('click', () => {
  manager.cut();
});
destroy(): void

Stop managing everything: leaves editing, and removes exactly what this manager added — its stage and box handlers (namespaced ".richtextManager"; the host's own handlers stay), its window keyboard/paste listeners, and its boxes from the context's map and editing state. After this, nothing reacts to keys or clicks on its behalf.

edit(richText: RichText): void

Start editing richText now, as a tap on it while selected would: it becomes the selection and takes the caret. For a host gesture that should go straight to typing (a double-click on a decorated group whose words are the point). A box marked editable: false declines, exactly as it declines a tap.

ParameterTypeDescription
richText RichText
editing(): null | RichText

The text box being edited, or null.

Returns null | RichText

enterStoryMode(box?: RichText): void

Opens Story Mode - a larger editor for long text, over the canvas - on box, else on the box being edited, else on the selected box. Inside your own panel after setStoryModeContainer. While it is open, formatting commands reach the text selected in the panel. A box marked editable: false doesn't open.

ParameterTypeDescription
box RichText (optional)

The box to open it on (one of this manager's). Default: the box being edited, else the selected one.

exitStoryMode(): null | RichText

Exit Story Mode and save changes. Call this from your dialog's Apply/Save button.

Returns null | RichText

The updated RichText node, or null if validation failed

hasSelection(): boolean

Check if there is currently a text selection. Useful for determining whether copy/cut operations are available.

Returns boolean

true if there is a selection, false otherwise

isStoryModeActive(): boolean

Check if Story Mode is currently active

Returns boolean

paste(): Promise<void>

Paste content from clipboard into the currently editing RichText node. This method should be called from consuming applications that want to implement custom paste functionality (e.g., custom context menus, toolbar buttons).

Note: This method must be called during a user gesture (e.g., button click) to have clipboard access. If called outside a user gesture context, clipboard access may be denied by the browser.

Returns Promise<void>

Promise that resolves when paste is complete

// In a custom context menu handler
menuButton.addEventListener('click', async () => {
  await manager.paste();
});
redo(): void

Redo the last undone operation

replaceVariables(values: readonly VariableDefinition[], boxes?: readonly RichText[]): void

Merge fields: fill {{id}} markers (any case) with their values, each value in its marker's style - in every box this manager edits, or only boxes (one name on every surface: a box's own replaceVariables, the canvas's rt.replaceVariables). Not an undo step.

ParameterTypeDescription
values readonly VariableDefinition[]
boxes readonly RichText[] (optional)
selected(): null | RichText

The selected text box on this stage, or null (TextFormatting's "current box" when none is being edited).

Returns null | RichText

setStoryModeContainer(container: undefined | HTMLElement): void

Set the container for Story Mode. Call this before enterStoryMode() with your dialog's content div.

ParameterTypeDescription
container undefined | HTMLElement

The container element, or undefined to use document.body

// User clicks button to open dialog
const dialog = createMyDialog();
const editorDiv = dialog.querySelector('#story-editor-container');

manager.setStoryModeContainer(editorDiv);
manager.enterStoryMode();
stopEditing(): void

Stop editing (the caret and selection go; the box stays selected).

undo(): void

Undo the last operation

Plus every command in TextFormatting, described there: getFormattingState, getText, scaleFontSize, setAlignment, setBulletStyle, setColor, setCurveAmount, setDocument, setFill, setFontFamily, setFontSize, setFontWeight, setLetterSpacing, setLineHeight, setParagraphSpacing, setShadow, setStrokeColor, setStrokeWidth, setTabStops, setText, toggleBold, toggleBulletList, toggleCurve, toggleItalic, toggleStrikethrough, toggleUnderline.

CanvasRichText

interface Konva
interface CanvasRichText extends TextFormatting<CanvasTextBox>

Rich text on a plain HTML canvas, as returned by createCanvasRichText: the text boxes, the editor, and every formatting command (toggleBold, setFontSize, setColor and the rest).

Your app forwards pointer events to it (pointerDown, pointerMove, pointerUp, click, contextMenu) and calls paint and paintOverlay from its own render. Keyboard input and paste are heard on the window while a box is being edited.

addBox(config: CanvasBoxConfig): CanvasTextBox

Add a text box to the scene.

ParameterTypeDescription
config CanvasBoxConfig

Returns CanvasTextBox

boxes(): readonly CanvasTextBox[]

The boxes, bottom to top (the order paint draws them in).

Returns readonly CanvasTextBox[]

cancelStoryMode(): void

Closes Story Mode and puts the box's text back as it was.

click(p: CanvasPointer): { handled: boolean }

The click the browser sends after a press and release on the same spot.

ParameterTypeDescription
p CanvasPointer

Returns { handled: boolean }

contextMenu(p: CanvasPointer, screen: { x: number; y: number }): { handled: boolean }

The browser's contextmenu event (a right-click): on the box being edited, the library's menu opens at screen (the event's clientX / clientY) - spelling suggestions for a misspelled word, then Cut / Copy / Paste. handled: it opened (call preventDefault() on the event so the browser's own menu doesn't).

ParameterTypeDescription
p CanvasPointer
screen { x: number; y: number }

Returns { handled: boolean }

copy(): boolean

Copy the selected text to the clipboard. Returns whether there was a selection.

Returns boolean

cursor(): string

The mouse cursor at the last pointer event (a CSS cursor name) - the same rule on every surface. Apply it after each pointer call: canvas.style.cursor = rt.cursor().

Returns string

cut(): boolean

Cut the selected text to the clipboard (an undo step). Returns whether there was a selection.

Returns boolean

destroy(): void

Stop listening and release every box.

editing(): null | CanvasTextBox

The box being edited, or null.

Returns null | CanvasTextBox

enterStoryMode(box?: CanvasTextBox): void

Opens Story Mode - a larger editor for long text, over the canvas - on box, else on the box being edited, else on the selected box. The overlay opens over it when storyModePlacement says where it is, centered otherwise; or inside your own panel after setStoryModeContainer. While it is open, formatting commands reach the text selected in the panel. A box marked editable: false doesn't open.

ParameterTypeDescription
box CanvasTextBox (optional)

The box to open it on. Default: the box being edited, else the selected one.

exitStoryMode(): null | CanvasTextBox

Closes Story Mode and writes its text to the box (one undo step). Returns the box, or null.

Returns null | CanvasTextBox

fromEvent(e: MouseEvent, canvas: HTMLCanvasElement, sceneToCanvas?: DOMMatrix): CanvasPointer

A DOM pointer event as a CanvasPointer: canvas pixels, then (optional) the host's scene matrix inverted.

ParameterTypeDescription
e MouseEvent
canvas HTMLCanvasElement
sceneToCanvas DOMMatrix (optional)

Returns CanvasPointer

hasSelection(): boolean

Is text selected in the box being edited? (Whether copy and cut can do anything.)

Returns boolean

hitTest(p: { x: number; y: number }): null | CanvasTextBox

The topmost box under a scene point, or null.

ParameterTypeDescription
p { x: number; y: number }

Returns null | CanvasTextBox

isStoryModeActive(): boolean

Whether Story Mode is open.

Returns boolean

onFormattingChange(callback: (formatting: null | FormattingState, box: null | CanvasTextBox) => void): () => void

The formatting at the caret (while editing) or of the selected box, whenever it changes: a toolbar follows it. Called at once with the current state. Returns the unsubscribe.

ParameterTypeDescription
callback (formatting: null | FormattingState, box: null | CanvasTextBox) => void

Returns () => void

paint(ctx: CanvasRenderingContext2D): void

Paint every box, each under its own matrix, on top of the context's current transform.

ParameterTypeDescription
ctx CanvasRenderingContext2D
paintOverlay(ctx: CanvasRenderingContext2D): void

Paint the caret and the selection of the box being edited (call after everything else).

ParameterTypeDescription
ctx CanvasRenderingContext2D
paste(): Promise<boolean>

Paste the clipboard at the caret (call from a user gesture). Resolves false when no box is being edited.

Returns Promise<boolean>

pointerDown(p: CanvasPointer): { handled: boolean }

Pointer input. handled: the text editor took the event (else it is the host's).

ParameterTypeDescription
p CanvasPointer

Returns { handled: boolean }

pointerMove(p: CanvasPointer): { handled: boolean }

Pointer movement: drag-selecting text, moving or resizing a box with the object controls, and hover feedback. handled: the library used the event (else it is the host's).

ParameterTypeDescription
p CanvasPointer

Returns { handled: boolean }

pointerUp(p: CanvasPointer): { handled: boolean }

Pointer release: ends a drag-select, move or resize. handled: the library used the event.

ParameterTypeDescription
p CanvasPointer

Returns { handled: boolean }

ready(): Promise<void>

Every box on its real fonts and laid out - what to await before exporting or measuring (Konva: richTextReady). Bounded by the font time limit, so a font that never arrives can't hold it forever.

Returns Promise<void>

redo(): void

Redoes the last undone change. While a box is being edited, Ctrl+Y (Cmd+Y on Mac) does the same.

removeBox(box: CanvasTextBox): void

Take a box out of the scene.

ParameterTypeDescription
box CanvasTextBox
replaceVariables(variables: readonly VariableDefinition[], boxes?: readonly CanvasTextBox[]): void

Merge fields: fill {{id}} markers (any case) with their values, each value in its marker's style - in every box, or only boxes. Not an undo step. (One name on every surface: a box's own, Konva's manager.replaceVariables.)

ParameterTypeDescription
variables readonly VariableDefinition[]
boxes readonly CanvasTextBox[] (optional)
restore(step: HistoryStep, which: "before" | "after"): number

"host" history: undo ("before") or redo ("after") a step your app kept from onHistoryStep - every box it covers goes back to that state, as the library's own undo would put it. Returns how many boxes were restored.

ParameterTypeDescription
step HistoryStep
which "before" | "after"

Returns number

select(box: null | CanvasTextBox): void

Select a box (or none). A selected box's next click edits it.

ParameterTypeDescription
box null | CanvasTextBox
selected(): null | CanvasTextBox

The selected box, or null.

Returns null | CanvasTextBox

setStoryModeContainer(container: undefined | HTMLElement): void

Render Story Mode inside your own element (a panel or dialog) instead of an overlay; undefined: the overlay.

ParameterTypeDescription
container undefined | HTMLElement
stopEditing(): void

Leave edit mode.

undo(): void

Undoes the last text change (typing, formatting, a move or resize). The boxes it changes are restored and selected; edit mode stays as it was. While a box is being edited, Ctrl+Z (Cmd+Z on Mac) does the same.

Plus every command in TextFormatting, described there: getFormattingState, getText, scaleFontSize, setAlignment, setBulletStyle, setColor, setCurveAmount, setDocument, setFill, setFontFamily, setFontSize, setFontWeight, setLetterSpacing, setLineHeight, setParagraphSpacing, setShadow, setStrokeColor, setStrokeWidth, setTabStops, setText, toggleBold, toggleBulletList, toggleCurve, toggleItalic, toggleStrikethrough, toggleUnderline.

FormattingState

interface Konva or canvas
interface FormattingState

Formatting state at the current cursor/selection position. This is emitted whenever the selection changes to keep UI in sync.

alignment: "left" | "center" | "right" | "justify"

The paragraph's alignment.

bold: boolean

The text is bold.

canRedo: boolean

Redo can step forward.

canUndo: boolean

Undo can step back.

color: string

The color, as CSS.

cursorIndex: number

The caret's position, as a letter index in the box.

fontFamily: string

The font family.

fontSize: number

The size in pixels, as it looks on screen (a scaled group's scale applied).

fontWeight?: number

Numeric font weight of the selection (100–900), when uniform. Optional and READ-ONLY reporting — it reflects the document's existing fontWeight/bold attributes for the toolbar's weight picker; it does not affect rendering. Undefined when not resolvable (e.g. mixed weights across the selection).

hasSelection: boolean

Text is selected (not just a caret).

isList: boolean

The paragraph is a list item.

italic: boolean

The text is italic.

letterSpacing: number

The paragraph's letter spacing in pixels, as it looks on screen.

lineHeight: number

The paragraph's line height, as a multiple of the font size.

listStyle?: string

The list's marker.

listType?: "bullet"

The list's kind, when it is a bulleted list.

paragraphSpacing: number

The space after the paragraph, as a multiple of its font size.

selectionLength: number

How many letters are selected.

strikethrough: boolean

The text is struck through.

strokeColor: string

The outline's color.

strokeWidth: number

The outline's width in pixels, as it looks on screen (0: none).

tabStops: TabStop[]

The paragraph's tab stops; empty when it has none (a tab then goes to the next default stop).

underline: boolean

The text is underlined.

FontFamilyOptions

interface Plain canvas
interface FontFamilyOptions

A custom font's details for RichTextManager.setFontFamily, saved in the document with the family. The descriptive fields (fontImageUrl, description, attribution) are saved only for a custom font: one given a fontFaceUrl here, or one your fonts lookup knows.

attribution?: string

The font's author or license credit. Stored only.

description?: string

A short, user-friendly description of the font. Stored only.

fontFaceUrl?: string

The address of a stylesheet with the font's @font-face rules. Used to load the font, and saved so the design opens anywhere.

fontImageUrl?: string

The address of a preview image of the font, for a font picker. Stored only; never drawn.

Documents

The JSON a text box holds, and the functions that make and change it.

RichTextDocument

interface Konva or canvas
interface RichTextDocument

What a text box holds: its paragraphs (blocks) and the box-wide settings - a fill painted through the letters, a drop shadow, where its custom fonts live. Plain JSON: save it with your design and load it anywhere; it draws the same in every browser and in print.

baseFontSize?: number

The size, in pixels, of new text with no size of its own (an empty box's caret, a new paragraph).

blocks: Block[]

The paragraphs, in order.

fill?: TextFill

A gradient or image that fills every letter in the box, in place of the text colors. Setting a solid color (setColor) removes it.

fontMetadata?: FontMetadata[]

Where the document's custom fonts live, recorded as fonts are applied, so the design opens anywhere without your font lookup.

renderVersion?: number

The text version this document renders with (core/rendering.ts). None = version 1: made before versions existed. Saved with the text, so it renders the same wherever it goes.

shadow?: TextShadow

A drop shadow behind all of the box's text. No shadow when absent.

Block

interface Konva or canvas
interface Block

One paragraph of a document: a plain paragraph or a list item, its look, and its text in styled runs.

blockAttributes: BlockAttributes

The paragraph's look and layout.

content: InlineContent[]

The paragraph's text, in runs of one style each. A paragraph always has at least one run (an empty one: text: "").

id: string

A unique id within the document.

type: "paragraph" | "list-item"

"paragraph", or "list-item" for a line with a bullet or number.

BlockAttributes

interface Konva or canvas
interface BlockAttributes

A paragraph's look: the size, family and color its letters default to, and the paragraph's own layout - alignment, line height, letter spacing, spacing after it - plus an optional weight, all caps, background, border, tab stops and list marker.

alignment: "left" | "center" | "right" | "justify"

How the paragraph's lines sit in the box.

allCaps?: boolean

Display the paragraph in capitals. The words stay as typed — only layout and paint see capitals (see displayText), so copy, paste, spellcheck and switching it off all get the real text back. Set it for headings, labels and other text shown in capitals.

backgroundColor?: string

A color filled behind the paragraph's lines.

borderColor?: string

The color of a border around the paragraph.

borderWidth?: number

The width of that border, in pixels.

bulletOffset?: number

For a list item: how far its marker sits from the left, as a multiple of its font size (default 0.6).

color: string

The color of the paragraph's letters, as CSS: "#5e2dc1", "rgb(94, 45, 193)".

fontFamily: string

The font family of the paragraph's letters, e.g. "Lora" (a run may set its own).

fontSize: number

The size of the paragraph's letters in pixels (a run may set its own).

fontWeight?: number

A numeric weight for the paragraph's letters, 100-900 (a run may set its own).

letterSpacing: number

Extra space after every letter, in pixels (0 is none; negative tightens).

lineHeight: number

The height of a line as a multiple of the font size: 1.2 is normal.

listIndent?: number

For a list item: how far its text sits from the left, as a multiple of its font size (default 2).

listStyle?: string

For a list item: the marker - "disc", "circle", "square", "decimal", or any character, e.g. "➤".

listType?: "number" | "bullet"

For a list item: "bullet" (the default) or "number".

marker?: ListMarker

Vector list marker, drawn instead of the listStyle character.

A marker is TWO shapes in TWO colors — a white check inside a filled circle, a digit on a rounded square — so a single path plus a single color cannot express the common cases. Omit it and the character path is used, unchanged.

paragraphSpacing: number

Space after the paragraph, as a multiple of its font size (0 is none).

tabStops?: TabStop[]

Tab stops for this block. A \t in the text advances to the next stop.

An ARRAY from the outset even though v1 ships a single right stop: a user-pressed Tab key is a stated direction, and Item ⇥ Qty ⇥ Price is the obvious first thing someone does with it. Widening this later would be a document migration; starting wide costs nothing.

InlineContent

interface Konva or canvas
interface InlineContent

A run of text in one style. Its attributes override the paragraph's look for these letters only; a soft line break (Shift+Enter) before or after the run is recorded beside its text.

attributes: InlineAttributes

The run's own style, over its paragraph's.

startsWithSoftBreak?: boolean

The run starts on a new line within its paragraph (a soft break, Shift+Enter).

text: string

The run's letters.

trailingSoftBreakCount?: number

How many soft breaks follow the run's text (empty lines within the paragraph).

InlineAttributes

interface Konva or canvas
interface InlineAttributes

A run's own style, over its paragraph's: anything left out comes from the paragraph.

bold?: boolean

Bold (weight 700, unless fontWeight says otherwise).

color?: string

The run's color, as CSS, over its paragraph's.

fontFamily?: string

The run's font family, over its paragraph's.

fontSize?: number

The run's size in pixels, over its paragraph's.

fontWeight?: number

A numeric weight, 100-900. Wins over bold.

italic?: boolean

Italic.

strikethrough?: boolean

Struck through.

strokeColor?: string

The color of an outline around the letters.

strokeWidth?: number

The width of that outline, in pixels (0 or absent: none).

underline?: boolean

Underlined.

ListMarker

interface Konva or canvas
interface ListMarker

A list marker as vector art rather than a text glyph.

Lives on BlockAttributes so it serializes, clones and scales with the document for free. The library deliberately knows nothing about named icon sets or presets — it receives geometry and paints it.

glyphColor?: string

Glyph color. Defaults to the text color, matching the character-bullet behavior.

glyphPath?: string

SVG path data for the glyph, in a 24x24 viewBox. Omit for a shape-only marker.

scale?: number

Marker box as a multiple of fontSize. Default 1.0.

shape?: "none" | "square" | "circle"

Backing shape painted behind the glyph. Omit or "none" for a bare glyph.

shapeFill?: string

Fill for the backing shape. Ignored when there is no shape.

TabStop

interface Konva or canvas
interface TabStop

One tab stop, in the paragraph-attribute model Word and InDesign use — a ruler position belonging to the block, not a one-off jump belonging to a character.

align: "left" | "center" | "right"

How the run after the tab sits against the stop.

v1 IMPLEMENTS "right" only; "left" and "center" are accepted and fall back to it, so a document authored by a later version stays readable by this renderer instead of throwing. Declared as a union rather than a boolean because widening a union is free and changing a boolean's shape is a migration.

leader?: "dots" | "rule"

Optional leader painted across the gap.

pos: number

Position as a fraction of the node width. 1.0 is the right edge.

TextFill

type Konva or canvas
type TextFill = ImageFill | GradientFill

A non-color fill source for text glyphs (overrides per-run color).

GradientFill

interface Konva or canvas
interface GradientFill

Paints the text's letters with a two-color linear gradient across the whole box, instead of their colors. Set it as the document's fill.

doc.fill = { type: "gradient", color1: "#5e2dc1", color2: "#ff7903", angle: 90 }; // left to right
angle: number

The gradient's direction in degrees, as in CSS linear-gradient: 0 runs from color1 at the bottom to color2 at the top, 90 from left to right, 180 from top to bottom.

color1: string

The color the gradient starts with, any CSS color.

color2: string

The color the gradient ends with, any CSS color.

type: "gradient"

Always "gradient".

ImageFill

interface Konva or canvas
interface ImageFill

Paints the text's letters with an image instead of their colors. Set it as the document's fill.

doc.fill = { type: "image", url: "https://example.com/wood.jpg", fit: "cover" };
fit?: "cover" | "contain" | "stretch"

How the image maps onto the text box. Defaults to "cover".

type: "image"

Always "image".

url: string

The image's URL. It must be loadable by the page (served with CORS when it is on another origin).

TextShadow

interface Konva or canvas
interface TextShadow

Document-level drop shadow. A single shadow (not a CSS shadow list). color may be rgba() — its alpha carries the shadow opacity. blur is in px with CSS text-shadow blur-radius semantics (stored raw; the renderer maps it to a Gaussian σ = blur/2).

blur: number

Blur radius in pixels, as in CSS text-shadow. 0 is a hard-edged shadow.

color: string

Shadow color, any CSS color. Use rgba() to make it translucent, e.g. "rgba(0, 0, 0, 0.4)".

offsetX: number

Horizontal offset in pixels; positive moves the shadow right.

offsetY: number

Vertical offset in pixels; positive moves the shadow down.

FontMetadata

interface Konva or canvas
interface FontMetadata

Where a font the document uses comes from, saved with the document (its fontMetadata) so the text finds the same font wherever it is opened. Only source: "custom" entries change how a font is loaded: their files or fontFaceUrl are used before any other source.

doc.fontMetadata = [{ family: "Acme Sans", source: "custom", fontFaceUrl: "https://cdn.example.com/acme.css" }];
attribution?: string

The font's author or license notice. Informational.

description?: string

A description for people choosing a font. Informational.

family: string

The font family name, exactly as the document's fontFamily attributes spell it.

files?: FontFileAddress[]

A custom font's files, one per face (weight and style). Used instead of fontFaceUrl when both are given.

fontFaceUrl?: string

A custom font's stylesheet: the URL of a CSS file with @font-face rules for the family.

fontImageUrl?: string

A preview image of the font, for font pickers. Not used to draw text.

metadata?: Record<string, any>

Any other data you want saved with the font. The library does not read it.

source: "google" | "custom" | "system"

Where the font comes from: "google" (Google Fonts), "custom" (your own files or stylesheet, given here) or "system" (installed on the device).

variants?: string[]

The faces the family offers, for font pickers, e.g. ["400", "700", "400italic"]. Informational.

weights?: number[]

The weights a custom font ships, e.g. [400, 700]. Recorded with a stylesheet font so the design draws the same weights where it opens without your fonts lookup (a weight it lacks resolves to one it has, never a fake bold). Files carry their own weights. Version 2 text reads it.

FontFileAddress

interface Konva or canvas
interface FontFileAddress

One face of a custom font, by address (what a saved design records).

style: "normal" | "italic"

The face's style.

url: string

A .woff2, .woff, .ttf or .otf file.

weight: number

The face's weight, 100 to 900 (400 is regular, 700 bold).

createDocumentFromText

function Konva or canvas
createDocumentFromText(text: string, fontSize?: number, fontFamily?: string, color?: string): RichTextDocument

A document from plain text: one paragraph per line (\n), every paragraph in the same style. Blank lines between paragraphs are kept as empty paragraphs; blank lines at the end are dropped.

ParameterTypeDescription
text string

The text, one paragraph per line.

fontSize number (optional)

Font size in pixels. Default 16.

fontFamily string (optional)

Font family name. Default Poppins.

color string (optional)

Text color, any CSS color. Default black.

Returns RichTextDocument

A new document.

const doc = createDocumentFromText("Grand opening\nThis Saturday", 32, "Lato", "#5e2dc1");

replaceVariablesInDocument

function Konva or canvas
replaceVariablesInDocument(document: RichTextDocument, variables: readonly VariableDefinition[]): RichTextDocument

The document with every marker filled - a new document; document itself is never changed. Nothing to fill: document is handed back as it is (callers can tell nothing changed by identity).

ParameterTypeDescription
document RichTextDocument
variables readonly VariableDefinition[]

Returns RichTextDocument

VariableDefinition

interface Konva or canvas
interface VariableDefinition

A merge field and the value it is filled with.

id: string

The field's name, as written between the braces (e.g. "phone" for {{phone}}).

value: undefined | null | string

The text it becomes. Written as is (a "$" is a dollar sign, never a replacement pattern). None (null): empty text.

scaleDocumentTypography

function Plain canvas
scaleDocumentTypography(document: RichTextDocument, scaleFactor: number): RichTextDocument

Scale all typography properties in a document by a given factor. Used for copyfitting - scaling content to fit within fixed dimensions.

ParameterTypeDescription
document RichTextDocument

The document to scale

scaleFactor number

The factor to scale by (e.g., 0.5 = 50% of original size)

Returns RichTextDocument

A new scaled document (does not mutate the original)

UsedColors

interface Konva or canvas
interface UsedColors

Result interface for color extraction

shadowColors: string[]

The drop shadow's color (its opacity kept: "rgba(0, 0, 0, 0.4)").

strokeColors: string[]

The outline (stroke) colors drawn, each once; a stroke color with no stroke width is not drawn and not listed.

textColors: string[]

The letters' colors, each once: run and paragraph colors (or a gradient fill's two colors, which paint over them) and list marker colors. Opaque colors are given as "#rrggbb", translucent ones as "rgba(r, g, b, a)".

ColorReplacement

interface Konva or canvas
interface ColorReplacement

Interface for color replacement operations

newColor: string

The color to use instead, any CSS color. Each place keeps its own opacity.

replaceColor: string

The color to replace, in any CSS spelling ("#FFF", "white" and "rgb(255, 255, 255)" all match the same color). It matches that color at any opacity.

ColorReplacementResult

interface Konva or canvas
interface ColorReplacementResult

Result interface for color replacement operations

replacedCount: number

How many places changed color in all: the sum of the three counts below.

replacements: ({ count: number; newColor: string; oldColor: string })[]

Each replacement that changed something, with the color it matched (oldColor, as "#rrggbb"), the color it put there and how many places it changed. Replacements that matched nothing are left out.

shadowColorReplacements: number

How many shadow colors changed (0 or 1: a document has at most one shadow).

strokeColorReplacements: number

How many outline (stroke) colors changed.

textColorReplacements: number

How many letter colors changed (runs, paragraphs, gradient colors and list markers).

Fonts, layout and rendering

Loading fonts, waiting for them, laying text out without a box, and how text is drawn.

loadFonts

function Konva or canvas
loadFonts(requests: RichTextDocument | FontRequest[]): Promise<FontLoadResult>

Load fonts — a font picker's previews, a font about to be applied. The ONE font loader: every face a text box draws or measures comes through the same place, so nothing is fetched twice, and each face comes out ready to draw AND to measure.

await loadFonts(["Lato", "Bitter"]); // ready for regular / bold / italic text loadFonts([{ family: "Lato", weights: [300, 900] }]); // exactly those weights (fire and forget)

await loadFonts(document); // everything a document uses, as a box would

Never throws: families that could not load are in failed. Text boxes load what they use on their own; this is only for fonts not (yet) in a text box — a picker's previews, or a document about to be measured with layoutText.

ParameterTypeDescription
requests RichTextDocument | FontRequest[]

Returns Promise<FontLoadResult>

FontRequest

type Konva or canvas
type FontRequest = string | { family: string; italic?: boolean; weights: number[] }

A family to load: its name (regular, bold, italic, bold italic), or specific weights.

FontLoadResult

interface Konva or canvas
interface FontLoadResult

Per FAMILY: loaded = at least one requested face is drawable; failed = none could be loaded.

allSuccess: boolean

True when every family can be drawn. A family still loading when the wait ends is in neither list, and makes this false.

error?: Error

The error that stopped loading, when one did. Usually absent: a failed family is listed in failed.

failed: string[]

The families that cannot be drawn: none of the faces asked for loaded. Text in them uses a stand-in font.

loaded: string[]

The families that can be drawn: at least one of the faces asked for loaded.

FontLookup

type Konva or canvas
type FontLookup = (family: string) => FontInfo | null | undefined

Your app's answer for a font family it knows about (configureRichText fonts.lookup): where its files or stylesheet live and which weights it ships. Return nothing for a family you know nothing about (a Google family works without).

FontInfo

interface Konva or canvas
interface FontInfo

What the app knows about one family. Every field optional.

files?: FontFile[]

The family's font files, one per face. The weights it ships are these files' weights (a weight it lacks is drawn from the nearest file, never synthesized). Takes precedence over fontFaceUrl.

fontFaceUrl?: string

A stylesheet with the family's @font-face rules (a brand or uploaded font).

weights?: number[]

The weights the family ships, so a weight it lacks resolves to one it has (no fake bold).

FontFile

interface Konva or canvas
interface FontFile

One face of a family, as a font file: its address or its bytes. Any of .woff2, .woff, .ttf, .otf. The library draws and measures from this one file.

bytes?: ArrayBuffer

The file itself: an upload, a font bundled with the app, offline use. Not recorded in designs.

style?: "normal" | "italic"

Default "normal".

url?: string

Where the file is (it must allow cross-origin requests when on another origin).

weight?: number

The weight this file draws, 100-900. Default 400.

FontFaceRequest

interface Konva or canvas
interface FontFaceRequest

One font face the library needs. weight is already resolved to one the family ships.

family: string

The family asked for.

style: "normal" | "italic"

The style asked for.

weight: number

The weight asked for, 100-900.

FontError

interface Konva or canvas
interface FontError

A font face that could not be loaded. The text keeps drawing in a fallback font meanwhile.

  • cors-blocked: the font is on another origin whose server does not allow cross-origin reads. The library reads font files itself (to measure them), so the font host must send Access-Control-Allow-Origin (e.g. * or your app's origin). Not tried again. A server error on a host that leaves that header off its error pages looks the same from the page, and is reported as this too.
  • css-fetch-failed / font-fetch-failed: the stylesheet / the file could not be fetched (transient: a network or server error, tried again later; otherwise the address has nothing, e.g. a 404).
  • no-matching-rule: the stylesheet has no rule for this face.
  • font-parse-failed: the file is not a font the library can read (.woff2, .woff, .ttf, .otf).
family: string

The family that could not be loaded.

reason: "cors-blocked" | "css-fetch-failed" | "no-matching-rule" | "font-fetch-failed" | "font-parse-failed" | "font-draw-failed"

Why. Every reason but the last is about getting the file; font-draw-failed: the file loaded, but the browser would not draw it.

style: "normal" | "italic"

The face's style.

transient: boolean

Worth trying again later (the library does, by itself).

url?: string

The address that failed, when there is one.

weight: number

The face's weight.

FallbackFontRequest

interface Konva or canvas
interface FallbackFontRequest

One fallback-font download: a Noto family, a style, and the exact characters that are missing.

chars: string

The characters needed: a stylesheet may serve only these (a subset).

family: string

The fallback family, e.g. "Noto Sans JP".

hasItalicAxis: boolean

The family has an italic axis (ask for italic in the address).

hasWeightAxis: boolean

Whether the family is served with a weight / italic axis (Google's css2 needs to know).

italic: boolean

Whether that text is italic.

weight: number

The weight to match the text it stands in for.

EmojiSource

interface Konva or canvas
interface EmojiSource

Where emoji images come from (vector mode): a base address serving one SVG per emoji, named by its code points. Default: our hosted Noto emoji set. loadImage turns an SVG's text into something a canvas can draw, for environments without Image (a server).

baseUrl: string

Folder serving emoji_u.svg for the pinned release (no trailing slash).

loadImage?: (svgText: string) => Promise<CanvasImageSource>

How to turn an SVG file into something drawImage takes. Defaults to the browser's.

richTextReady

function Plain canvas
richTextReady(container: Container): Promise<void>

Wait until every text box inside container (a stage, layer or group — nested groups too, and copies no editor manages, e.g. a thumbnail clone) is laid out on its real fonts. The one wait an app needs before printing, taking a picture, or saying "loaded". A box given a new document while waiting is waited for again; a font that can't load doesn't hang it (each box's own time limit). Never rejects.

ParameterTypeDescription
container Container

Returns Promise<void>

layoutText

function Konva or canvas
layoutText(document: RichTextDocument, options: { width: number }): TextLayout

Lay a document out at width without a text box — the same layout a box performs on its first layout (same normalization, wrapping, measuring), returned instead of applied. For finishing documents before they reach a canvas (a converter fitting text to its source's line breaks) and for trial layouts (does it fit at this size?). Synchronous and side-effect free: it measures with the fonts already loaded — await loadFonts(doc) first for exact results.

ParameterTypeDescription
document RichTextDocument
options { width: number }

Returns TextLayout

TextLayout

interface Konva or canvas
interface TextLayout

What layoutText returns: a document laid out at a width, with no box.

contentWidth: number

The widest line (a box this wide shows every line unwrapped by it).

graphemes: GraphemeMetric[]

Every grapheme's position and metrics, as a box's layout produces them.

height: number

The height the text needs (a box sizes itself to this).

lines: LaidOutLine[]

The lines, as a box's getLines() reports them.

longestWordWidth: number

The widest single word (for a list, plus its indent): narrower than this, a word overflows.

LaidOutLine

interface Konva or canvas
interface LaidOutLine

One line exactly as the layout set it — the same lines the renderer draws.

Exposed because the line count is part of a design: a headline stacked on two lines by WRAPPING and rewritten to copy that sets on one leaves the second line's band empty. Nothing outside the layout can know where the wraps fell — an estimate from height and font size cannot tell a mixed-size line from two lines — so the layout reports it.

baselineY: number

Baseline, in the node's own coordinates. One per line, whatever sizes the line mixes.

blank: boolean

No visible characters — an empty paragraph kept for spacing.

blockId: string

The block (paragraph) this line belongs to. A wrapped paragraph spans several lines.

endsWith: "paragraph" | "wrap" | "softbreak" | "end"

Why the line ended: the text ran out of room (wrap), the paragraph ended (paragraph), an authored in-paragraph break (softbreak), or it is the last line (end).

text: string

The line's words, joined as laid out, without the whitespace a wrap swallowed.

width: number

Visible extent, list indent and tab columns included — what maxLineWidth is the max of.

GraphemeMetric

interface Konva or canvas
interface GraphemeMetric

One laid-out letter: what it is, its style, and where it sits in the box. A box's layout lists them in reading order (getLayout().graphemes). Positions are in pixels in the box's own coordinates (0, 0 is its top-left corner, before any rotation or scale), as the text is laid out flat.

Besides letters, the list holds a mark for each empty line (grapheme is "") and one for each paragraph's end (grapheme is "\n"), so a caret index can point at every place the caret can stop.

baselineY: number

The baseline of the letter's line, in pixels. Every letter on a line shares it.

blockId?: string

The id of the paragraph (block) the letter is in.

color: string

The letter's color, as the document gives it.

fontFamily: string

The font family the letter is drawn in.

fontSize: number

The font size the letter is drawn at, in pixels.

grapheme: string

The letter as drawn (one user-perceived character, which may be several code points, such as an emoji).

height: number

The letter's height, in pixels: its ascent plus its descent.

width: number

The letter's advance width, in pixels, letter spacing not included.

x: number

The letter's left edge, in pixels. Includes the line's alignment and any list indent.

y: number

The top of the letter's box, in pixels: its baseline minus its ascent.

TextRenderingMode

type Konva or canvas
type TextRenderingMode = "native" | "vector"

How letters are drawn: "vector" (the default) draws them as shapes from the font file, so every browser and the print match; "native" lets the browser draw them. Set with configureRichText before any text exists.

TextUpgradePolicy

type Konva or canvas
type TextUpgradePolicy = "never" | "immediately"

When text older than the latest version moves to it — the host's choice (upgradeOlderText):

  • "never" (default): it keeps its version, edited or not - saved text never moves;
  • "immediately": as it loads (it shifts by design; only a save persists it). An upgrade only ever goes up. (An "onEdit" choice was removed before 1.0: a version change re-wraps the whole box, so fixing one letter could move every line - docs/guides/upgrading.md.)

NativeLayoutComparison

interface Plain canvas
interface NativeLayoutComparison

What RichText.compareWithNativeLayout found: the box's lines as vector mode sets them and as native mode would, and the characters the library draws from its own fallback fonts because the box's fonts lack them.

fallbackCharacters: string[]

The characters drawn from the library's fallback fonts (and emoji set) instead of the device's. These can look different between the modes even when linesChanged is false.

linesChanged: boolean

True when the two modes break the text into different lines.

native: string[]

The text of each line as native mode would set it, top to bottom.

vector: string[]

The text of each line as the box sets it now (vector mode), top to bottom.

Editing sessions and undo

Which box is being edited, the change and formatting events, and undo kept in your app's own history.

createRichTextContext

function Plain canvas
createRichTextContext(options?: undefined | { history?: HistoryMode; onHistoryStep?: (step: HistoryStep) => void }): RichTextContext<RichText>

A new, independent editing session for enableRichText({ context }): its own undo history, its own box being edited, its own listeners. history: who keeps its undo steps - "library" (the default), "host" (with onHistoryStep, for your app's own undo) or "none" (previews and print). Stages given the same context share one history.

ParameterTypeDescription
options undefined | { history?: HistoryMode; onHistoryStep?: (step: HistoryStep) => void } (optional)

Returns RichTextContext<RichText>

defaultRichTextContext

const Plain canvas
const defaultRichTextContext: RichTextContext<RichText>

The editing session every stage joins unless enableRichText is given its own context: its undo history, which box is being edited, and the formatting and change listeners your toolbar uses.

RichTextContext

class Plain canvas
class RichTextContext<Box>

One editor session's shared state: its undo history, its text boxes, which box is being edited, and its formatting listeners. Create one with createRichTextContext.

Every manager belongs to one context. Managers in the same context share one undo history, for example an editor with several pages and one manager per page. Managers in different contexts never affect each other, for example an offscreen copy of a design, a preview, or a second editor on the page. A manager given no context joins the default one.

onDocumentChange(callback: (box: Box) => void): () => void

A box's text or formatting changed: every change people make (typing, formatting, a resize, Story Mode), and every undo, redo and restore. Called after the change is applied, once per change - each keystroke too, so debounce an autosave. Changes your own code makes outside the library's commands (replaceVariables, setDocument on the box itself) are not reported: your code made them. Returns a function that unsubscribes.

ParameterTypeDescription
callback (box: Box) => void

Returns () => void

onFormattingChange(callback: (formatting: null | FormattingState, box: null | Box) => void): () => void

Formatting reports for this session's boxes: called with the formatting at the caret / selection (bold, size, color…) and the box, whenever it changes — to keep a toolbar in step. It is also called once right away with the latest report (null formatting when there is none). Returns a function that unsubscribes.

ParameterTypeDescription
callback (formatting: null | FormattingState, box: null | Box) => void

Returns () => void

restore(step: HistoryStep, which: "before" | "after"): number

"host" history mode: undo ("before") or redo ("after") a step the app kept — every box it covers goes back to that snapshot, exactly as the library's own undo would put it (document, size, position, scale, curve, caret). Boxes no manager of this context has any more (deleted) are skipped; returns how many were restored.

ParameterTypeDescription
step HistoryStep
which "before" | "after"

Returns number

isEditingText

function Plain canvas
isEditingText(): boolean

Is a text box being edited anywhere on the page (any editor session)? E.g. to let the box have the keys: an app's own shortcuts (Delete, arrows, Ctrl+A) should stand down while this is true.

Returns boolean

stopEditingText

function Plain canvas
stopEditingText(): boolean

Stop editing text wherever it is on the page (e.g. before the app's own undo). True if it was.

Returns boolean

HistoryMode

type Konva or canvas
type HistoryMode = "library" | "host" | "none"

Who keeps a context's undo steps:

  • "library" (the default): the context keeps the steps, and the library's undo and redo apply them. Best when the text is all your app undoes.
  • "host": the context keeps no steps. Each one is passed to your onHistoryStep callback to store in your app's own undo stack; to undo or redo it, call context.restore(step, "before") or context.restore(step, "after"). Use this when your app already has an undo stack for its other objects.
  • "none": nothing is recorded. For offscreen work, previews and server-side rendering.

HistoryStep

interface Konva or canvas
interface HistoryStep

One undo step, as a host receives it. Steps that share group are ONE step to the user (letters typed within a second, in the same box): keep the first step's before and take the latest after. A step can cover several boxes (bold on two selected boxes is one step).

boxes: BoxChange[]

Each box the step changed, with its state before and after.

group: number

The step's group number. Consecutive steps with the same number are one step to the user: merge them by keeping the first step's before states and the latest step's after states.

kind: "typing" | "deletion" | "formatting" | "structural"

What kind of change it was: "typing", "deletion", "formatting", or "structural" (a new paragraph, a paste, a whole-document replacement, a resize, Story Mode closing).

BoxChange

interface Konva or canvas
interface BoxChange

One box's change in an undo step: its state before and after.

after: DocumentSnapshot

The box's state after the change: what redo restores.

before: DocumentSnapshot

The box's state before the change: what undo restores.

id: string

The id of the text box that changed.

DocumentSnapshot

interface Konva or canvas
interface DocumentSnapshot

One text box's state at a moment, as an undo step keeps it: its document, caret, selection, size, position, scale and curve. Restoring a snapshot puts the box back exactly as it was. Treat it as opaque data: keep it and hand it back, rather than building or changing one.

cursorIndex: number

Where the caret was, as a character index.

document: RichTextDocument

The box's document at that moment.

documentCurve?: DocumentCurve

The box's curve settings, when it has any.

height: number

The box's height, in the box's own units (before its scale).

scaleX: number

The box's horizontal scale.

scaleY: number

The box's vertical scale.

selectionEnd: null | number

Where the selection ended, as a character index, or null when nothing was selected.

selectionStart: null | number

Where the selection started, as a character index, or null when nothing was selected.

width: number

The box's width, in the box's own units (before its scale).

x: number

The box's x position.

y: number

The box's y position.

Export and upgrade

Images and print from a Konva stage, and turning Konva.Text into rich text.

cloneStageForExport

function Plain canvas
cloneStageForExport(stage: Stage): Promise<Stage>

Clone a Konva stage and prepare all RichText nodes for export. This ensures cloned RichText nodes render properly by recalculating their layout and waiting for the rendering to complete.

When a stage is cloned, RichText nodes lose their graphemeMetrics data and have incorrect lifecycle states, preventing them from rendering. This helper function automatically fixes all RichText nodes in the cloned stage and waits for rendering to complete before returning.

Important: The cloned stage is removed from the global Konva.stages array to prevent interference with application state management and event handling. When you're done with the cloned stage, call cloned.destroy() to clean up resources.

ParameterTypeDescription
stage Stage

The Konva stage to clone

Returns Promise<Stage>

A promise that resolves to the cloned stage with all RichText nodes ready for export

import { cloneStageForExport } from '@mycreativeshop/rich-text/konva';

// Clone and prepare stage for export
const cloned = await cloneStageForExport(Konva.stages[0]);

// Now you can safely generate images
const dataURL = cloned.toDataURL();
const blob = await cloned.toBlob();

// Use the dataURL
const img = new Image();
img.src = dataURL;
document.body.appendChild(img);

// Clean up when done (recommended)
cloned.destroy();

convertKonvaTextToRichText

function Plain canvas
convertKonvaTextToRichText(konvaText: Text): Promise<RichText>

Converts a Konva.Text node to a RichText node with pixel-perfect positioning.

This function handles the conversion from Konva.Text's rendering to RichText's rendering, adjusting the Y coordinate to account for the difference in baseline calculations:

  • Konva.Text uses: currentY + (ascent - descent) / 2 + lineHeight / 2
  • RichText uses: currentY + ascent

The Y adjustment ensures the text appears at exactly the same position visually.

Attribute Handling:

  • Font-related attributes (fontSize, fontFamily, fill, align, etc.) are transformed into the RichText document structure
  • Non-font attributes (draggable, uiType, constraints, designStep, assetId, zIndex, etc.) are preserved as-is
  • Transform attributes (x, y, width, scaleX, scaleY, rotation, offset, skew) are copied to maintain positioning
  • Visual attributes (opacity, visible) and identity (id, name) are preserved
ParameterTypeDescription
konvaText Text

The Konva.Text node to convert

Returns Promise<RichText>

A RichText instance with all non-font attributes preserved

const konvaText = new Konva.Text({
  x: 100,
  y: 100,
  text: 'Hello World',
  fontSize: 24,
  fontFamily: 'Arial',
  draggable: true,
  // Custom application attributes
  assetId: 'abc-123',
  constraints: { lockContent: true },
  designStep: { order: 1, title: 'Step 1' }
});

// Convert to RichText (preserves custom attributes)
const richText = await convertKonvaTextToRichText(konvaText);
layer.add(richText);

// Custom attributes are preserved
console.log(richText.getAttr('assetId')); // 'abc-123'
console.log(richText.getAttr('constraints')); // { lockContent: true }

konvaText.destroy(); // Remove old node