This document describes the public surface of Markdown4D: the facade, the pipeline builder, the abstract syntax tree, the document builder, the table of contents, the theme, and the VCL / FMX viewer and editor components.
All public enumerations are scoped ({$SCOPEDENUMS ON}), so qualify them:
TMarkdownDialect.Gfm, TMarkdownNodeKind.Heading, and so on.
- Facade:
TMarkdown - Pipeline builder
- Abstract syntax tree
- Document builder
- Table of contents
- Theme
- Incremental parser
- Viewer components
- Editor components
- Drawing SVG
- Glyph outlines and image decoding
Unit Markdown4D. The one-stop entry point for the common cases.
type
TMarkdown = class
class function Version: string;
class function ToHtml(const Source: string;
const Dialect: TMarkdownDialect = TMarkdownDialect.CommonMark): string;
class function ToUnsafeHtml(const Source: string;
const Dialect: TMarkdownDialect = TMarkdownDialect.CommonMark): string;
class function Parse(const Source: string;
const Dialect: TMarkdownDialect = TMarkdownDialect.CommonMark): IMarkdownDocument;
class function ToMarkdown(const Document: IMarkdownDocument): string;
class function CreateIncrementalParser(
const Dialect: TMarkdownDialect = TMarkdownDialect.CommonMark): IMarkdownIncrementalParser;
end;Version returns the library version string ('2.0.0', defined as
Markdown4DVersion in unit Markdown4D.Version).
TMarkdownDialect (unit Markdown4D.Defines) is (CommonMark, Gfm). The
facade caches one pipeline per dialect and rendering mode.
ToHtml renders safely: raw HTML becomes <!-- raw HTML omitted -->, and a
link or image destination using javascript:, vbscript:, file: or a
non-image data: scheme is emptied. Use it for any document the application did
not produce itself.
ToUnsafeHtml renders what the CommonMark and GFM specifications prescribe:
raw HTML and every destination reach the output untouched. It is the right
choice for trusted input, or when the result passes through an HTML sanitizer
afterwards. The conformance suites are checked against this method.
For finer control build your own pipeline; UnsafeHtml and UnsafeLinks on the
builder correspond to the two halves of ToUnsafeHtml.
uses
Markdown4D,
Markdown4D.Defines,
Markdown4D.Ast.Interfaces;
const Doc = TMarkdown.Parse(Source, TMarkdownDialect.Gfm);
const Markdown = TMarkdown.ToMarkdown(Doc);Errors raised by the library derive from EMarkdownError (unit
Markdown4D.Defines).
Units Markdown4D.Pipeline and Markdown4D.Extensions.Interfaces.
TMarkdownPipeline.Create returns a fluent IMarkdownPipelineBuilder. Every
configuration method returns the builder, so calls chain; Build produces an
immutable, reusable IMarkdownPipeline.
type
IMarkdownPipelineBuilder = interface
function UseCommonMark: IMarkdownPipelineBuilder;
function UseGfm: IMarkdownPipelineBuilder;
function Use(const Extension: IMarkdownExtension): IMarkdownPipelineBuilder;
function XhtmlOutput: IMarkdownPipelineBuilder;
function UnsafeHtml: IMarkdownPipelineBuilder;
function UnsafeLinks: IMarkdownPipelineBuilder;
function TagFilter: IMarkdownPipelineBuilder;
function RegisterBlockParser(const Parser: IMarkdownBlockParser;
const TriggerCharacters: string; const Priority: Integer): IMarkdownPipelineBuilder;
function RegisterInlineParser(const Parser: IMarkdownInlineParser;
const TriggerCharacters: string; const Priority: Integer): IMarkdownPipelineBuilder;
function RegisterDelimiterProcessor(const Processor: IMarkdownDelimiterProcessor;
const Priority: Integer): IMarkdownPipelineBuilder;
function RegisterRendererHook(const Hook: IMarkdownRendererHook;
const Priority: Integer): IMarkdownPipelineBuilder;
function RegisterDocumentProcessor(const Processor: IMarkdownDocumentProcessor;
const Priority: Integer): IMarkdownPipelineBuilder;
function Build: IMarkdownPipeline;
end;
IMarkdownPipeline = interface
function ToHtml(const Source: string): string;
function Parse(const Source: string): IMarkdownDocument;
end;| Method | Effect |
|---|---|
UseCommonMark |
Registers the full CommonMark 0.31.2 block and inline grammar |
UseGfm |
UseCommonMark plus tables, task lists, strikethrough, autolinks, tag filter |
Use(ext) |
Installs a custom IMarkdownExtension |
UnsafeHtml |
Allows raw HTML in the rendered output (CommonMark spec behaviour) |
UnsafeLinks |
Writes every link and image destination out, including javascript:, vbscript:, file: and non-image data: (spec behaviour). Without it those destinations are emptied |
XhtmlOutput |
Emits self-closing XHTML tags |
TagFilter |
Applies the GFM tag filter to raw HTML |
Register* |
Adds a single parser, processor or hook at a given priority |
Higher priority wins; ties break by registration order. Rather than passing
magic numbers, use the named constants on TMarkdownPriorities (unit
Markdown4D.Extensions.Interfaces): Highest, High, AboveNormal,
Normal, BelowNormal, Low, Lowest, and the extension slots
ExtensionProcessor, ExtensionRenderer and ExtensionLayoutOverride. A built
pipeline is thread-safe to reuse for parsing and rendering.
uses
Markdown4D.Pipeline,
Markdown4D.Extensions.Interfaces;
const Html = TMarkdownPipeline.Create
.UseGfm
.UnsafeHtml
.Build
.ToHtml(Source);See EXTENSIONS.md for the extension interfaces used by the
Register* and Use methods.
Unit Markdown4D.Ast.Interfaces. Parse returns an IMarkdownDocument, the
root of a tree of IMarkdownNode. Nodes are reference-counted interfaces; hold
the document and the whole tree stays alive.
type
IMarkdownNode = interface
function GetKind: TMarkdownNodeKind;
function GetSegment: TMarkdownSegment;
function GetChildCount: Integer;
function GetChild(const Index: Integer): IMarkdownNode;
procedure Accept(const Visitor: IMarkdownVisitor);
procedure SetExtensionData(const Key: string; const Data: IInterface);
function TryGetExtensionData(const Key: string; out Data: IInterface): Boolean;
property Kind: TMarkdownNodeKind read GetKind;
property Segment: TMarkdownSegment read GetSegment;
property ChildCount: Integer read GetChildCount;
property Children[const Index: Integer]: IMarkdownNode read GetChild;
end;TMarkdownNodeKind enumerates every node type: Document, Paragraph, Heading, ThematicBreak, CodeBlock, BlockQuote, List, ListItem, HtmlBlock, Text, Emphasis, Strong, CodeSpan, Link, Image, Autolink, SoftLineBreak, HardLineBreak, InlineHtml, CustomInline, Table, TableRow, TableCell.
TMarkdownSegment (StartOffset, EndOffset, Length) locates the node in
the source string. SetExtensionData / TryGetExtensionData attach arbitrary
interface payloads keyed by string, the mechanism the chart extension uses to
cache its parsed model on the node.
Query a node for a richer interface with as or Supports:
| Interface | Extra members |
|---|---|
IMarkdownHeading |
Level, SourceLine |
IMarkdownCodeBlock |
Literal, InfoString, IsFenced |
IMarkdownList |
IsOrdered, StartNumber, IsTight |
IMarkdownText |
Literal (also used for code spans, HTML blocks, inline HTML) |
IMarkdownLink |
Destination, Title (also used for images and autolinks) |
IMarkdownCustomInline |
NodeName (extension inline nodes such as strikethrough) |
IMarkdownTableRow |
IsHeader |
IMarkdownTableCell |
Alignment (TMarkdownTableColumnAlignment) |
const Doc = TMarkdown.Parse(Source, TMarkdownDialect.Gfm);
for var Index := 0 to Doc.ChildCount - 1 do
begin
const Child = Doc.Children[Index];
if Child.Kind = TMarkdownNodeKind.Heading then
begin
const Heading = Child as IMarkdownHeading;
Writeln(Format('H%d at line %d', [Heading.Level, Heading.SourceLine]));
end;
end;IMarkdownVisitor offers a Visit* method per node kind for double-dispatch
traversal via Node.Accept(Visitor).
Unit Markdown4D.Ast.Builder. Constructs a valid document in code, then hands
it to the writer or the layout engine. TMarkdownDocumentBuilder.Create returns
a fluent IMarkdownDocumentBuilder.
Convenience methods (Heading, Paragraph, Bold, Italic, Code, Link,
Image, Cell, …) emit a complete node in one call. Begin… / End… pairs
open a container you fill with nested content (BeginParagraph,
BeginBulletList, BeginOrderedList, BeginListItem, BeginTaskListItem,
BeginTable, BeginTableRow, BeginTableCell, BeginBlockQuote,
BeginBold, BeginItalic, BeginStrikethrough, BeginLink, BeginHeading).
Structural rules are enforced (a table may contain only rows, a row only cells),
and Build raises EMarkdownError if any node is left open.
uses
Markdown4D,
Markdown4D.Ast.Builder;
const Doc = TMarkdownDocumentBuilder.Create
.Heading(1, 'Report')
.Paragraph('Generated by Markdown4D.')
.BeginBulletList
.BeginListItem.Text('First item').EndListItem
.BeginListItem.Text('Second item').EndListItem
.EndList
.Build;
const Markdown = TMarkdown.ToMarkdown(Doc);Unit Markdown4D.Toc. TMarkdownToc.FromDocument walks the headings of any
document and returns a nested IMarkdownToc. Each IMarkdownTocEntry carries
Caption, Level, Anchor (a GitHub-style slug, de-duplicated with a numeric
suffix), SourceLine, and nested Children.
uses
Markdown4D,
Markdown4D.Toc;
const Doc = TMarkdown.Parse(Source, TMarkdownDialect.Gfm);
const Toc = TMarkdownToc.FromDocument(Doc);
for var Index := 0 to Toc.EntryCount - 1 do
begin
const Entry = Toc.Entries[Index];
Writeln(Format('%s -> #%s', [Entry.Caption, Entry.Anchor]));
end;Unit Markdown4D.Theme. TMarkdownTheme holds every colour, font and metric
the layout engine uses. Construct one with CreateLight, CreateDark or
CreatePreset(TMarkdownThemePreset); you own the instance and must Free it
(the viewer/editor take ownership when you assign their Theme property).
Selected properties: BaseFont, CodeFont, HeadingFonts[Level], TextColor,
BackgroundColor, LinkColor, CodeTextColor, CodeBackgroundColor,
BlockQuoteBarColor, TableHeaderBackgroundColor, TableBorderColor,
ThematicBreakColor, ParagraphSpacing, ListIndent, ContentPadding, the
Chart* colours and ChartPalette, and TokenColors[Kind] for code
highlighting. Colours are TLayoutColor ($AARRGGBB).
SaveToJson / LoadFromJson serialise a complete theme so you can ship it as a
resource or let users edit it.
uses
Markdown4D.Theme;
const Theme = TMarkdownTheme.CreateDark;
try
Theme.LinkColor := $FF3B82F6;
const Json = Theme.SaveToJson;
finally
Theme.Free;
end;Units Markdown4D and Markdown4D.Parser.Interfaces.
TMarkdown.CreateIncrementalParser returns an IMarkdownIncrementalParser that
keeps state between edits and reparses only the affected region.
type
IMarkdownIncrementalParser = interface
procedure Append(const Chunk: string);
procedure ReplaceRange(const StartIndex, Count: Integer; const Replacement: string);
function ToHtml: string;
end;Append adds text at the end (the streaming case); ReplaceRange edits an
existing region (the editor case); ToHtml renders the current document.
uses
Markdown4D,
Markdown4D.Defines,
Markdown4D.Parser.Interfaces;
const Parser = TMarkdown.CreateIncrementalParser(TMarkdownDialect.Gfm);
Parser.Append('# Live'#10);
Parser.Append('More **text** streaming in.'#10);
const Html = Parser.ToHtml;The viewer components use the same incremental machinery internally; see STREAMING.md.
TMarkdownViewer renders markdown natively onto the control canvas, without a
browser.
The VCL control lives in Markdown4D.Vcl.Viewer (a TCustomControl); the FMX
control in Markdown4D.Fmx.Viewer (a TControl). Their public surface is the
same.
| Property | Type | Notes |
|---|---|---|
Text |
string |
The whole markdown document as one value |
ThemePreset |
TMarkdownThemePreset |
Light / Dark, editable in the Object Inspector |
Images |
TMarkdownViewerImageSettings |
How image destinations are resolved and fetched (see below) |
| Member | Description |
|---|---|
Theme: TMarkdownTheme |
Assign a fully customised theme at run time (the control takes ownership) |
AppendMarkdown(const Markdown: string) |
Append text and repaint; thread-safe, debounced |
LoadFromFile(const FileName) / LoadFromStream(const Stream) |
Load a document |
FindText(const Needle): Boolean |
Scroll to the first match |
CopySelectionToClipboard |
Copy the current selection |
SelectAll |
Select the whole document |
ClearSelection |
Drop the selection |
SelectedText: string |
The selected text |
ContentHeight: Integer |
Laid-out document height, for auto-sizing |
ScrollOffset: Single |
Read / set the vertical scroll position |
LayoutCount: Integer |
Advances on every relayout (first width, resize, arriving images), so a host can notice layout-derived state going stale |
DisplayList: IMarkdownDisplayList |
The rendered primitives, for advanced hosts |
| Event | Signature | Raised when |
|---|---|---|
OnLinkClick |
(const Sender: TObject; const Url: string) |
A link is clicked |
OnLinkHover |
(const Sender: TObject; const Url: string) |
The hovered link changes ('' on leave) |
OnResolveImage |
(const Sender: TObject; const Url: string; const Picture/Bitmap; var Handled: Boolean) |
An image needs resolving; set Handled to supply it yourself |
OnRemoteImageRequest |
(const Sender: TObject; const Url: string; var Allow: Boolean) |
About to fetch a remote image. Allow arrives holding Images.AllowRemote; clear it to refuse this address |
OnScroll |
TNotifyEvent |
The scroll position changes |
The viewer loads http(s) images asynchronously and local images relative to
Images.BaseUrl or the loaded document's folder. Code blocks tagged pascal,
sql, json or xml are syntax-highlighted; chart blocks render as
graphics when the chart block override is registered (see
EXTENSIONS.md).
An HTML block in the document is never painted as markup: unlike ToHtml and
ToUnsafeHtml, which either omit or emit raw HTML as text, the viewer
translates an allowed subset to markdown and lays that out through the
ordinary path, so it gets selection, hit-testing and theming for free. The
subset covers p, div, center, section, article, h1-h6, hr,
br, strong/b, em/i, code/kbd/samp/tt, del/s/strike,
a, img, ul/ol/li, blockquote, details/summary and pre.
script, style, head, iframe and object are dropped along with their
content; any other tag disappears while its content stays, the way a browser
would show it with the styling removed. The translation is implemented in
Markdown4D.Html.Subset and cached on the AST node, since layout runs again
on every resize and every streamed chunk.
The mouse wheel scrolls the control only while its content overflows; otherwise the wheel passes through to the parent, so viewers stacked inside a scroll box scroll the list they sit in. The VCL controls carry the native window scrollbar; the FMX viewer and editor draw a draggable overlay thumb whenever their content overflows.
A focused viewer scrolls on the arrow keys, PgUp / PgDn, Home and End.
Ctrl+A selects the document, Ctrl+C copies the selection. Right-clicking
opens a Copy / Select All menu; assigning PopupMenu replaces it with the
host's own menu.
Unit Markdown4D.Viewer.ImageSettings, republished by both viewer units.
| Property | Default | Notes |
|---|---|---|
BaseUrl |
'' |
Resolves relative image destinations |
AllowRemote |
True |
Whether http(s) destinations may be fetched at all |
MaxBytes |
8 MB | Upper bound on one downloaded image; 0 removes the bound |
RestrictToDocumentFolder |
False |
Keeps a relative path inside the document's own folder |
Opening a document fetches every remote image it names, which tells those hosts
that the document was read. An application showing documents it did not write
should decide what it wants here: clear AllowRemote to fetch nothing, or leave
it on and refuse individual addresses through OnRemoteImageRequest.
procedure TMainForm.ViewerRemoteImageRequest(const Sender: TObject; const Url: string;
var Allow: Boolean);
begin
Allow := Url.StartsWith('https://cdn.example.com/', True);
end;uses
Markdown4D.Theme,
Markdown4D.Vcl.Viewer;
const Viewer = TMarkdownViewer.Create(Self);
Viewer.Parent := Self;
Viewer.Align := alClient;
Viewer.ThemePreset := TMarkdownThemePreset.Dark;
Viewer.Text := '# Welcome'#10#10 + 'This is **Markdown4D**.';TMarkdownEditor is a syntax-highlighting source editor for markdown. The VCL
control lives in Markdown4D.Vcl.Editor, the FMX control in
Markdown4D.Fmx.Editor.
| Property | Type | Notes |
|---|---|---|
Text |
string |
The markdown source |
ThemePreset |
TMarkdownThemePreset |
Light / Dark |
ShowLineNumbers |
Boolean |
Gutter line numbers (default False) |
| Member | Description |
|---|---|
CaretPosition: Integer |
Read / set the caret offset |
SelectedText: string |
The current selection |
Theme: TMarkdownTheme |
Assign a custom theme at run time |
ExecuteCommand(const Command: TEditorCommand) |
Apply Bold, Italic, Link or CodeBlock to the selection |
Undo / Redo / CanUndo / CanRedo |
Undo stack |
AttachPreview(const Viewer: TMarkdownViewer) / DetachPreview |
Bind a live preview viewer |
FlushPreview |
Force a pending preview refresh immediately |
| Event | Signature | Raised when |
|---|---|---|
OnChange |
TNotifyEvent |
The text changes |
OnScroll |
TNotifyEvent |
The editor scrolls |
AttachPreview wires the editor to a TMarkdownViewer: edits refresh the
preview on a short debounce, and the preview keeps its scroll aligned with the
editor's first visible source line.
uses
Markdown4D.Vcl.Editor,
Markdown4D.Vcl.Viewer;
FEditor.AttachPreview(FPreview);
FEditor.Text := '# Live preview'#10#10 + 'Type on the left, rendered on the right.';The viewers render SVG images with an engine of this project. Nothing has to be switched on for that: adding a viewer to a form brings it along.
Markdown4D.Image.Svg is the hook the viewers route image bytes through, and
Markdown4D.Image.Svg.Native is the engine registered on it:
uses
Markdown4D.Image.Svg,
Markdown4D.Image.Svg.Native;
// Draws with this engine alone, reporting False for anything it will not draw
// rather than passing it on. TMarkdownSvgSupport.TryRasterize goes through
// whichever engine is registered.
var Raster: TMarkdownSvgRaster;
if TryRasterizeSvgNatively(Bytes, 200, 200, Raster) then
// Raster.Pixels is premultiplied BGRA, top down, stride = Width * 4.Covered: path, rect with corner radii, circle, ellipse, line,
polyline, polygon, g, use, svg with a viewBox, transform lists,
solid fills, linear and radial gradients, patterns, strokes with miter and
round joins and caps, opacity, both fill rules, clipPath, mask, image
carrying a data URI, text, and filters built from feGaussianBlur,
feOffset, feFlood, feComposite and feMerge.
Refused whole, so a document is never drawn half right: foreignObject, an
image pointing outside the document, and any filter primitive not in that list.
Two units underneath are useful on their own. Markdown4D.Image.Rasterizer
fills polygons with anti-aliasing, one colour or a gradient or a tile, held
inside an optional mask. Markdown4D.Image.Filters holds the pixel operations
the filters are built from.
Two things drawing an SVG needs from the machine it runs on. Both sit behind a seam, and both already have an answer on every platform, so an application normally does nothing here.
uses
Markdown4D.Image.Glyphs;
// The outlines of a run of text, laid out from an origin at the baseline
// start. Registered by Markdown4D.Image.Glyphs.Gdi on Windows and by
// Markdown4D.Fmx.Glyphs everywhere FMX runs.
TMarkdownGlyphSupport.RegisterOutliner(
function (const FamilyName: string; const PixelSize: Single; const Bold, Italic: Boolean;
const Text: string; out Run: TMarkdownGlyphRun): Boolean
begin
// Run.Contours in the units of PixelSize, Run.Advance is the width.
end);uses
Markdown4D.Image.Decoder;
// Encoded image bytes to pixels. Registered by Markdown4D.Vcl.ImageDecoder and
// Markdown4D.Fmx.ImageDecoder, whichever the application already has.
TMarkdownImageDecoding.RegisterDecoder(
function (const Data: TBytes; out Raster: TMarkdownPixelRaster): Boolean
begin
end);Register your own to reach a font or a format the platform does not offer. A provider registered before the viewer unit initialises keeps its place: the bundled ones step aside when something is already there.