Internal Use These classes are used internally by RichTextEditor. You typically do not need to interact with them directly. This documentation is provided for developers implementing custom platforms or extending the Avalonia integration.

DocumentRenderer

class · internal sealed

Caches the Skia rendering of the document as an SKPicture to prevent redundant repaints. One DocumentRenderer lives for the lifetime of a RichTextEditor instance.

MemberDescription
DocumentRenderer(DocumentController)Construct with the controller whose document will be rendered.
InvalidateDocument()Mark the cache as stale. The next call to Render() will re-record the picture.
Render(SKCanvas, SKRect bounds)Called by DocumentRenderOp during the Avalonia render thread. Re-records the picture if dirty, then replays it to the canvas.
Dispose()Releases the cached SKPicture.

Design Notes

DocumentRenderer is intentionally not an ICustomDrawOperation itself, because Avalonia disposes ICustomDrawOperation instances after each frame. Instead, a lightweight DocumentRenderOp wrapper is created fresh each frame and delegates to the long-lived DocumentRenderer.

FieldDescription
_cache (SKPicture)The cached recording. Replayed every frame until _documentDirty is set.
_documentDirty (volatile bool)Thread-safe dirty flag. Set by InvalidateDocument(), cleared after re-recording.

DocumentRenderOp

class · internal sealed

A lightweight per-frame ICustomDrawOperation. Created fresh each Render() call by RichTextEditor and delegates to the long-lived DocumentRenderer.

MemberDescription
Bounds (Rect)The Avalonia bounds rect passed to the draw operation.
DocumentRenderOp(DocumentRenderer, Rect)Construct with the renderer and current bounds.
Dispose()No-op — owns no resources.
Equals(ICustomDrawOperation)Always returns false to force Avalonia to redraw every frame.
HitTest(Point)Always returns false.
Render(ImmediateDrawingContext)Obtains the Skia canvas via TryGetFeature<ISkiaSharpApiLeaseFeature> and calls DocumentRenderer.Render().
// How RichTextEditor uses DocumentRenderOp each frame:
public override void Render(DrawingContext context)
{
    var op = new DocumentRenderOp(_renderer, Bounds);
    context.Custom(op);
}

AvaloniaClipboardHandler

class · public

Implements IClipboardHandler using Avalonia's TopLevel.Clipboard API. Passed to DocumentController during initialization.

MemberDescription
AvaloniaClipboardHandler(Control control)Construct with any Avalonia control in the visual tree (used to access TopLevel).
Task<string?> GetTextAsync()Returns clipboard text via IClipboard.GetTextAsync().
Task SetTextAsync(string)Writes text to the clipboard via IClipboard.SetTextAsync().
Task<byte[]?> GetImageDataAsync()Checks for bitmap image data on the clipboard using named-format access. Returns PNG bytes decoded from an Avalonia Bitmap, or null.
Clipboard API Note GetImageDataAsync() uses the older Avalonia clipboard format enumeration APIs (GetFormatsAsync() / GetDataAsync()) because the newer API does not support raw named-format access for bitmap data. A #pragma warning disable CS0618 suppresses the deprecation warning.
// Usage in RichTextEditor.OnLoaded()
var clipboard = new AvaloniaClipboardHandler(this);
// 'this' is the RichTextEditor control
// Passed to DocumentController constructor

AvaloniaExtensions

class · public · static

Extension methods for Avalonia → Core type conversion.

Extension KeyCode ToKeyCode(this Key key)
Converts an Avalonia Key enum value to the Core KeyCode enum using a static Dictionary<Key, KeyCode> lookup table defined in Statics.KeyConversionMap. Falls back to KeyCode.None for unmapped keys.
// Used in RichTextEditor.OnKeyDown()
var coreKey = e.Key.ToKeyCode();
var keyInfo = new KeyInfo(coreKey, shift, control, alt);
await controller.OnKeyEvent(keyInfo);

Statics

class · internal · static

Holds the KeyConversionMap dictionary used by AvaloniaExtensions.ToKeyCode().

MemberTypeDescription
KeyConversionMap Dictionary<Key, KeyCode> Maps all standard Avalonia Key values to Core KeyCode values. Covers A–Z, 0–9, F1–F24, navigation keys (Home, End, PgUp, PgDn, arrows), editing keys (Delete, BackSpace, Enter, Tab, Escape, Insert), and punctuation/symbol keys.

Anti-Flicker Architecture

Rapid editing operations (caret blink, scroll, key repeat) can trigger many invalidation requests per second. The platform layer uses a two-stage debouncing approach to avoid unnecessary Skia redraws:

Stage 1 — DocumentController.Invalidate()

Called by MoveCaret(), ScrollTo(), ScrollBy(), and OnDocumentDidChange(). Invokes RequestRedraw?.Invoke().

Stage 2 — RichTextEditor.RequestRedrawFromController()

Calls DocumentRenderer.InvalidateDocument() and sets _redrawPending = true. If no redraw is already queued, dispatches InvalidateVisual() at DispatcherPriority.Render. Clears _redrawPending when the visual invalidation fires.

Stage 3 — DocumentRenderer.Render()

On the render thread, checks the volatile _documentDirty flag. Only re-records the SKPicture when dirty. Otherwise replays the cached picture. This means a scroll operation or caret movement does not re-execute all Skia draw calls — only a re-recording triggers full draw-call replay.

Thread Safety Image mutations are wrapped in lock(_drawLock) in DocumentController.Draw(). The _documentDirty flag in DocumentRenderer is volatile for safe cross-thread access between the UI thread (setting dirty) and the render thread (reading/clearing dirty).

Cursor Mapping

RichTextEditor maps EditorCursor (from Core) to Avalonia StandardCursorType:

EditorCursorStandardCursorTypeWhen
DefaultIbeamNormal text editing area
ResizeNSSizeNorthSouthTop or bottom edge handle of selected image
ResizeEWSizeWestEastLeft or right edge handle of selected image
ResizeDiagNWSETopLeftCornerTopLeft or BottomRight corner handle
ResizeDiagNESWTopRightCornerTopRight or BottomLeft corner handle
MoveSizeAllImage body (drag to move)
Avalonia Cursor Type Note SizeNorthWestSouthEast and SizeNorthEastSouthWest do NOT exist in Avalonia's StandardCursorType enum. Use TopLeftCorner and TopRightCorner respectively for diagonal resize cursors.