本日は、私が最近ほぼ開発を完了した CodeWF.Markdown についてお話しします。
これは C# + Avalonia 12 + Markdig をベースにした Markdown レンダリングコントロールです。もともとは CodeWF.AvaloniaControls の一部でしたが、後に Markdown 関連のコードを独立したリポジトリと NuGet パッケージ群に分割しました。レンダリングコントロール・テーマリソース・サンプルプログラム・テストが、Markdown プレビューという目的に沿って整理されています。
このコントロールの位置づけは「Markdown を HTML に変換して WebView に詰め込む」ではなく、「Markdown AST を Avalonia コントロールツリーとしてレンダリングする」ことです。これにより、テーマ・フォント・テキスト選択・コピー・画像プレビュー・コードブロックツールバー・インクリメンタルリフレッシュといったデスクトップコントロールの機能を、すべて Avalonia の枠組みで処理できます。
今回は単なるスクリーンショットだけでなく、サンプルプログラムを起動して改めて素材をキャプチャし直しました。静的スクリーンショットと GIF アニメーションを含めています。まずは機能ツアーをご覧ください。

なぜ Markdown コントロールを独立させたのか
私は普段、記事を書いたりドキュメントを整理したりツールボックスページを作成したりする際、ローカルで Markdown をプレビューできるコントロールをよく必要とします。WebView でも実現できますが、HTML/CSS の注入、スクリプトのセキュリティ、プラットフォームごとの差異、コピー動作、画像プレビューウィンドウ、テーマリソースの同期など、別の体系に問題を持ち込むことになります。
Avalonia のデスクトップアプリケーションでは、Markdown を直接コントロールツリーに変換する方が自然です。
- 段落・見出し・リスト・引用・テーブルは Avalonia コントロール。
- コードブロックにはコピーボタン・言語識別子・ハイライトコントロールを付けられる。
- 画像は Avalonia のウィンドウ機能を使ってプレビュー・ズーム・回転・保存が可能。
- テーマは
ResourceDictionaryで管理でき、CSS を別途用意する必要がない。 - 複数の
MarkdownViewerを同一ウィンドウ内で異なるレイアウト密度で表示できる。
これが CodeWF.Markdown の主な方向性です。Avalonia アプリケーションに直接組み込める Markdown Viewer を提供し、ブラウザをさらにラップしないことです。
現在のパッケージライン
現在、リポジトリには主に以下のパッケージラインがあります。
CodeWF.Markdown:フルバージョンのMarkdownViewer。コードハイライト、画像/SVG、数式、多言語リソース、インクリメンタルレンダリングなどに対応。CodeWF.Markdown.Themes:フルバージョンのコントロールテンプレートとタイポグラフィテーマリソース。CodeWF.Markdown.Lite:軽量版 Viewer。パッケージ参照は Avalonia と Markdig のみで、基本的な Markdown プレビューが必要なシーン向け。CodeWF.Markdown.Lite.Themes:軽量版のテーマリソース。CodeWF.Markdown.Sample:フルバージョンのサンプルプログラム。CodeWF.Markdown.Lite.Sample:軽量版のサンプルプログラム。tests/CodeWF.Markdown.Tests:レンダリングモデル、差分サービス、テーマリソース関連のテスト。
リポジトリはこちら:https://github.com/dotnet9/CodeWF.Markdown
現在のスクリーンショットのリポジトリバージョンは 12.0.3.1 です。ローカルで以下を実行しています。
dotnet build CodeWF.Markdown.slnx
dotnet test CodeWF.Markdown.slnx --no-restore
テストプロジェクトは現在 13 ケース合格しています。ビルド時に .NET プレビュー版 SDK の警告が出ますが、コンパイル警告やエラーはありません。
サンプルプログラム:左側で編集、右側でリアルタイムプレビュー
まずメインのサンプルプログラムを見てみましょう。画面は左右に分かれており、左側が Markdown 入力、右側がレンダリングプレビューです。

