Skip to main content

Document Format

Updated: 2026-04-21|9 min|enes

Postext reads a deliberately small markdown dialect.

The parser ships as a hand-written tokenizer — not a full CommonMark implementation — so the source format is narrow and predictable. The intent is twofold: keep the engine small and fast, and make documents trivially portable between Postext and any other CommonMark reader (Obsidian, Pandoc, VS Code…). Anything not listed on this page is either treated as plain text or removed from the inline stream.

If you are building a document programmatically, the parseMarkdown function (see Configuration › Parsing) gives you the exact block structure the layout engine consumes.

#Frontmatter

A document may begin with an optional YAML frontmatter block fenced by --- markers:

---
title: Chapter One
author: Jane Doe
publishDate: 2026-04-15
---
 
# Chapter One
 
The story begins here…

Call extractFrontmatter(source) to split the frontmatter from the body. The parsed metadata object is returned alongside the remaining markdown and the character offset where the body begins — useful if you need to map errors or cursor positions back to the original source.

Frontmatter is parsed with gray-matter, so any shape of YAML is accepted. Postext itself only looks at title, subtitle, author, and publishDate; additional keys are preserved on PostextContent.metadata and are yours to use.

#Block constructs

Postext recognises seven block types. Blocks are always terminated by a blank line or by the start of another block.

Block constructs at a glanceThe seven block types Postext recognises: heading, paragraph, blockquote, unordered list, ordered list, task list, and display math, each with its markdown syntax.Heading# TitleParagraphPlain text linesBlockquote> quoted textUnordered list- itemOrdered list1. itemTask list- [x] doneDisplay math$$ \int f(x) $$
Every block type and its markdown entry point.
ConstructSyntaxNotes
Heading# Title###### H6One to six # characters followed by a space and the heading text. Levels 1–6 map directly to the headings.levels config.
ParagraphPlain text over one or more linesConsecutive non-blank, non-special lines are joined with a single space and emitted as one paragraph. Manual line breaks inside a paragraph are not preserved — use a blank line to start a new paragraph.
Blockquote> quoted textEvery line of the quote must start with > (one optional space after). Consecutive quote lines merge into a single blockquote block.
Unordered list- item, * item, + itemAny of the three bullet markers is accepted. Nesting uses exactly two spaces per level, up to a maximum depth of 5.
Ordered list1. item, 2) itemDigits followed by . or ). The start number is preserved (so a list can begin at 5, or at 0). The separator rendered in the output comes from orderedLists.separator, not from the source.
Task list (GFM)- [ ] todo, - [x] doneAn unordered item with a bracketed checkbox. Accepts lowercase x or uppercase X. Rendered with the taskCheckboxChar / taskCheckedChar glyphs.
Display math$$ … $$A LaTeX formula either on a single line ($$\int_0^1 x^2,dx$$) or fenced across multiple lines with $$ markers on their own lines. Rendered centred on the column, snapped to the baseline grid like a heading, and kept vectorial in the PDF output.

A single blank line between two list items is tolerated — the list stays together. Two or more blank lines terminate the list.

Lists of mixed kinds at the same depth are accepted (you can switch from unordered to ordered mid-run), but the engine treats the runs as separate for numbering purposes. In practice, keep one kind per depth unless you have a reason to mix them.

List nesting depthLists nest using exactly two spaces per level, up to a maximum depth of five. Each level indents further and may use different bullet styling.Level 1Level 2 — two spaces inLevel 3 — four spaces inExactly two spaces per nesting level. Max depth: 5.
Two spaces per level. Max depth: five.

#Inline formatting

Inline markup is recognised inside any text block (headings, paragraphs, blockquotes, list items).

