Mermaid in a Web Worker: the getBBox rules, side by side#

Mermaid 12.2.0 can run in a Web Worker in the browser: on a DOM without layout (linkedom), with getBBox, getComputedTextLength and getBoundingClientRect answered by a small measuring layer (text widths from OffscreenCanvas.measureText). The page's main thread then never runs mermaid.

Checked against Chrome on every example of mermaid's docs/syntax at 12.2.0 (538 examples, 33 diagram types including zenuml; htmlLabels: false, dagre, classic look): each example is drawn on the main thread and in the worker, both in Chrome, and compared. 538 of 538 match: the size within 5%, and every text within max(8 px, 2% of the diagonal).

Each rule below was needed because some diagram type drew differently without it. For each one: mermaid in Chrome, the worker as it is, and the worker with only that rule switched off. The images are captured, so they keep showing the difference whatever mermaid version is current. The source is under each one, as text.


1. CSS cascade order#

When a font setting is given in several places, the inline style wins over the stylesheet, and the stylesheet wins over the plain SVG attribute, so the worker has to measure text in the font the browser will actually use.

Example: ishikawa #0 of the docs.

Mermaid in Chrome (main thread)

Mermaid in Chrome (main thread)

The worker

The worker

The worker without this rule

The worker without this rule

ishikawa-beta
    Blurry Photo
    Process
        Out of focus
        Shutter speed too slow
        Protective film not removed
        Beautification filter applied
    User
        Shaky hands
    Equipment
        LENS
            Inappropriate lens
            Damaged lens
            Dirty lens
        SENSOR
            Damaged sensor
            Dirty sensor
    Environment
        Subject moved too quickly
        Too dark


2. Empty elements in a group's box#

An empty label that holds an empty <tspan> still counts in Chrome as a single point at (0,0) in its group's box, and ignoring it changes the boxes mermaid lays the diagram out with: here the relationships come out shorter and the tables closer together.

Example: entityRelationshipDiagram #13 of the docs.

Mermaid in Chrome (main thread)

Mermaid in Chrome (main thread)

The worker

The worker

The worker without this rule

The worker without this rule

erDiagram
    direction LR
    CUSTOMER ||--o{ ORDER : places
    CUSTOMER {
        string name
        string custNumber
        string sector
    }
    ORDER ||--|{ LINE-ITEM : contains
    ORDER {
        int orderNumber
        string deliveryAddress
    }
    LINE-ITEM {
        string productCode
        int quantity
        float pricePerUnit
    }


3. dominant-baseline middle = half the x-height#

Switching it off only shifts a whole diagram by 2 to 4 px: nothing moves relative to anything else, so a picture cannot show it.


4. SVG whitespace#

SVG drops newlines, turns tabs into spaces, collapses runs of spaces and trims the ends, so text measured with its raw spaces comes out wider than the browser draws it, and mermaid moves the label out of its bar.

Example: gantt #1 of the docs.

Mermaid in Chrome (main thread)

Mermaid in Chrome (main thread)

The worker

The worker

The worker without this rule

The worker without this rule

gantt
    dateFormat  YYYY-MM-DD
    title       Adding GANTT diagram functionality to mermaid
    excludes    weekends
    %% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)

    section A section
    Completed task            :done,    des1, 2014-01-06,2014-01-08
    Active task               :active,  des2, 2014-01-09, 3d
    Future task               :         des3, after des2, 5d
    Future task2              :         des4, after des3, 5d

    section Critical tasks
    Completed task in the critical line :crit, done, 2014-01-06,24h
    Implement parser and jison          :crit, done, after des1, 2d
    Create tests for parser             :crit, active, 3d
    Future task in critical line        :crit, 5d
    Create tests for renderer           :2d
    Add to mermaid                      :until isadded
    Functionality added                 :milestone, isadded, 2014-01-25, 0d

    section Documentation
    Describe gantt syntax               :active, a1, after des1, 3d
    Add gantt diagram to demo page      :after a1  , 20h
    Add another diagram to demo page    :doc1, after a1  , 48h

    section Last section
    Describe gantt syntax               :after doc1, 3d
    Add gantt diagram to demo page      :20h
    Add another diagram to demo page    :48h


5. x/y/dx/dy belong to the next character, the inner one wins#

A dy on a <text> and a dy on its first <tspan> do not add up: the tspan's value replaces the outer one, so adding them puts the first line a whole line lower and stretches the timeline.

Example: timeline #5 of the docs, with a 32 px font size added (the first line) so the difference shows.

Mermaid in Chrome (main thread)

Mermaid in Chrome (main thread)

The worker

The worker

The worker without this rule

The worker without this rule

%%{init: {"themeVariables": {"fontSize": "32px"}}}%%
timeline TD
  title MermaidChart 2023 Timeline
    section 2023 Q1 <br> Release Personal Tier
      Bullet 1 : sub-point 1a : sub-point 1b
      Bullet 2 : sub-point 2a : sub-point 2b
    section 2023 Q2 <br> Release XYZ Tier
      Bullet 3 : sub-point <br> 3a : sub-point 3b
      Bullet 4 : sub-point 4a : sub-point 4b


6. Transforms and nested <svg> viewBox#

An icon is a nested <svg> whose content is in viewBox units and must be scaled onto its width and height before it counts in the box, otherwise mermaid frames the icon in the wrong place.

Example: flowchart #68 of the docs.

Mermaid in Chrome (main thread)

Mermaid in Chrome (main thread)

The worker

The worker

The worker without this rule

The worker without this rule

flowchart TD
    A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 }


7. Emoji ink#

Chrome adds a colour emoji's own ink to the text's box, and it rises above the font's ascent, so without it each row of a tree with emoji comes out shorter.

Example: treeView #11 of the docs.

Mermaid in Chrome (main thread)

Mermaid in Chrome (main thread)

The worker

The worker

The worker without this rule

The worker without this rule

treeView-beta
    🚀 rocket-app/
        📦 packages/
            🎨 ui/
            🛠️ utils/
        🧪 tests/
        📝 README.md
        ⚙️ config.yaml