上部のツールバーにはいくつかの重要な設定があります。
- アプリテーマ:ライト、ダーク。
- タイポグラフィテーマ:Markdown 内容のアクセントカラー、見出しの下線、引用の背景、テーブルスタイル、コード領域のスタイルなどを切り替え。
- コンパクトレイアウト:フォントサイズ、行間、ブロック間の余白を詰め、情報密度の高いツールインターフェースに適した表示。
- 言語:現在のサンプルは簡体字中国語、繁体字中国語、英語、日本語に対応。
左側のドロップダウンには複数の Markdown サンプルファイルが読み込まれており、基本要素、タイポグラフィテーマ、コードとテーブル、リストと引用、画像リンク、インクリメンタルレンダリングの負荷など、さまざまなシナリオをカバーしています。
このサンプルプログラムは、完全な Markdown エディタを作るためのものではなく、実際のデスクトップウィンドウでのコントロールの振る舞い(ウィンドウリサイズ、スクロール、テーマ切り替え、多言語対応、編集領域の継続的な変更、プレビュー領域のインクリメンタルリフレッシュ)をまとめて検証するためのものです。
一般的な Markdown 要素
フルバージョンでは Markdig を使って Markdown を解析しています。一般的な要素は基本的にコントロールツリーとしてレンダリングされます。
- 見出し、段落、太字、斜体、打ち消し線
- インラインコードとコードブロック
- 順序付きリスト、順序なしリスト、ネストしたリスト
- タスクリスト
- 引用ブロック(引用内のリスト、コード、テーブルを含む)
- テーブル、リンク、水平線
- 脚注、数式などの拡張構文
- HTML inline と HTML block のフォールバック
ここで一つトレードオフがあります。CodeWF.Markdown は HTML を実行しません。つまり、Markdown 内に <section> や <span> などが書かれている場合、それらはブラウザのように HTML やスタイルを実行するのではなく、テキストとして安全に表示されます。デスクトッププレビューコントロールとしては、このデフォルト動作の方が安定しており、特に外部入力やユーザーが編集したコンテンツを表示する場合に適しています。
コードブロック:ハイライト、言語ラベル、コピーボタン
コードブロックは Markdown プレビューで見落とされがちですが、実際の体験に大きく影響する部分です。

現在のコードブロックには言語ラベルとコピーボタンが表示され、TextMateSharp を使用したシンタックスハイライトが適用されます。スクリーンショットでは、JSON、C#、TypeScript、Shell などのコードブロックの表示例を確認できます。
コードブロックを単なるテキストボックスにしなかった理由は、Markdown でドキュメントを読む際にコードをコピーする必要が頻繁に発生するからです。また、テーマごとにコード領域の背景色、枠線、フォントサイズ、ボタンサイズもリソースに合わせて変化させる必要があります。
コントロールには CodeBlockToolRender イベントが用意されており、外部プロジェクトがコードブロックのツール領域に独自のボタンを追加できます。たとえば、コードブロックに「実行」「コマンドとしてコピー」「ターミナルに送信」といったエントリを追加する場合、それらを MarkdownViewer 内部にハードコードすべきではありません。
テーブルと長いコンテンツ

テーブルは単純な文字列連結ではなく、行・列・セルとしてレンダリングされます。ヘッダーの背景色、枠線、文字色はすべてテーマリソースから読み取ります。
この点は一見地味ですが、中国語のドキュメントでは重要です。多くの Markdown ドキュメントには、長い中国語の説明、英語の識別子、リンク、バージョン番号、パスが含まれます。テーブルが自然に改行できないと、プレビュー領域が横方向にすぐにはみ出してしまいます。

サンプルでは、整列したテーブルや複雑なセル内容を配置し、以下の点を確認しています。
- ヘッダーとコンテンツ領域の枠線が安定しているか
- 長いテキストがセル内で折り返されているか
- インラインコード、太字テキスト、リンクがテーブル内で正しく表示されるか
- テーマ切り替え後、テーブルのスタイルが即座に更新されるか
画像、SVG、プレビューウィンドウ
Markdown 内の画像は独立したコントロール MarkdownImage として扱われ、単に画像をドキュメントに埋め込むだけではありません。

ローカル画像、URL 画像、Data URI、SVG 画像に対応しています。

画像の読み込みに失敗した場合はフォールバックテキストが表示され、Markdown ドキュメント全体のレンダリングが中断されることはありません。

画像をクリックするとプレビューウィンドウが開きます。

プレビューウィンドウでは以下の操作が可能です。
- 縮小、拡大
- 1:1 表示
- ウィンドウにフィット
- 左回転、右回転
- 名前を付けて保存
SVG 画像はプレビュー時に追加でビットマップに変換され、プレビュー入力として使用されます。これにより、元の Viewer が画像リソースをクリアした後でもプレビューウィンドウが無効になることを防ぎます。最近の対応では、画像リソースの解放も重点的に処理しました。Markdown が置き換えられた場合、コントロールがビジュアルツリーから外れた場合、画像読み込みがまだ完了していない場合に、未完了のタスクをキャンセルし、ビットマップを解放します。
数式と化学式