Inline formatting at a glanceMarkdown-to-rendered comparison for bold, italic, bold italic, inline code, and links.MarkdownRendered**bold**bold*italic*italic***both***both`code`code[link](https://…)link
Markdown on the left, rendered result on the right.
MarkupSyntaxNotes
Boldbold or boldRendered with bodyText.boldFontWeight. An optional bodyText.boldColor overrides the default body color for bold spans.
Italicitalic or italicRendered with the italic variant of the current font family. An optional bodyText.italicColor overrides the default body color for italic spans.
Bold italicboth or bothBoth flags combine.
Inline codecodeBackticks are stripped; the span is rendered as plain text. Distinct code styling is on the roadmap.
LinktextThe visible text is kept in the flow; the URL is discarded by the current renderer. Link handling is on the roadmap.
ImagealtInline image markdown is removed from the text. Images must be declared on PostextContent.resources so the layout engine can place them according to resourcePlacement rules.
Inline math$…$A LaTeX formula that flows with the surrounding text, e.g. $e^+1=0$. Typeset by MathJax and rendered as vector paths on every backend. Formulas whose ascent or depth would overflow the body line box are scaled down so they stay on the baseline grid. Use \$ for a literal dollar sign.

#Mathematical formulas

Math support is a first-class part of the document format. Postext parses $…$ for inline formulas and $$…$$ for display (block) formulas, and renders them via MathJax in SVG mode. The same vector paths drive all three backends, so the canvas preview, the HTML export, and the PDF output are pixel-for-pixel consistent — and the PDF stays fully vectorial regardless of the zoom level.

  • Inline: $…$. Recognised inside any text block (paragraph, heading, blockquote, list item). Contributes a single atomic, non-breaking box to the line; Knuth-Plass treats it like a word that must not be split. If the formula's natural height would break the line box, it is scaled down uniformly so the baseline grid is preserved — very tall expressions belong in display mode.
  • Display: $$…$$. Either on its own line ($$\int_0^1 x^2\,dx$$) or fenced across multiple lines with $$ markers on their own lines. Rendered centred on the column and snapped to the baseline grid with configurable top and bottom margins (math.marginTop, math.marginBottom) — exactly the same correction mechanism headings use, so the paragraph after the formula lands back on the grid.
  • Escaping: \$ is a literal dollar sign. Unmatched $ or $$ delimiters produce an unclosedMath entry in the warnings panel with a click-to-focus source anchor.
  • Errors: TeX source that MathJax rejects (undefined macros, syntax errors) surfaces as an invalidMath warning. The formula is replaced by a small red placeholder so the layout geometry stays valid.
  • Configuration: the math section of the config exposes enabled, fontSizeScale (relative to the body font size), color (inherits the body colour when unset), and the display margins.
The Euler identity $e^{i\pi}+1=0$ links the five fundamental constants.
 
$$
\int_0^{\infty} e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2}
$$

#What is NOT supported

Postext does not recognise the following CommonMark features. They are either treated as plain text (and therefore will appear literally in the output) or silently dropped:

  • Setext-style headings — the === / --- underline form. Use ATX (#) headings.
  • Fenced or indented code blocks — triple-backtick fences and 4-space indentation. Inline code works; multiline code will be rendered line-by-line as paragraphs.
  • HTML passthrough — raw <tags> are not interpreted. MDX-style tags are not supported either; Postext source is pure markdown.
  • Horizontal rules---, ***, ___.
  • Tables — pipe tables are not parsed. Tables are modeled as structured resources on PostextContent.resources.
  • Reference-style links[text][id] plus a definition block.
  • Autolinks<https://example.com>.
  • Strikethrough~~text~~. The strikethrough renderer is currently reserved for completed task items.
  • Footnote markers in markdown[^1]. Footnotes ride on PostextContent.notes and are referenced by id, not by inline syntax.

This list will shrink over time. Until then, anything not explicitly listed in the supported section above should be assumed to be literal text.

#Authoring conventions

A few conventions make the difference between a document that parses cleanly and one that surprises you:

  • Leave a blank line between blocks. Two paragraphs separated by a blank line are two paragraphs. Two paragraphs on consecutive lines become one — every line collapses into the preceding paragraph.
  • Nest lists with exactly two spaces per level. One space is parsed as a level-1 item. Three or four spaces round down to level 2 (the engine uses floor(leading / 2) + 1, clamped to depth 5). Tab indentation is not recognised — convert tabs to spaces.
  • Do not indent the first list item. Level-1 items start at column 0. Leading whitespace on a bullet implicitly raises the depth.
  • Task markers must be in square brackets with a single space. [ ], [x], [X] — no variations. [*] or [-] are not task markers; they render as literal text.
  • Blockquotes inside lists are not supported. Start the blockquote at column 0, outside the list.
  • Images and tables live in resources. Inline ![alt](src) is stripped precisely because inline images break column-aware placement. Declare each image as a resource and reference it by id — the engine then decides whether it floats, breaks the column, or moves to the top of the next page.
  • Escape dollar signs with \$ when you do not mean math. Postext interprets $…$ as inline LaTeX, so a raw $ in prose will start a formula. Prices, shell prompts, and anything else with a bare dollar sign should be written as \$.

#Worked example

A short document that exercises every supported construct:

---
title: The Typesetter's Craft
author: Anon
---
 
# Opening
 
A good book reads itself. The **reader** should never notice the
typesetter's work — only the author's voice.
 
## What makes text readable
 
Three properties matter most:
 
1. Line measure — 40 to 75 characters per line.
2. Leading — 1.3 to 1.5 times the font size.
   a. Tighter at short measures.
   b. Looser at long measures.
3. Contrast between body and headings.
 
Common failure modes include:
 
- Lines that stretch across the whole page.
- Headings that float without a following paragraph.
- Orphans and widows at column boundaries.
 
> Typography is the craft of endowing human language with a durable
> visual form.
> — Robert Bringhurst
 
### Review checklist
 
- [x] Column width under 75 characters
- [x] Leading set to 1.5
- [ ] Orphan and widow pass
- [ ] Final proofread
 
### A note on formulas
 
Inline math such as $a^2 + b^2 = c^2$ flows with the surrounding text, and
display math sits centred on the baseline grid:
 
$$
\int_0^1 x^2\,dx = \tfrac{1}{3}
$$

The same document, rendered through the layout engine, produces a structured VDTDocument whose pages carry each of these blocks as typed entries — see the Architecture page for how blocks become geometry.