Skip to content

Mermaid

```mermaid blocks are drawn as SVG by Pele, a Mermaid renderer in TypeScript by the Obsidian team. ope runs it in the JavaScript engine that also runs DataviewJS, so it needs no Node.js, npm or browser. It reads Mermaid 12.1 syntax and draws every diagram type Mermaid has, in a few milliseconds per diagram. A block Pele cannot draw, for example because of a syntax error, stays a code block and the export prints a line starting with Warning:.

Markdown

```mermaid
flowchart LR
    A[Notes] --> B{Export}
    B -->|PDF| C[Readers]
    B -->|Redline| D[Reviewers]
```

```mermaid
sequenceDiagram
    Author->>ope: export ./Notes
    ope->>Author: handbook.pdf
```

PDF

A flowchart and a sequence diagram

The output is SVG, so text stays sharp and can be searched.

Options

Option Effect Default
-O mermaid=off Keep Mermaid blocks as code. on
-O mermaid.config=PATH JSON Mermaid config, such as themeVariables.fontSize. none
-O mermaid.max_height=LENGTH Largest height, such as 10cm. Taller diagrams are scaled down. none
-O mermaid.landscape=on\|off Put wide diagrams on landscape pages. off
-O mermaid.landscape_ratio=R Width to height ratio from which a diagram that has to shrink goes to landscape. 1.4
-O mermaid.landscape_min_font=PT Smallest text size a diagram may shrink to on a portrait page before it goes to landscape, or to fit a tall diagram on one page. 7
-O mermaid.fit=on\|off Scale or split diagrams that are taller than the page. on

A template can set a base config with the mermaid key in template.yaml or Template.mermaid_config(). mermaid.config is merged over it.

ope export ./Notes -O mermaid.config=mermaid.json
{ "themeVariables": { "fontSize": "14px", "fontFamily": "Georgia, serif" } }

Earlier versions drew diagrams with mermaid-cli, which needed Node.js and a headless Chromium. ope no longer uses it. Its options mermaid.renderer, mermaid.theme, mermaid.format, mermaid.scale, mermaid.cli, mermaid.puppeteer_config and mermaid.trim have no effect, and the export prints a warning when one is set. mermaid.renderer=pele is accepted without a warning.

Colors and fonts

Pele draws with its own neutral style. Its colors are the CSS variables listed under Theming, such as --pele-fg, --pele-surface, --pele-border, --pele-line, --pele-accent and --pele-series-1 to --pele-series-8. WeasyPrint does not resolve CSS variables in SVG, so ope writes their values into the SVG. They come from the template's CSS:

  • The label font is the font-family of body, the monospace font the one of code or pre, and the text color the color of body. Diagrams use the font of the document.
  • themeVariables.fontFamily in the Mermaid config sets the label font too.
  • themeVariables.fontSize in the Mermaid config or in the front matter of a diagram sets the base font size.
  • The other themeVariables of the Mermaid config or of the front matter of a diagram set the colors of that diagram, see Mermaid theme variables. They win over the template's values.
  • --pele-* variables in a rule for :root, html, body or .pele set any color. They may use var() of other custom properties of those rules.
.pele {
  --pele-surface: #eef2f7;
  --pele-border: #6b7f99;
  --pele-accent: var(--color-accent);
}

Mermaid theme variables

Pele does not read Mermaid's themeVariables. ope maps each variable that has a counterpart in Pele's drawing, so a diagram looks as it does in Obsidian. A variable in the front matter of a diagram wins over the Mermaid config. These variables apply to every diagram type:

Theme variable Effect
fontFamily Font of the labels.
fontSize Base font size. Pele derives the size of every label from it.
textColor, else primaryTextColor Text color (--pele-fg).
lineColor Lines and arrows (--pele-line).
background Background color, also used for text on dark bars (--pele-bg).
titleColor Diagram title, unless a variable below sets it.

Per diagram type, a list such as taskTextOutsideColor, taskTextDarkColor means the first one that is set wins, as in Mermaid:

Diagram Theme variable Effect
Flowchart mainBkg, primaryColor Node fill.
nodeBorder, primaryBorderColor Node border.
nodeTextColor, primaryTextColor Node text.
clusterBkg, clusterBorder Subgraph fill and border.
titleColor Subgraph title.
edgeLabelBackground Background of edge labels.
Sequence actorBkg, mainBkg, primaryColor Participant fill.
actorBorder, primaryBorderColor Participant border.
actorTextColor, primaryTextColor Participant text.
actorLineColor Lifelines.
signalColor, textColor Message lines and arrows.
signalTextColor, textColor Message text.
noteBkgColor, noteBorderColor, noteTextColor Notes.
activationBkgColor, activationBorderColor Activation bars.
labelBoxBkgColor, labelBoxBorderColor Loop and alt frames, and the box of their keyword.
labelTextColor, loopTextColor Frame keyword and frame label.
Gantt taskBkgColor, taskBorderColor Tasks and milestones.
activeTaskBkgColor, activeTaskBorderColor Active tasks.
doneTaskBkgColor, doneTaskBorderColor Done tasks.
critBkgColor, critBorderColor Critical tasks.
taskTextColor Label on a bar.
taskTextDarkColor Label on a done or active bar.
taskTextOutsideColor, taskTextDarkColor Label next to a bar or milestone.
sectionBkgColor, altSectionBkgColor, sectionBkgColor2 Section bands.
gridColor, todayLineColor, excludeBkgColor, vertLineColor Grid, today marker, excluded days, vertical markers.
Timeline cScale0 to cScale7 Color of each period and its events (--pele-series-1 to 8).
cScaleLabel0 to cScaleLabel7 Text of each period and its events.
Quadrant chart quadrant1Fill to quadrant4Fill Quadrant fills.
quadrant1TextFill to quadrant4TextFill Quadrant labels.
quadrantPointFill, quadrantPointTextFill Points and their labels.
quadrantXAxisTextFill, quadrantYAxisTextFill Axis labels.
quadrantInternalBorderStrokeFill, quadrantExternalBorderStrokeFill Quadrant borders. Pele draws one kind of border.
quadrantTitleFill Title.
Pie pie1 to pie8 Slice colors (--pele-series-1 to 8).
pieStrokeColor, pieStrokeWidth, pieOpacity Slice border and opacity.
pieSectionTextColor, pieLegendTextColor, pieTitleTextColor Slice labels, legend and title.
XY chart xyChart.plotColorPalette Colors of bars and lines (--pele-series-1 to 8). A shorter list starts again from its first color.
xyChart.titleColor Title.
xyChart.xAxisLabelColor, xyChart.yAxisLabelColor Axis labels. Pele draws both axes in one color.
xyChart.xAxisTitleColor, xyChart.yAxisTitleColor Axis titles.
xyChart.xAxisLineColor, xyChart.yAxisLineColor Axis lines and ticks.

The colors become --pele-* variables of that diagram or CSS rules for Pele's class names in the SVG. They never change sizes, so labels keep the width they were measured with.

Pele reads most of the per-diagram config itself, for example gantt.barHeight, gantt.fontSize, gantt.useWidth, quadrantChart.chartWidth, quadrantChart.chartHeight, quadrantChart.pointRadius, xyChart.width, xyChart.chartOrientation, timeline.disableMulticolor, pie.textPosition and sequence.mirrorActors. The font sizes of a quadrant chart come from quadrantChart.titleFontSize, quadrantLabelFontSize, xAxisLabelFontSize, yAxisLabelFontSize and pointLabelFontSize. Pele derives the size of other text from fontSize, so keys such as xyChart.titleFontSize or themeVariables.pieTitleTextSize have no effect.

The export prints one warning that lists every key of the Mermaid config with no effect: theme other than default or base, themeCSS, theme variables without a counterpart (such as pieOuterStrokeColor, cScaleInv0 or cScale8 to cScale11) and keys of the gantt, quadrantChart, xyChart, timeline, pie and sequence sections that Pele does not read.

Pele needs the width of every label to lay a diagram out. ope measures them with Pango, the text engine WeasyPrint uses, in the font of the PDF and at the size they are drawn, so labels fit their boxes in any font. When the template font is not installed, labels are measured and drawn in the font that replaces it. Each label is drawn as one line of shaped text, so Arabic letters join, Arabic and Hebrew read from right to left and Hindi or Thai vowel signs stay on their letters.

A character that no installed font has is drawn as □ (or ? when no font has □ either), and the boxes of the diagram are sized for it. The export prints a warning that lists these characters. Install fonts that cover the scripts of the labels, for example Noto, see Fonts and scripts.

Differences from Mermaid

Pele reads the same syntax as Mermaid, but it is not mermaid.js. A diagram can look different from the one in Obsidian:

  • Pele has its own layout, so boxes and lines can be placed differently.
  • Mermaid's built-in themes (theme: dark, forest, neutral), themeCSS and %%{init}%% theme settings are not used. Theme variables are mapped where Pele has a counterpart, see Mermaid theme variables. Other colors can be set with --pele-* variables in the template, see Colors and fonts.
  • Pele moves quadrant chart point labels to avoid overlap, so labels can sit in other places than in Mermaid. A point label only wraps when it is wider than the plot area.
  • layout: elk is not used. Pele always lays a diagram out on its own.
  • Links and images in diagrams are turned off.
  • <br> and </br> both break a label into lines.

The Pele build is part of ope (version 1.12.0, MIT license, see the license header of obsidian_pdf_exporter/core/assets/pele.js). ope applies a few small changes to it when it loads, listed in PELE_PATCHES in obsidian_pdf_exporter/core/mermaid_pele.py. The quadrant chart font sizes and the width at which point labels wrap are such a change. It stays until a Pele release has the fix, see obsidianmd/pele#108. scripts/update_pele.py in the repository replaces it with another version and checks it against the hash the npm registry publishes. CI checks that the stored build is the pinned one.

Diagram size

A diagram keeps its natural size and only shrinks when it is wider than the text. mermaid.max_height scales down taller ones. Other sizes belong in the template's CSS: every diagram has the classes doc-image, mermaid and mermaid-<type>, see CSS reference.

Pele gives every diagram an exact size with a margin of 8 px, so there is no empty space to trim.

%% comment lines in a block are removed before rendering.

Wide diagrams

A diagram wider than the text shrinks, and so does its text. Diagrams stay on portrait pages by default. With -O mermaid.landscape=on, a diagram that gets hard to read goes to its own landscape page, like a wide table.

Markdown

A wide diagram moves to its own landscape page:

```mermaid
flowchart LR
    A[Draft] --> B[Review] --> C[Approve] --> D[Export PDF] --> E[Sign] --> F[Publish] --> G[Archive] --> H[Retire]
