How Inline Images Work
Images are stored in the text stream as U+FFFC (Object Replacement Character) placeholders. Each placeholder
carries an InlineImage object that the RTK layout engine calls to measure and paint the image.
From the user's perspective, images behave exactly like characters: they participate in selection, copy/paste,
undo/redo, and wrap with the surrounding text.
Click to Select
Clicking an image auto-selects the 1-character range and shows 8 resize handles.
Resize Handles
Corners maintain aspect ratio; edge handles allow free resize in one axis. Drag to resize live.
Undo / Redo
Every resize is committed as an undoable operation on pointer release.
Clipboard Paste
Pasting bitmap images from the clipboard inserts them as inline objects automatically.
InlineImage
classImplements IInlineObject (from Topten.RichTextKit) to participate in text layout.
| Member | Type | Description |
|---|---|---|
| Width | float { get; set; } | Display width in pixels. Mutable to support live resize without undo overhead. |
| Height | float { get; set; } | Display height in pixels. Mutable for the same reason. |
| Image | SKImage | The underlying Skia image (read-only). |
| InlineImage(SKImage, float w, float h) | ctor | Create with an existing SKImage and display dimensions. |
| Paint(SKCanvas, SKPoint) | void | Called by RTK to draw the image during layout. Do not call directly. |
| GetPngBytes() | byte[] | Returns the image encoded as PNG bytes — used by exporters. |
Inserting Images
Use DocumentController.InsertInlineImage(). It automatically clamps the display dimensions
so the image does not exceed the available content width.
// Load from a file path
using var skData = SKData.Create("photo.png");
var skImage = SKImage.FromEncodedData(skData);
// Insert at the current caret position
controller.InsertInlineImage(skImage, displayWidth: 400f, displayHeight: 300f);
// Or load from a stream
await using var stream = File.OpenRead("photo.jpg");
var bitmap = new Bitmap(stream);
// convert Avalonia Bitmap → SKImage via MemoryStream if needed
ResizeHandleType
enumIdentifies which of the eight resize handles the user is interacting with. Used internally by DocumentController.
| Value | Position | Behavior |
|---|---|---|
None | — | No handle active |
TopLeft | Top-left corner | Maintains aspect ratio |
TopCenter | Top edge midpoint | Free resize (vertical only) |
TopRight | Top-right corner | Maintains aspect ratio |
MiddleRight | Right edge midpoint | Free resize (horizontal only) |
BottomRight | Bottom-right corner | Maintains aspect ratio |
BottomCenter | Bottom edge midpoint | Free resize (vertical only) |
BottomLeft | Bottom-left corner | Maintains aspect ratio |
MiddleLeft | Left edge midpoint | Free resize (horizontal only) |
EditorCursor
enum
A platform-agnostic cursor hint sent to the UI via DocumentController.RequestCursorUpdate.
The UI layer is responsible for mapping these to native cursor types.
| Value | When Used | Avalonia Mapping |
|---|---|---|
Default | Normal text area | StandardCursorType.Ibeam |
ResizeNS | Top or bottom edge handle | StandardCursorType.SizeNorthSouth |
ResizeEW | Left or right edge handle | StandardCursorType.SizeWestEast |
ResizeDiagNWSE | TopLeft or BottomRight corner | StandardCursorType.TopLeftCorner |
ResizeDiagNESW | TopRight or BottomLeft corner | StandardCursorType.TopRightCorner |
Move | Image body (drag-to-move) | StandardCursorType.SizeAll |
// Wire cursor updates in your Avalonia control
controller.RequestCursorUpdate = (cursor) =>
{
Cursor = cursor switch
{
EditorCursor.ResizeNS => new Cursor(StandardCursorType.SizeNorthSouth),
EditorCursor.ResizeEW => new Cursor(StandardCursorType.SizeWestEast),
EditorCursor.ResizeDiagNWSE => new Cursor(StandardCursorType.TopLeftCorner),
EditorCursor.ResizeDiagNESW => new Cursor(StandardCursorType.TopRightCorner),
EditorCursor.Move => new Cursor(StandardCursorType.SizeAll),
_ => new Cursor(StandardCursorType.Ibeam),
};
};
Exporting Images
When iterating with DocumentReader, image runs are identified by ContentRun.IsImage == true.
The PNG bytes are available via ContentRun.ImageData.
foreach (var run in block.Runs)
{
if (run.IsImage)
{
// Embed as base-64 in HTML
var b64 = Convert.ToBase64String(run.ImageData);
html += $"<img src='data:image/png;base64,{b64}' "
+ $"width='{run.ImageWidth}' height='{run.ImageHeight}'>";
// Or save to a file
File.WriteAllBytes("image.png", run.ImageData);
}
}