Repository navigation
Releases: centwon/WinUIRichEditor
Release list
1.2.0 — 동결 이후 첫 기능 릴리스 (추가만, breaking 없음)
공개 표면은 추가만(바인딩 가능한 의존성 속성 7개)이라 코드 수정 없이 올릴 수 있다. 다만 동작 변경이 몇 가지 있고 두 가지는 기존 문서에서 눈에 보인다 — 아래를 먼저 읽을 것. 전체 목록은 CHANGELOG.
⚠ 올릴 때 확인할 것
- 줄 간격이 HWP 기준:
LineSpacing은 "글자 크기 × 값"이고, 설정 없는 문단은 **160%**로 그려진다(전에는 폰트 고유 높이 ≈120%). 문단 기본 아래 여백은 10px → 0. 저장 형식은 그대로지만 기존 문서가 더 넓게 보인다. - 제목의 굵게·크기가 글자 속성: 제목 안에서 굵게를 끄거나 크기를 바꿀 수 있고, 제목을 다시 적용하면 제목 모양으로 되돌아간다. 옛 문서는 편집기가 받을 때 한 번 변환한다(
"HeadingFormat": 1) — 직렬화기는 변환하지 않으므로, 편집기 없이 옛 파일을 HTML/RTF로 바로 바꾸는 호스트는 결과가 달라진다. - 굵게·기울임·밑줄·취소선 토글이 Word 방식(전부 그 서식이면 끄고, 아니면 모두 켠다).
- 인쇄와
SavePdf가 글자로 나간다(전에는 페이지마다 비트맵) — 프린터 해상도, PDF에서 글자 선택·검색 가능. - 문서가 아닌 입력을 거부한다:
LoadJson/LoadPackageAsync가 예외를 던지고 열린 문서를 지킨다(전에는 조용히 빈 문서로 바꾸고 "저장됨"으로 표시해 다음 저장이 원본을 덮었다). - 링크는 http/https만 연다(문서 속
ms-msdt:같은 프로토콜 링크가 클릭 한 번에 실행되던 것 — 보안). - 그 밖에: 페이지 설정이 호스트 기본값에서 시작 ·
AllowImages/AllowTables가 리치 붙여넣기에도 적용 · 언두 예산이 요소 수 기준 · 영어 UI의 셀 세로 가운데 "Middle".
새 기능
벡터 인쇄·PDF(AOT에서도 인쇄 대화상자) · 표·그림을 끌어서 옮기기(Ctrl=복사) · 그림을 가로·세로로 따로 늘리는 손잡이 · 한 셀 선택(F5)과 Shift+방향키 셀 블록 · 찾기 UI(Ctrl+F/H·F3, 뷰의 찾기 막대) · XAML에서 바인딩할 수 있는 툴바·뷰 속성 7개 · 벡터 툴바 아이콘.
검증
테스트 391 → 815. AOT self-contained 게시 후 JIT↔AOT 동등성 스윕(두 페이지 모두 문서 바이트 동일, AOT 전용 fault는 알려진 LineMetricsOf 하나) · NuGet 소비자 스모크 15/15(PackageReference로 세운 별도 앱에서 빌드·실행).
1.1.1 — AOT에서 깨져 있던 줄 메트릭
1.1.0은 Native AOT에서 깨진 채 나갔다. 그것이 이 릴리스의 이유다.
CanvasTextLayout.LineMetrics가 반환하는 구조체 배열이 bool을 품어 non-blittable이고, CsWinRT가 AOT에서 이 배열을 마샬링하지 못한다. 이 API를 쓰는 8곳이 전부 "줄 없음"으로 폴백해서 AOT 빌드는:
- 줄바꿈을 넘는 세로 캐럿 이동이 동작하지 않고
- 글머리표가 베이스라인 정렬을 잃고
- 페이지네이션이 모든 문단을 한 줄로 쟀다
빌드도 되고 렌더도 되니 멀쩡해 보였다 — 1.1.0의 릴리스 노트조차 "AOT 재실측"을 통과로 적었다. 게시와 렌더만 봤기 때문이다. JIT 빌드는 영향이 없다(이 API는 AOT에서만 실패한다).
올릴 때
- Breaking 없음. 공개 표면은 변화조차 없다 —
PublicAPI.Shipped.txt567줄 그대로. 코드 수정 없이 올라간다. - Native AOT로 게시하는 소비자는 올릴 것.
- ⚠ 동작 변경: 자릿수가 int를 넘는 RTF 제어어 파라미터는 더 이상 "손상"이 아니다 —
TryParse가 거부하던 일부 파일이 열린다(손상 판정은 이제 절단만 본다). 상류AvaloniaRichEditor와 수렴한 결정으로, malformed 토큰은 damaged 문서가 아니다. - 최소 요구
Microsoft.WindowsAppSDK.WinUI는 2.2.1 그대로.
그 밖의 수정
외부 감사 보고서 31건을 코드와 1:1 대조해 나온 수정 13건 — 표 열 리사이즈·TableBlock.Extract 크래시, RTF 토큰화가 문서를 통째로 날리던 것, Backspace/Delete/화살표의 서로게이트 페어 분할, 셀 안 이미지 우클릭 미인식, 셀 안 블록↔인라인 변환 무동작(양방향), CSS 퍼센트 색상, UIA 텍스트/선택 알림 부재(스크린 리더). 메모리: 문서에서 빠진 문단을 붙잡던 곁캐시, 언두 체크포인트의 표 격자 할당 −40%(표가 지배적인 문서). 게시 어셈블리에서 빌드 머신 절대 경로 제거.
검증
- 테스트 304 → 391, 빌드 0 warn / 0 err, 퍼즈 4000시드 클린
- AOT 동등성 실측(신규
tools/fault-sweep.ps1): 같은 편집 순서를 JIT와 AOT 게시본에 주입해 ① 라이브러리가 보고한 fault 집합 ② 되읽은 문서를 비교 — AOT 전용 fault는 줄 메트릭 폴백 하나뿐이고 문서는 두 페이지에서 바이트 동일. 1.1.0이 놓친 것이 정확히 이 비교다. - NuGet 소비자 스모크 15/15 — 별도 unpackaged WinUI 3 앱이
PackageReference로 빌드·실행 - 언두/찾기 축에 검증 44개 신설(그 전엔
Undo()·FindNext를 호출하는 테스트가 하나도 없었다)
전체 변경은 CHANGELOG.md 참조.
1.0.0 — public API frozen
The public API is frozen. From here the surface follows SemVer — no breaking change without a major bump — and it is tracked in PublicAPI.Shipped.txt (553 entries), so a change to it shows up as a build warning and a diff rather than shipping unnoticed.
What made this 1.0 is verification depth, not new features. The editor has been feature-complete for a while; this release is what a full comparison against AvaloniaRichEditor 1.0, four audit passes and a randomized edit-sequence fuzz turned up — 34 defects, several of them data-losing.
⚠️ Upgrading from 0.9.x
One breaking removal:
Formatters.RoundTripHarnessis gone. It was a development tool that only used the public formatter API, so it moved into the demo project. Removing public API is breaking, which is why it happens at the freeze rather than after it.
Two behaviour changes — check these if you host the control:
- A damaged document now throws instead of reading as an empty one.
LoadJson/LoadJsonAsync/LoadPackageAsyncraiseJsonException/InvalidDataException. Swallowing the error was the outcome that lost data: a host could not tell "this file was empty" from "this file is damaged", showed a blank editor, and the next save overwrote a recoverable file with nothing. If you call these, handle the exception — the open document is left alone when they throw. - RTF table output changed shape. A horizontal merge is now expressed geometrically (one cell whose
\cellxsits at the merged edge) because HWP dissolves the flag form that Word also accepts, and a table nested in a cell is now real\itapnesting instead of flattened text.
Fixed
Reachable from a paste — any pasted or opened file is untrusted input:
- A single
<td colspan="100000000">exhausted memory in the HTML importer.rowspanwas bounded by the rows that exist;colspanhad no ceiling at all. - A
.flow/JSON document declaring a huge table exhausted memory before a cell was read — the constructor was sized from the file's own numbers, and everything it allocated was discarded three lines later. - A JSON
nullinsideBlocks/Cells/InlinesthrewNullReferenceException. - Ordinary Word RTF imported with text missing. The pending run was flushed at an ignorable group's closing brace, by which point the skipped destination was active and threw it away — and Word writes such groups routinely (bookmarks, fields, a nested table's properties). A nested table's row definition also restarted the row mid-cell, discarding what the parent cell had accumulated.
Data loss in round trips:
- A document of indented list items exported to HTML came back as literal markup text. Indent the only list item in a document and the export opens
<ol><ol><li>…; the importer looked at direct<li>children only, found nothing, produced zero blocks, and the raw-text fallback dumped the whole file as text. - Several paragraphs in a table cell collapsed into one on every RTF round trip (and on every Word/HWP paste): intra-cell
\parwas read as a newline rather than a paragraph break. - An inline table alone in its paragraph — the ordinary "treat as character" shape — swallowed the paragraph before it when reloaded, in both HTML and RTF.
- Separators accumulated as content on each save/load cycle: a space after an inline table in HTML, a newline before a nested table in RTF, a
<br>after a block element in a cell.
Interoperability (verified in Word, HWP and a browser):
- Horizontal cell merges dissolved in HWP.
- Inline tables now survive an HTML and an RTF round trip, and sit inside the text line in browsers and Word instead of stretching to a full-width band.
- Cell background colour is written to and read from RTF (
\clcbpat) — it was dropped even by our own reader.
Editing:
- The toolbar silently took keyboard focus: the caret vanished and typing stopped, while the buttons still appeared to work because the commands ran against the remembered caret position. Nothing in the strip takes focus now, and pickers hand it back when they close.
- The caret was drawn below the text, and vertical arrow movement broke, at line spacing above 100% — the caret is placed on the line's baseline now, and movement steps over the line rather than over the caret.
- Turning on a list detached an inline table or image in the same paragraph, leaving the caret pointing into a subtree no longer in the document.
- A block image inside a table cell could not be selected, resized or deleted at all.
GetPlainText()andGetImageCount()reached only one level deep, so text inside nested and inline tables was invisible — including to assistive technology, which reads the former.
Added
- Nine behaviour flags became dependency properties, so they can be bound and styled:
AllowImages,AllowTables,AllowRichPaste,AllowFindReplace,AllowLocalFileImages,AllowRemoteImagesOnPaste,AutoLinkOnType,MaxRecommendedImages,ShowFormattingMenu. Same names, same defaults. RichEditor.FocusEditor()— returns keyboard focus to the editing surface without moving the caret. Focus lives on an inner canvas, soFocus()on the control does not reach it.- XML documentation now ships with the package, so the library's comments reach IntelliSense.
- A randomized edit-sequence fuzz over the document model (
DocumentFuzzTests): structural invariants after every step, plus a two-cycle round-trip comparison through all four formats. It found three of the defects above.
Verified
Build 0 warnings / 0 errors · 106 headless tests · Native AOT publish builds, runs and renders (self-contained, 15 MB native exe) · package contents checked (dll + XML docs + README, no stray files) · a separate consumer app consuming the NuGet package builds and runs (13/13 checks) · Word, HWP and browser interoperability checked by hand.
Known limitations
- HWP does not implement RTF nested tables. It drops
\nestcelland runs the nested cells' text together. Our output is byte-identical to what AvaloniaRichEditor 1.0 emits, and Word reads it correctly. - A table whose every row is merged identically reads back from RTF as one wide column — nothing in the file then reveals the underlying grid. It renders identically.
- A nested table's column widths come back at the default: they live in an ignorable group.
- HTML loses one space inside a merged cell on the second round trip. Deliberately not fixed at 1.0 — HTML whitespace handling is separately tuned for browser copy, Word paste and
<pre>, and that risk was not worth a cosmetic space on release day.
Requires Windows App SDK 2.2.1 or later. Apps that publish self-contained must reference the Microsoft.WindowsAppSDK meta-package (only it carries the redistributable runtime).
Full detail, including the reasoning behind each fix, is in CHANGELOG.md.
WinUIRichEditor 0.9.1
⚠️ 0.9.0 대신 이 버전을 쓰세요. 0.9.0의 유일한 실질 차이는 잘못 올라간 SDK 하한선입니다.
Fixed
최소 요구 Microsoft.WindowsAppSDK.WinUI 2.3.2 → 2.2.1로 환원
0.9.0이 요구한 2.3.2는 소비자가 쓰는 메타패키지 Microsoft.WindowsAppSDK가 실제로 주는 WinUI보다 높습니다:
| 메타패키지 | 딸려오는 WinUI |
|---|---|
| 2.2.0 | 2.2.1 |
| 2.3.1 | 2.3.0 |
그래서 프레임워크 의존 앱이 자기가 배포하는 런타임보다 높은 WinUI로 컴파일되어 어긋났고, self-contained로 전환하거나 편집기를 되돌리는 수밖에 없었습니다.
라이브러리는 2.3에서 추가된 API를 하나도 쓰지 않습니다. 2.2에서 실측 확인했습니다 — 솔루션 빌드 0/0, 테스트 74/74, AOT self-contained 게시 성공(81MB, 네이티브 exe 14.2MB), 실행 + Win2D 렌더 정상.
하한선에 적용되는 규칙 두 가지를 csproj 주석에 못박았습니다:
- 실제로 빌드·테스트·AOT가 통과하는 가장 낮은 버전일 것
- 소비자 메타패키지가 주는 WinUI 버전을 넘지 말 것
문서
소비자 앱이 self-contained 배포(WindowsAppSDKSelfContained=true)를 한다면 메타패키지 Microsoft.WindowsAppSDK를 참조해야 한다는 점을 README에 명시했습니다. Microsoft.WindowsAppSDK.Runtime(재배포 런타임)은 메타패키지에만 딸려옵니다 — 이 라이브러리가 분할 컴포넌트를 참조하는 건 컴파일 표면만 필요한 라이브러리의 사정이고, 앱이 그걸 따라 하면 번들할 런타임이 없어집니다.
0.9.0의 내용
기능·수정 사항은 모두 0.9.1에 포함됩니다. 0.9.0 릴리스 노트 참조.
WinUIRichEditor 0.9.0
원본 AvaloniaRichEditor와의 양방향 수렴(파리티 갭 분석 → 원본→포트 소스 호환 → 포트→원본 역이식 → 원본 감사 결과의 역방향 스윕)과 HWP/Word 상호운용 교정이 이 릴리스의 축입니다. 6·7차 전수 리뷰까지 포함해 결함 20건을 고쳤고, 테스트는 67 → 74로 늘었습니다.
⚠️ 업그레이드 전 확인 (breaking / 동작 변경)
- 최소 요구
Microsoft.WindowsAppSDK.WinUI2.2.1 → 2.3.2. 2.2.x에 머무는 앱은 설치할 수 없습니다. - 동기
ParseHtml/LoadHtml/InsertHtml이 원격(http) 이미지를 더 이상 받지 않습니다. UI를 최대 5초 멈추던 동기 다운로드를 제거했습니다. 원격 이미지가 필요하면ParseHtmlAsync/LoadHtmlAsync를 쓰세요. AllowRemoteImagesOnPaste가 붙여넣기 전용이 아닙니다 —LoadHtml/LoadHtmlAsync/InsertHtml까지 관장합니다. 끈 호스트가LoadHtmlAsync에서 실제로 원격 요청을 보내던 버그의 수정입니다.- 목록 토글 해제가
ListLevel/ListMarker까지 지웁니다. 중첩 목록을 끄면 들여쓰기도 함께 사라집니다(종전에는 마커만 사라지고 레벨×20px 들여쓰기가 유령처럼 남았습니다). - RTF 출력 형태가 크게 바뀌었습니다 — 행 정의 2회 방출, 모든 문단에 명시 정렬(
\ql포함), 인라인 표를 실제\trowd행으로 승격. HWP/Word가 표와 정렬을 제대로 읽도록 하기 위한 변경입니다. - 로컬라이제이션 키
ListNone제거, 툴바 ▾ 드롭다운과 우클릭 메뉴의 "없음" 항목 제거.
주요 추가
- 읽기 전용 뷰어 캐럿 옵트인
ShowCaretWhenReadOnly(기본 off, 깜빡이지 않음) - 원본 API 소스 호환 계층:
SetFontFamily/InsertImageBytes/PasteFromClipboardAsync별칭,FocusDocumentEnd(),InsertInlineTable(r,c),InsertImageFromFileAsync(nint),RichEditorView.ShowStatusBar/ZoomFactor
검증
빌드 0 warn / 0 err · 테스트 74/74 · Native AOT self-contained 게시 재검증(85.2MB, 네이티브 exe 14.2MB, 관리형 IL 어셈블리 0개, 실행 + Win2D 렌더 확인) · HWP 붙여넣기 사람 검증(블록 표·인라인 표·정렬)
전체 변경 내역은 CHANGELOG.md 참조.
v0.8.1 — printing fixes
NuGet: WinUIRichEditor 0.8.1
dotnet add package WinUIRichEditor --version 0.8.1
A patch release fixing printing (RichEditorPrintHelper).
⚠️ Behavior change:ShowPrintUIAsync'sdpidefault drops from 300 to 150. If you rely on the
default resolution, pass it explicitly. See Changed below.
Fixed
RichEditorPrintHelper.ShowPrintUIAsyncnever printed: the dialog opened but preview stayed on
"loading" and no output was produced. ThePrintDocumentand itsIPrintDocumentSourcewere locals,
and nothing in the print system holds a managed reference back to them — they were collectable as soon
as the method awaited, taking the pagination/preview/page callbacks with them. Both are now rooted in
static fields for the life of the job and released onPrintTask.Completed.ShowPrintUIAsyncreturnedfalseafter the dialog had already opened, so callers with a fallback path
(e.g. export to PDF) showed it on top of a live print dialog. It now reports success once the dialog has
asked for the document, regardless of whatShowPrintUIForWindowAsyncreports afterwards.- Page rasters no longer go through
WriteableBitmap.PixelBuffer.AsStream(), which only covers buffers
backed by managed arrays under CsWinRT (.NET 5+) and not the native buffer XAML returns; they are encoded
to an in-memory PNG and decoded throughBitmapImageinstead. PDF export was never affected — it uses a
separate raster path.
Changed
ShowPrintUIAsyncrenders every page before opening the dialog — thePrintDocumentcallbacks are
synchronous and image decoding cannot be awaited inside them. Itsdpidefault drops from 300 to 150
(matchingSavePdf), holding roughly 8 MB per A4 page; lower it further for very long documents.ShowPrintUIAsyncnow returnsfalseimmediately on hosts with dynamic code disabled (Native AOT, or
any build settingPublishAot, which bakesIsDynamicCodeSupported=falseintoruntimeconfig.json).
PrintManagerInterop.ShowPrintUIForWindowAsynccasts its result toIAsyncOperation<bool>through
IDynamicInterfaceCastable, and CsWinRT 2.2 refuses to resolve that ABI helper without dynamic code —
the throw lands after the native call has already put the dialog on screen, stranding a window with no
document behind it. NeitherCsWinRTAotOptimizerEnabled(it emits CCW vtables only) nor manual
registration (WinRT.TypeExtensions.RegisterHelperTypeand the ABI types are internal) covers that
instantiation, so AOT callers need their own path — e.g.SavePdfand a viewer.
v0.8.0 — first release (AvaloniaRichEditor 0.8.0 parity)
NuGet: WinUIRichEditor 0.8.0
dotnet add package WinUIRichEditor --version 0.8.0
Requires .NET 10 (net10.0-windows10.0.26100.0), Windows App SDK 2.2+, Win2D 1.4.0.
First published release — feature parity with AvaloniaRichEditor 0.8.0. The port was developed in phases
(see Project_Roadmap.md):
Phase 0 — Scaffold
- WinUI 3 class library
src/WinUIRichEditor+ unpackaged WinExe demosamples/WinUIRichEditor.Demo,
solutionWinUIRichEditor.slnx. .NET 10, Win2D 1.4.0,IsAotCompatible, code-only control. - Verified the Win2D
CanvasControl+CanvasTextLayoutrendering pipeline end to end.
Phase 1 — Document model + formatters
- Ported
Documents/(FlowDocument, Paragraph, Run, TableBlock, …) andFormatters/
(JSON source-gen serializer,.flowpackage, HTML, RTF, PDF) — byte-compatible document format. - Type swaps:
IBrush?→Windows.UI.Color?,TextDecorationCollection→[Flags] TextDecorationFlags,
FontWeightenum→Windows.UI.Text.FontWeightstruct,Avaloniabrushes→WinRT colors,
Bitmap→CanvasBitmap(decode deferred to the render layer; natural size parsed from headers viaImageInfo).
Phase 2 — Rendering engine
RichEditor : ContentControlhosting aScrollViewer+CanvasVirtualControl.BuildTextLayout→CanvasTextLayoutwith per-range formatting; one layout drives render/caret/hit-test.- Inline images via
SpacerInlineObject : ICanvasTextInlineObject(U+FFFC space reservation) + post-draw
bitmap from character regions; async image decode cache (ImageCache). - Recursive table rendering (
LayoutTable), inline tables, run-background highlight.
Phase 3 — Input
- Focus, pointer hit-testing (
HitTest→ offset), caret (blink,GetCaretPosition), drag/Shift selection. - Self-contained editing core: text insert, Backspace/Delete (+ paragraph merge), Enter split,
Shift+Enter soft break, arrow/Home/End navigation, Ctrl+A.
Phase 4 — IME
- Korean/CJK composition via
CoreTextEditContext(works in WinUI 3 desktop on Windows 26100). - IME buffer = caret paragraph; composing text applied by
TextUpdating, underline viaFormatUpdating. - Hybrid input:
CharacterReceivedis suppressed only while composing (plain English still arrives there).
Phase 5 — Editing features
- Undo/redo (
UndoManager, snapshot + coalescing), Ctrl+Z/Y. - Clipboard: copy/cut/paste (internal rich → external HTML → plain text),
CF_HTMLout. - Formatting commands + demo toolbar: bold/italic/underline/strikethrough, foreground/highlight color,
alignment, headings, blockquote, bullet/numbered lists, indent. - Find / replace with wrap-around and caret scroll-into-view.
- File open/save (
.flow/.json/.html) via pickers + HWND interop. - Right-click context menu, block image insert, table cell editing (click-in, type, IME,
row auto-grow).
Parity completion — tables, images, page view, host controls
- Tables: Tab cell navigation; right-click row/column insert·delete, cell merge/split; draw-to-size
toolbar grid picker; column-width & row-height resize (drag borders) for top-level and nested/inline
tables; table block selection; block↔inline ("treat as character") toggle; inline-table cell editing +
full arrow-key caret traversal (horizontal cell entry/exit and vertical row navigation / box exit). - Images: clipboard image paste; block & inline image selection with aspect-locked resize handles
(resize cursor); right-click size presets / replace / save / delete; inline image insert. - Clipboard: RTF, image, and Excel/TSV→table paste connected.
- Formatting UI: font/size/color pickers, custom line spacing render + commands, hyperlink edit
dialog (ContentDialog), format painter. - Page view / print / PDF:
PageSize/Orientation/ShowPageBoundaries, stacked page render with
line-aware page breaks, headers/footers/page numbers;RenderPrintPage,SavePdf(rasterized → PDF). - Host controls: drop-in
RichEditorView+RichEditorToolbar;EditorModepresets + feature flags;
RichEditorLocalization(KO/EN); accessibility automation peer (IValueProvider). - Convenience API:
ToHtml/LoadHtml,ToRtf/LoadRtf,ToJson/LoadJson(Async),
SavePackageAsync/LoadPackageAsync,Clear,GetPlainText,CanUndo/CanRedo,InsertHtml,
GetCaretFormat,GetStatus,StatusChanged. - Tests:
tests/WinUIRichEditor.Tests(xUnit.v3, headlessdotnet test,OutputType=Exe) — 26
model/formatter tests. Model/formatterFontWeightdefaults moved to plain struct values
(FontWeightValues) so that layer constructs without activating the WinUI runtime.
Parity polish (2026-06-24)
- System font enumeration:
DefaultFontFamilynow defaults to the OS UI font (e.g. "맑은 고딕" on
Korean Windows) viaSystemFontInfo(P/Invoke). NewRichEditor.FontFamilyChoicesenumerates the
installed fonts throughCanvasFontSet.GetSystemFontSet()with names localized to the OS UI language;
the toolbar combo and a new right-click font submenu are built from it (each item rendered in its own face). - IME: candidate-window placement fixed —
CaretScreenRectnow returns true physical screen coordinates
(TransformToVisual(null)× rasterization scale +GetActiveWindow/ClientToScreenclient origin), so the
IME UI tracks the caret. Composition now integrates with undo (PushUndo("ime")coalesces one composition
into a single undo group;CompositionCompletedresets the key). Table-cell IME verified. - Engine-level zoom: new
RichEditor.Zoom([0.25–5.0]) scales the Win2D drawing session and canvas and
folds intoViewToDoc/DocToView, so text stays crisp at any factor and hit-testing/caret stay correct
(continuous mode reflows to the zoomed width). - Host controls:
RichEditorViewgained a page/zoom chrome bar — zoom combo, paper-size combo
(Continuous/A4/A3/A5/B4/B5/Letter/Legal/Tabloid →PageSize+ page outline), orientation combo, and a page
count in the status bar.
Native AOT — works end-to-end
- Self-contained Native AOT publish (
samples/.../PublishProfiles/win-x64.pubxml:PublishAot+
SelfContained+PublishSingleFile+PublishTrimmed,WindowsAppSDKSelfContained=true) builds
(0 trim/AOT warnings, ~14 MB native exe), runs, and renders — Win2DCanvasTextLayout/CanvasDevice
andCanvasFontSetall work under AOT. The earlier "crashes at startup incombase 0x80004005" was a
framework-dependent-only limitation; the self-contained bundle supplies WinRT activation. - Build workaround (still required):
GenerateLibraryLayouton the library + an MSBuild target stripping
the stalewindowsappsdk.winui\1.8PRIs that Win2D 1.4.0 pulls in (conflicts with WinAppSDK 2.2's PRI).
Removable once a WinAppSDK-2.x-aligned Win2D ships (latest is still 1.4.0, depending on WinUI 1.8.x).
API surface, toolbar composition & document-format verification (2026-06-25)
- Events & appearance: added
TextChanged/SelectionChanged/DocumentChanged(driven off the
PushUndomutation choke point + a selection snapshot, flushed viaRaiseStatusChanged) and
SelectionBrush/CaretBrushdependency properties (the previously hard-coded caret/selection colors). - Icon abstraction: ported
RichEditorIcon(45-value enum, original ordinals) +RichEditorIcons.Provider
host override. Built-in icons are Segoe Fluent IconsFontIcons (ToolbarIcons), resolved
Provider > FontIcon > text—FontIconworks in the code-only control (onlySymbolIconhad crashed). - Toolbar composition → original parity:
LeadingItems/TrailingItemshost slots + a custom
ToolbarWrapPanel(WinUI ships noWrapPanel); 40-swatch palette + hex-input color pickers with a
caret-reflecting swatch; list combo-boxes[icon | marker preview | ▾ style menu]+ public
SetListStyle(ListMarkerStyle); line-spacing box[↕ | editable % | ▾ presets | ▲▼ steppers]. Removed the
quote toolbar button (the original has none — available via right-click /ToggleQuote()). - Objects: one image insert button; image and table gained a checkbox "treat as character"
(ToggleMenuFlyoutItem) toggling block↔inline (ConvertImageBlockToInline/ConvertInlineImageToBlock).
InsertTablenow sizes equal columns to the document (or enclosing cell) content width. - View file actions:
RichEditorViewgainedShowFileActions+ Export/Import (JSON/.flow/HTML/RTF via the
picker,WindowHandlefor unpackaged interop) + a host-handledPrintRequested, in the toolbar's trailing slot. - Demo: rebuilt as a four-page nav shell — control / read-only / control+toolbar / view.
- Document format: confirmed the
.flow/JSON format is byte-compatible with AvaloniaRichEditor by loading
a.flowsaved by the original (paragraphs + image + table round-tripped). Found that the original stores
colors as CSS named colors ("Blue"), soColorUtilwas expanded from ~10 names to the full CSS/X11 set. - Context menu → original parity: rebuilt with organized Character-format and Paragraph submenus (size,
color, highlight, font, clear; alignment, list + bullet/number styles, heading, indent, quote), a Margin
submenu, clipboard Delete, Undo/Redo, inserts, a concise dedicated link menu (with copy-address), a
read-only branch, andRichEditorIconsglyphs on items. - Two-step table insert: pick rows×columns from the 8×10 hover grid (toolbar button or context menu —
the menu opens the grid as a flyout, since WinUI menus can't host a grid), then drag from the caret on the
document to set the table's width/height. The table inserts at the caret with the picked rows×columns; the
rubber-band (anchored at the caret) previews the grid at the dragged size, and a plain click uses the
default s...