```

Figure 1: The document life cycle

The text continues on a portrait page.

PDF

A wide flowchart on a landscape page, with its caption

With mermaid.landscape=on, a diagram moves when it has to shrink to fit a portrait page and either its width is at least mermaid.landscape_ratio times its height, or its text would get smaller than mermaid.landscape_min_font points. It only moves when the landscape page shows it larger, so a tall diagram stays where it is. On the landscape page it is sized to fit the page height, with room for a caption.

A short paragraph right below the diagram that starts with a word and a number, such as Figure 1: Overview, is its caption and moves with it. The diagram gets the class landscape, its wrapper wide-table-section landscape-figure, so a template's landscape page for tables applies to diagrams too.

ope export ./Notes -O mermaid.landscape=on
ope export ./Notes -O mermaid.landscape=on -O mermaid.landscape_ratio=2

Tall diagrams

A diagram taller than the page would run past the bottom margin. ope keeps every diagram inside the page content box. The size of the page and its margins come from the template, for portrait and landscape pages alike.

  • When the diagram's text stays at least mermaid.landscape_min_font points tall, the diagram is scaled down to the height of the page. It keeps its aspect ratio. A caption below it stays on the same page.
  • Otherwise the diagram is cut into parts that follow each other over several pages. The parts keep the width the diagram had, so the text keeps its size. Each cut goes through a row near the bottom of the page where little is drawn, so it does not go through a box or a line of text when there is a gap between them.

Markdown

A sequence diagram that is taller than the page goes on over two pages:

```mermaid
sequenceDiagram
    participant C as Client
    participant S as Server
    C->>S: Request 1
    S-->>C: Response 1
    C->>S: Request 2
    S-->>C: Response 2
    C->>S: Request 3
    S-->>C: Response 3
    C->>S: Request 4
    S-->>C: Response 4
    C->>S: Request 5
    S-->>C: Response 5
    C->>S: Request 6
    S-->>C: Response 6
    C->>S: Request 7
    S-->>C: Response 7
    C->>S: Request 8
    S-->>C: Response 8
    C->>S: Request 9
    S-->>C: Response 9
    C->>S: Request 10
    S-->>C: Response 10
    C->>S: Request 11
    S-->>C: Response 11
    C->>S: Request 12
    S-->>C: Response 12
    C->>S: Request 13
    S-->>C: Response 13
    C->>S: Request 14
    S-->>C: Response 14
    C->>S: Request 15
    S-->>C: Response 15
    C->>S: Request 16
    S-->>C: Response 16
    C->>S: Request 17
    S-->>C: Response 17
    C->>S: Request 18
    S-->>C: Response 18
```

The text continues after the diagram.

PDF

A long sequence diagram cut into two parts on two pages

Every part holds the whole drawing and shows only its band of it, so a search in the PDF also finds the text of the hidden bands. Fitting needs a second layout of the document, which only runs when a diagram is too tall. -O mermaid.fit=off turns it off, mermaid.max_height gives all diagrams a smaller limit.