フルバージョンでは Sylinko.CSharpMath.Avalonia を統合し、数式レンダリングに対応しています。最近追加した内部 MarkdownMathView は、数式の前景色を現在の Markdown テーマに追従させるためのものです。

こうした細部はライトテーマでは目立ちませんが、ダークテーマに切り替えると問題が明らかになります。通常のテキストは明るい色になるのに、数式が固定の黒色のままだと読みづらくなります。数式も Markdown テーマ体系に組み込むことで、ドキュメント全体の読みやすさを一貫して保てます。
サンプルドキュメントには化学式の処理も含まれており、一部の LaTeX 化学コマンドをコピーやプレーンテキスト抽出に適した形式に変換します。これは専門の数式エディタの代わりになるものではなく、一般的な技術記事内の数式部分がデスクトッププレビューで正しく表示されることを目的としています。

タイポグラフィテーマ:単なる色変更ではない
タイポグラフィテーマは、今回のコントロールで私が重視した部分の一つです。単にアクセントカラーを変えるだけではなく、Markdown を読むための一連のリソースを提供します。
テーマ切り替えの効果は次の GIF をご覧ください。

現在のテーマリソースには以下の方向性があります。
Basic- オレンジハート
- 墨黒
- 彩紫
- 若緑
- 緑意
- 紅緋
- 藍蛍
- テクノロジーブルー
- ラン青
- 山吹
- フロントエンドの峰
- ギークブラック
- シンプル
以下はテクノロジーブルーテーマの表示例です。

ダークアプリテーマにギークブラックのタイポグラフィテーマを組み合わせると、コードや技術メモに適した表示になります。

テーマリソースは固定のキーを使用しています。例:
<SolidColorBrush x:Key="CodeWFMarkdownAccentBrush" Color="#0F766E" />
<SolidColorBrush x:Key="CodeWFMarkdownQuoteBackgroundBrush" Color="#ECFDF5" />
<x:Double x:Key="CodeWFMarkdownParagraphLineHeight">31</x:Double>
これにより、外部プロジェクトがテーマをカスタマイズする場合、コントロールのコードを変更する必要はなく、これらのリソースキーを上書きするだけで済みます。デフォルトのテーマパッケージは Light/Dark の 2 セットのリソースを提供しており、Avalonia アプリケーションのテーマを切り替えると、同じ Markdown タイポグラフィテーマが対応する明るい/暗いリソースを読み込みます。
単一の Viewer でテーマを個別に上書き可能
実際のアプリケーションでは、Markdown プレビュー領域が複数存在することがあります。たとえば、左側に本文プレビュー、右側にレビュー記録、あるいはメインドキュメントは通常のタイポグラフィ、サイドの概要はコンパクトレイアウト、といった具合です。
そのため、MarkdownViewer は個別のコントロールで以下の設定を上書きできます。
TypographyThemeTypographySize
<md:MarkdownViewer
Markdown="{Binding Markdown}"
TypographyTheme="Simple"
TypographySize="Small" />
複数 Viewer のサンプルはこのシナリオを検証するためのものです。

Viewer A は上部の統一設定に従い、Viewer B は自身で「シンプル+コンパクト」を使用できます。ローカル設定は現在の Viewer のリソース範囲に書き込まれ、同階層の他の Viewer に影響を与えません。
これはデスクトップアプリケーションで非常に実用的です。多くのアプリケーションは単一の「記事閲覧ページ」だけでなく、設定説明、バージョン更新、AI 応答、ログ解釈、ドキュメント詳細、比較ビューなどに Markdown プレビューを埋め込みます。領域ごとに読みやすさの密度が異なるため、グローバルテーマで一律に設定することはできません。
インクリメンタルレンダリング:変更領域のみを可能な限り置き換え
毎回一文字入力するたびに Markdown ドキュメント全体を再構築すると、短いテキストでは問題ありませんが、長いドキュメントではカクつきが生じる可能性があります。
CodeWF.Markdown ではインクリメンタルレンダリングのパスを実装しています。コントロールは既にレンダリングされたブロックを保持し、テキスト変更後は差分サービスを使って変更範囲を特定し、影響を受けるブロックのみを可能な限り置き換えます。
サンプルプログラムには「インクリメンタルデモ開始」ボタンがあり、3 種類の変更を自動的にシミュレートします。
- ドキュメント内の一部の内容を置換
- 本文の中盤に中国語の断片を挿入
- ドキュメント末尾に新しい Markdown ブロックを追加
効果は次のとおりです。

