Catalog · How the catalog works
NativeMarkdownViewer
- ★ 11 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
Kai Markdown Viewer
Native VCL markdown viewer component for Delphi.
TMarkDownViewer is a custom VCL control that renders markdown directly with VCL/GDI. It does not use WebView, HTML, or an embedded browser, and it can be created at runtime without installing the package into the IDE.
Contents
MarkdownViewerVCL.pas- component source (TMarkDownViewer)MarkdownViewer.Model.pas- block, inline-token, and text-run model typesMarkdownViewer.Parser.pas- markdown-to-block parsing (including tail-only streaming reparse)MarkdownViewer.Renderer.pas- layout and GDI rendering helpersMarkdownViewer.Highlight.pas- pluggable code-block syntax highlighters and their registryMarkdownViewer.Html.pas- HTML export helpersKaiMarkdownViewer.dpk- Delphi package sourceKaiMarkdownViewer.dproj- package projectMarkdownGroup.groupproj- project group (package, demo, and tests)TestApp/MarkdownViewerDemo.dproj- VCL demo appTestApp/Demo.IntroForm.pas- launcher form for the basic and streaming demosTestApp/Demo.MainForm.pas- editor/preview demo that createsTMarkDownViewerat runtimeTestApp/Demo.StreamingForm.pas- incremental streaming demoDUnitX/MarkdownViewerTests.dproj- DUnitX test project
Features
Supported rendering includes:
- Headings (
#style and===/---setext underlines) - Underline rule beneath H1/H2 headings (
HeadingRuleColor,clNoneto disable) - Paragraphs
- Bold and italic spans
- Strikethrough spans
- Nested inline formatting (e.g. bold containing italic, or styled link text)
- Hard line breaks from two trailing spaces or a trailing backslash
- Escaped markdown punctuation
- HTML entities (named, decimal, and hex)
- Automatic links
- Angle-bracket email autolinks
- Reference-style links
- Inline code
- Fenced code blocks
- Syntax highlighting of fenced code blocks for 25+ languages (configurable via
SyntaxColors) - Block quotes
- Horizontal rules
- Ordered and unordered lists
- Nested list indentation
- Task lists with checked and unchecked boxes
- Clickable task checkboxes that toggle the source (
AllowTaskToggle) - Pipe tables with left, center, and right alignment
- Inline formatting and links inside table cells
- Local images with scaled rendering and alt-text fallback
- Clickable markdown links
- Vertical scrolling
- Keyboard scrolling
- Mouse text selection
- Copy selected markdown with
Ctrl+C - Copy selected plain text with
Ctrl+Shift+C - Read the current selection as markdown or plain text via
SelectedText - Case-insensitive find highlighting with next/previous navigation
- Configurable code font (
CodeFontName, monospace fallback) - Highlight syntax using double equals (
==highlighted==) - Superscript (
^sup^) and subscript (~sub~) spans - Common emoji shortcode parsing (e.g.
:smile:,:warning:) - Floating clipboard "Copy" button when hovering code blocks
- Incremental streaming with tail-only block parsing
- Optional in-place editing of the rendered markdown source
- Undo and redo of edits
- Caret navigation, insertion, and deletion mapped back to the markdown source
Strikethrough uses double tildes:
This is ~~no longer current~~.Highlighting uses double equals:
This is ==highlighted== text.Superscript uses carats, and subscript uses tildes:
Here is a formula: E = mc^2^.
Water is H~2~O.Emoji shortcodes are parsed automatically:
This is a success :check: and a warning :warning:.Inline emphasis nests, so a span can carry more than one style, and link text can be formatted:
This is **bold with _italic_ inside**, and a [**bold link**](https://example.com/).End a line with two spaces or a backslash to force a hard line break within a paragraph:
First line.<two spaces>
Second line, same paragraph.Prefix markdown punctuation with a backslash to render it literally:
\*not italic\* and \[not a link\]A line of text underlined with = or - becomes a heading:
Title becomes an H1
===================
Subtitle becomes an H2
----------------------HTML entities are decoded, including named, decimal, and hex forms:
© 2024 — 100 × 200, ©, and €URLs using http://, https://, or www. are linked automatically. Angle-bracket URL and email autolinks are also supported:
https://www.embarcadero.com/
<https://docwiki.embarcadero.com/>
<support@example.com>Reference-style links are resolved from definitions in the markdown:
Read the [DocWiki][docwiki].
[docwiki]: https://docwiki.embarcadero.com/The table syntax supports the common markdown alignment row:
| Header Column 1 | Header Column 2 | Align Center | Align Right |
| :--- | :--- | :---: | ---: |
| Left row data 1 | Sample value A | Center text | $100.00 |Inline markdown is supported inside table cells:
| Name | Status |
| :--- | :---: |
| **Current** | `active` |
| ~~Retired~~ | [Details](https://example.com/) |Task list items are recognized in list items:
- [x] Completed item
- [ ] Remaining itemNested list indentation is based on leading spaces:
- Parent item
- Child item
- Grandchild itemImages use standard markdown image syntax. Local paths are resolved relative to BasePath, or relative to the loaded markdown file when using LoadFromFile.
Basic Usage
Add MarkdownViewerVCL to your uses clause and create the control like any other VCL control:
uses
MarkdownViewerVCL;
var
Viewer: TMarkDownViewer;
begin
Viewer := TMarkDownViewer.Create(Self);
Viewer.Parent := Self;
Viewer.Align := alClient;
Viewer.MarkdownText := '# Hello' + sLineBreak + sLineBreak + 'This is **markdown**.';
end;You can also assign lines:
Viewer.Markdown.Assign(Memo1.Lines);To highlight matching text, assign SearchText:
Viewer.SearchText := 'markdown';
// SearchHighlightColor auto-adapts to the background; set explicitly to override:
// Viewer.SearchHighlightColor := $00BFFFFF;FindNext and FindPrevious move the selection to the next or previous match
(wrapping around the document) and scroll it into view, and SearchMatchCount
returns the number of matches:
if Viewer.FindNext then
StatusBar.SimpleText := Format('%d matches', [Viewer.SearchMatchCount]);Fenced and inline code render in a monospace font you can change, with an automatic fallback when the requested font is not installed:
Viewer.CodeFontName := 'Cascadia Code';Fenced code blocks are syntax highlighted when the opening fence carries a language tag. The tag is matched case-insensitively against a registry of built-in highlighters:
```pascal
procedure Hello;
begin
WriteLn('Hi'); // greet
end;
```Built-in languages (with the fence tags that select them):
| Language | Tags |
|---|---|
| Delphi / Object Pascal | pascal, objectpascal, objpas, delphi, pas, dpr, dpk, pp, lpr |
| Delphi form file | dfm |
| C / C++ / C# | c; cpp, c++, cxx, cc, hpp; cs, csharp, c# |
| Java / JavaScript / TypeScript | java; js, javascript; ts, typescript |
| Go / Rust / PHP | go; rs, rust; php |
| Python / Ruby | py, python; rb, ruby |
| SQL | sql |
| HTML / XML / CSS | html, htm; xml; css |
| JSON / YAML | json; yaml, yml |
| Shell / INI | sh, bash, shell; ini, cfg, conf |
Token colours and font styles are exposed through the SyntaxColors property.
Each token kind (keyword, comment, string, number, type, preprocessor, symbol,
and plain) has a colour and a style. Colours default to clDefault, which
resolves to a theme-aware palette that adapts to light and dark backgrounds;
set a colour explicitly to override it:
Viewer.SyntaxColors.KeywordColor := clNavy;
Viewer.SyntaxColors.CommentStyle := [fsItalic];An unrecognized or missing language tag falls back to plain, unhighlighted
code. Additional languages can be registered at runtime with
TMarkdownSyntaxHighlighterRegistry.RegisterHighlighter.
Task list checkboxes are clickable by default and toggle the underlying
markdown source (set AllowTaskToggle := False for a static preview).
Or load from a file:
Viewer.LoadFromFile('README.md');For streamed content, append only the newly received text. The viewer reparses the final unstable block instead of rebuilding the complete document:
Viewer.AppendMarkdownText(NewChunk);When loading from a file, BasePath is set automatically to the markdown file folder so relative image paths resolve naturally. You can also set it yourself:
Viewer.BasePath := ExtractFilePath(Application.ExeName);Links
By default, clicking a markdown link opens it with ShellExecute. To handle links yourself, assign OnLinkClick:
Viewer.OnLinkClick :=
procedure(Sender: TObject; const Url: string)
begin
ShowMessage(Url);
end;Editing
The viewer is read-only by default. Set ReadOnly to False to edit the
markdown source directly in the rendered view; the caret, typing, and deletion
are mapped back to the underlying markdown text:
Viewer.ReadOnly := False;While editing, you can use:
Tab/Shift+Tabon list items (bullet, numbered, or checklists) to indent/outdent the line.Tab/Shift+Tabon headings to increase/decrease the heading level.Ctrl+Kto wrap the selected text in a link structure[selected]()and position the caret inside the parentheses, or insert[]()if no text is selected.Ctrl+Spaceto toggle a task list checkbox ([ ]/[x]) on the current line.Ctrl+1throughCtrl+6to set the heading level of the current line, andCtrl+0to convert it back to a plain paragraph.Alt+Up/Alt+Downto move the current line or block up/down in the document.Ctrl+Tto toggle strikethrough (~~text~~) andCtrl+Hto toggle highlight (==text==).
Edits raise OnChange, and Undo/Redo walk the edit history:
Viewer.OnChange :=
procedure(Sender: TObject)
begin
StatusBar.SimpleText := 'Modified';
end;
Viewer.Undo;
Viewer.Redo;OnScroll fires when the vertical scroll position changes, which is useful for
synchronizing an external editor or scrollbar.
HTML Export
The current markdown can be exported to HTML. AsHtml returns an HTML fragment,
and AsHtmlDocument wraps it in a complete document with an optional title:
Fragment := Viewer.AsHtml;
Page := Viewer.AsHtmlDocument('My Notes');The conversion is also available without a control through
MarkdownToHtml / MarkdownToHtmlDocument in MarkdownViewer.Html.
Demo Application
Open and run:
TestApp/MarkdownViewerDemo.dprojThe demo creates TMarkDownViewer at runtime, so the package does not need to be
installed into the IDE to try the component. It opens an intro launcher with two
demos:
- Basic demo (
Demo.MainForm) - a side-by-side editor and preview with file open/save/reload commands, unsaved-change prompts, an editable preview with undo and a read-only toggle, clipboard commands, search highlighting, editor visibility and word-wrap controls, and adjustable preview/editor font sizes. - Streaming demo (
Demo.StreamingForm) - loads a text file and feeds it to the viewer incrementally withAppendMarkdownText, with a trackbar that controls how many characters arrive per tick.
Keyboard Navigation
When the viewer has focus, it supports:
- Up and Down arrows
- Page Up and Page Down
- Home and End
- Space and Shift+Space
- Ctrl+A
- Ctrl+C to copy selected markdown
- Ctrl+Shift+C to copy selected plain text
- Escape to clear selection
Building
Open KaiMarkdownViewer.dproj in RAD Studio and build the package.
Command-line build example for RAD Studio 37.0:
call "C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat"
MSBuild KaiMarkdownViewer.dproj /t:Build /p:Config=Debug /p:Platform=Win32Build the demo:
cd TestApp
MSBuild MarkdownViewerDemo.dproj /t:Build /p:Config=Debug /p:Platform=Win32Tests
The DUnitX/MarkdownViewerTests.dproj console project contains focused tests
for the model, parser, renderer helpers, and VCL component. It is included in
MarkdownGroup.groupproj.
MSBuild DUnitX\MarkdownViewerTests.dproj /t:Build /p:Config=Debug /p:Platform=Win32
DUnitX\Win32\Debug\MarkdownViewerTests.exe --exitbehavior:ContinueNotes
This is a lightweight native markdown renderer, not a full CommonMark implementation. It is intended for typical application help, notes, preview panes, and embedded documentation where native VCL rendering and simple deployment are more important than exhaustive markdown compatibility.