このロジックは単なるパフォーマンスの見せびらかしではなく、実際の編集シナリオに即したものです。中国語のドキュメントでは、英語の単語を一文字変えるだけでなく、文章全体を修正したり、説明を挿入したり、テーブルやコードブロックを追加したりすることがよくあります。インクリメンタルレンダリングは、こうした連続的なテキスト変更を処理するためのものです。
もちろん、正しさを無視して強引に部分リフレッシュするわけではありません。変更範囲が大きすぎたり、構造への影響が大きい場合、または部分的な置き換えが古いブロックの再利用に適さない場合は、完全なレンダリングにフォールバックします。プレビューコントロールにとっては、「常に部分リフレッシュ」よりも正しさの方が重要です。
テキスト選択とコピー
MarkdownViewer は読み取り専用のプレビューコントロールですが、読み取り専用だからといってインタラクションができないわけではありません。
現在のコントロールでは以下が可能です。
- レンダリングされたテキストの選択
- 選択範囲のコピー
- 空白部分でレンダリングされた文書全体のコピー
- コードブロックの個別コピー
ここで重要なのは、レンダリングされたテキストは単純に元の Markdown を返すわけではないという点です。テーブル、リスト、画像の alt テキスト、数式、タスクリストなどの内容は、コピーに適したプレーンテキストに変換する必要があります。リポジトリ内の MarkdownParser と共有レンダリングモデルが、このような処理の一部を担っています。
この機能を実装するにあたって私が重視したのは、「コピーした結果が使えるかどうか」です。ユーザーが Markdown プレビューから説明文、テーブル、コードブロックをコピーしたいだけなら、Markdown の記号に邪魔されるべきではありません。
Lite パッケージ:基本的なプレビューへの軽量な道
フルバージョンは機能が豊富ですが、依存関係も多くなります。
TextMateSharp:コードハイライト用Svg.Controls.Skia.AvaloniaとSvg.Skia:SVG 用Sylinko.CSharpMath.Avalonia:数式用Lang.Avalonia.Json:多言語リソース用
プロジェクトによっては、基本的な Markdown プレビューだけで十分で、これらの拡張機能は不要な場合があります。そのようなシナリオのために、CodeWF.Markdown.Lite を分割しました。
Lite 版では以下を維持しています。
- 見出し、段落、リスト、タスクリスト
- 引用、テーブル
- ビットマップ画像
- プレーンテキストのコードブロックとコピーボタン
- 対応する Lite テーマパッケージ
直接のパッケージ参照は Avalonia と Markdig のみで、依存関係のサイズに敏感なプロジェクトに適しています。コードハイライト、SVG、数式、画像プレビューウィンドウなどが必要な場合は、フルバージョンを使用してください。
導入方法
フルバージョンを使用する場合、パッケージをインストールします。
Install-Package CodeWF.Markdown.Themes
.NET CLI を使用する場合は以下のように追加します。
dotnet add package CodeWF.Markdown.Themes
対応する NuGet アドレス:
軽量版の基本プレビューのみが必要な場合は、Lite パッケージラインをインストールします。
dotnet add package CodeWF.Markdown.Lite.Themes
Lite の NuGet アドレス:
プロジェクトのソースコードは GitHub:https://github.com/dotnet9/CodeWF.Markdown
App.axaml でテーマを導入します。
<Application
xmlns="https://github.com/avaloniaui"
xmlns:markdown="https://codewf.com">
<Application.Styles>
<FluentTheme />
<markdown:MarkdownThemes />
</Application.Styles>
</Application>
ページ内で直接 MarkdownViewer を使用します。
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:md="https://codewf.com">
<ScrollViewer
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<md:MarkdownViewer
Markdown="{Binding Markdown}"
TypographyTheme="Simple"
TypographySize="Small" />
</ScrollViewer>
</UserControl>
TypographyTheme と TypographySize を指定しない場合のデフォルトは Basic + Normal です。MarkdownThemes でグローバルデフォルトを設定することもできます。
実行時にリソースを切り替えるには、以下を呼び出します。
MarkdownThemes.OverrideTypographyResources(
app,
MarkdownTypographyThemes.BlueGlow,
MarkdownTypographySizes.Small);
特定のウィンドウやコントロールの範囲だけを上書きして、グローバルなアプリケーションスタイルに影響を与えないようにすることもできます。
リポジトリ構成
現在のリポジトリ構造はおおよそ次のとおりです。
src/
CodeWF.Markdown
CodeWF.Markdown.Themes
CodeWF.Markdown.Lite
CodeWF.Markdown.Lite.Themes
CodeWF.Markdown.Sample
CodeWF.Markdown.Lite.Sample
CodeWF.Markdown.Shared
tests/
CodeWF.Markdown.Tests
CodeWF.Markdown.Shared には、共有レンダリングモデル、Markdown 解析、差分サービスなどのコードが含まれています。フルバージョン、Lite、テストのすべてでこのレイヤーのロジックを再利用できます。
テーマリソースは CodeWF.Markdown.Themes と CodeWF.Markdown.Lite.Themes に配置されています。これにより、コントロールコード、デフォルトテンプレート、タイポグラフィリソースをそれぞれ管理でき、外部プロジェクトは必要なパッケージのみを参照できます。
最近の対応内容
更新ログを見ると、最近のバージョンでは主に以下の点を補完しています。
CodeWF.Markdown.Liteと対応するテーマパッケージ、サンプルアプリケーションを新規追加MarkdownViewerにTypographyThemeとTypographySizeを追加し、個別の Viewer での上書きをサポートMarkdownThemesにコンパクトタイポグラフィリソースを追加- サンプルアプリケーションをタブ構造に変更し、複数 Viewer のデモを追加
- 多言語リソースを Resx から JSON に移行し、NuGet の content files として配布
- 画像リソースの解放と画像プレビューウィンドウのリソース保持を改善
- 内部
MarkdownMathViewを新規追加し、数式の色が現在のテーマに追従するように改良 - Markdown、テーマ、SVG および関連アセンブリのトリミング保持設定を補充し、トリミングビルドの互換性を改善
これらの作業は「大きな機能追加」ほど目立ちませんが、コントロールの長期的な利用可能性にとって重要です。特にテーマリソース、画像の解放、ローカルな Viewer の上書き、Lite パッケージラインは、このコントロールがサンプル内で見た目が良いだけでなく、実際のプロジェクトに組み込めるかどうかを左右します。
まだ改善できる点
CodeWF.Markdown はすでに私が普段使う Markdown プレビューのシナリオをカバーできていますが、まだ改善の余地があります。
- テーマをさらに追加し、細部を統一する
- 長いドキュメントや複雑なテーブルでさらに負荷テストを行う
- コードハイライトの言語カバレッジをさらに検証する
- 画像プレビューウィンドウのショートカットキーとインタラクションをより充実させる
- 「業務プロジェクトでどうやって導入するか」のドキュメントサンプルを追加する
- Lite とフルバージョンの機能境界を README でもっと分かりやすく記述する
- AOT、トリミングビルド、プラットフォームごとのフォント差異のテストを続ける
現在のこのコントロールの目標は明確です。まず一般的な読み取りとプレビューのシナリオを安定させ、徐々に高度な機能を追加していきます。Markdown コントロールは簡単に散らかりやすいため、パッケージライン、テーマリソース、共有レンダリングモデル、テストをまずはしっかりと固めます。
最後に
CodeWF.Markdown は、私自身のプロジェクトから切り出した Avalonia 向け Markdown レンダリングコントロールです。その価値は「私も見出しやリストをレンダリングできる」という点ではなく、Markdown プレビューをデスクトップコントロールとして真剣に扱っている点にあります。
- WebView ではなく、Avalonia コントロールツリー
- 単一のスタイルではなく、タイポグラフィテーマリソース
- グローバル設定のみではなく、個別の Viewer での上書きをサポート
- 毎回の入力で完全に再構築するのではなく、可能な限りインクリメンタルレンダリング
- フルバージョンだけでなく、基本プレビュー用の Lite パッケージラインも用意
デスクトップツール、ドキュメント管理、AI 応答プレビュー、更新履歴表示、設定説明、開発者ツールボックスなどのシナリオにとって、テーマ対応、コピー可能、埋め込み可能、長期的にメンテナンス可能な MarkdownViewer は非常に価値があります。
今後もこのコントロールを自分のツールや記事作成のワークフローで使い続け、実際のシナリオで遭遇する細かい点を補完していきます。一度きりのデモを作るのではなく、Avalonia プロジェクトでそのまま使える Markdown プレビューコントロールに磨き上げたいと考えています。