Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Typography

This chapter defines the typographic system for MarkSpec rendered output (PDF documents, HTML books, slide decks). All values are defaults — authors may override them in project.yaml.

Foundations

Fonts

IBM Plex is chosen for its technical clarity, broad Unicode coverage, and open license. It ships in Sans, Mono, and Serif variants — covering all document roles without mixing font families.

Sans Regular — The braking system shall achieve full braking force within 150ms. Sans Italic — Table: Sensor thresholds for the braking ECU Sans SemiBold — Sensor noise filtering Mono Regular — Id: SRS_01HGW2Q8MNP3 | Satisfies: SYS_BRK_0042 Serif Regular — The design philosophy prioritizes simplicity as a discipline.

Figure: IBM Plex font roles

The font family is IBM Plex — a typeface designed for technical documentation with broad Unicode coverage, multiple weights, and native support for tabular figures.

RoleFamilyWeightUsage
Body textIBM Plex SansRegular (400)Paragraphs, list items, captions
Body emphasisIBM Plex SansItalic (400)Emphasized text
Body strongIBM Plex SansSemiBold (600)Strong text, headings
MonospaceIBM Plex MonoRegular (400)Code blocks, inline code, entry IDs
Long proseIBM Plex SerifRegular (400)Optional — for narrative-heavy documents

Only Sans and Mono are required. Serif is available for projects that prefer it for body text.

Font fallback chain

IBM Plex is open-source (OFL) and bundled in CI. For contexts where Plex is not installed (local preview, SVGs viewed on GitHub), use this fallback chain:

RoleFallback chain
Sans"IBM Plex Sans", "Segoe UI", "Helvetica Neue", "DejaVu Sans", sans-serif
Mono"IBM Plex Mono", "Cascadia Mono", "SF Mono", "DejaVu Sans Mono", monospace
Serif"IBM Plex Serif", "Georgia", "DejaVu Serif", serif

The chain picks the best match per platform: Segoe UI on Windows, Helvetica Neue on macOS, DejaVu Sans on Linux. The final generic keyword (sans-serif, monospace, serif) ensures a last resort on any system.

PlantUML only accepts a single font name — no fallback chain. Use IBM Plex Sans and ensure the font is installed on the build machine. When Plex is not available, PlantUML falls back to its built-in SansSerif.

Type scale

The minor third ratio (1.2) produces a compact scale that works for technical documents where space is at a premium. Larger ratios (e.g., major third 1.25) waste vertical space; smaller ratios lack visual hierarchy.

H1 — Section title (20pt) H2 — Subsection (16.5pt) H3 — Topic (14pt) H4 — Subtopic (12pt) Body text at 10pt — the sensor driver shall debounce raw inputs. Caption at 8.5pt — Table: Sensor thresholds

Figure: Type scale hierarchy

Sizes follow a minor third ratio (1.2) rounded to half-points. The base size is 10pt for A4 documents.

ElementSizeWeightLeading
H120ptSemiBold24pt
H216.5ptSemiBold20pt
H314ptSemiBold17pt
H412ptSemiBold15pt
Body10ptRegular14pt
Small / caption8.5ptRegular12pt
Code block9ptRegular (Mono)13pt
Inline code9ptRegular (Mono)
Footer / header8ptRegular10pt

Spacing

A 4pt base unit keeps spacing consistent and proportional. All values are multiples of 4 — no magic numbers, easy to reason about.

Sensor noise filtering space-6 Debounce strategy space-4 space-3 space-2

Figure: Spacing scale applied to document structure

A 4pt base unit defines the spacing scale. All vertical and horizontal spacing derives from multiples of this unit.

TokenValueUsage
space-14ptInline padding, tight gaps
space-28ptBetween list items, cell padding
space-312ptBetween paragraphs within a section
space-416ptBetween sections (below headings)
space-624ptBetween major sections (below H2)
space-832ptPage-level separation

Color palette

Document palette (default)

Monochrome with one accent ensures B&W printability — critical for auditor handoffs and archived compliance documentation.

text secondary muted accent accent-dark bg-code bg-alert border

Figure: Document palette swatches

The document palette is monochrome with a single accent color. All content remains legible in greyscale print and B&W photocopy.

TokenHexUsage
text#1a1a1aBody text, headings
secondary#6b6b6bCaptions, metadata, footer
muted#999999Disabled, placeholder
accent#0072B2Links, cross-references, active elements
accent-dark#005580Visited links, hover state
bg-code#f5f5f5Code block background
bg-alert#f0f4f8Alert/admonition background
border#d4d4d4Table rules, dividers, code block border
white#ffffffPage background

Diagram palette (categorical)

The Paul Tol qualitative palettes are designed for scientific publishing and tested under protanopia, deuteranopia, and tritanopia. Two sub-palettes are used, selected by output target:

  • Screen / HTML (default, ~90% of usage) — Tol vibrant: more saturated, designed for data visualization on screens.
  • Print / PDFTol bright: softer tones, designed for print documents.
Screen (Tol vibrant) Blue Cyan Teal Orange Red Magenta Grey Print (Tol bright) Blue Cyan Green Yellow Red Purple Grey

Figure: Paul Tol categorical palettes — vibrant (screen) and bright (print)

When a diagram requires categorical distinction (e.g., component types, data flows, requirement layers), use the appropriate Tol palette. All colors are distinguishable under protanopia, deuteranopia, and tritanopia.

NameScreen (Tol vibrant)Print (Tol bright)
Blue#0077BB#4477AA
Cyan#33BBEE#66CCEE
Teal#009988#228833
Orange#EE7733#CCBB44
Red#CC3311#EE6677
Magenta#EE3377#AA3377
Grey#BBBBBB#BBBBBB

Note: Tol vibrant has no yellow — orange (#EE7733) is the closest equivalent. Tol bright has no magenta — purple (#AA3377) is used instead.

When categorical distinction is not needed, diagrams use the document palette (monochrome + accent).

Alert colors

Screen (full border + tint background, Tol vibrant) Note— informational context for the reader. Tip— helpful suggestion for the reader. Important— key information the reader must know. Warning— potential issue the reader should avoid. Caution— action may cause data loss or harm. Print / PDF (full border, no background tint, Tol bright) Note— informational context for the reader. Tip— helpful suggestion for the reader. Important— key information the reader must know. Warning— potential issue the reader should avoid. Caution— action may cause data loss or harm.

Figure: Alert rendering — screen (full border + tint, Tol vibrant) vs print (full border only, Tol bright)

Alerts use a full rectangular border on all sides, distinguishing them from entry blocks (which use a left border only). Screen adds a light background tint; print uses an outline-only style to remain legible in greyscale and B&W photocopy.

AlertScreen border (Tol vibrant)Screen bgPrint border (Tol bright)
NOTE#0077BB (blue)#eef5fc#4477AA (blue)
TIP#009988 (teal)#eef7f5#228833 (green)
IMPORTANT#EE3377 (magenta)#fdeef5#AA3377 (purple)
WARNING#EE7733 (orange)#fdf4ee#CCBB44 (yellow)
CAUTION#CC3311 (vermillion)#fdeeed#EE6677 (red)

Entry type colors

Entry blocks are colored by type (the nature of the artifact), not by layer. The layer is already encoded in the ID prefix and does not need redundant color signaling.

Two Paul Tol sub-palettes are used, selected by output target:

TypePrefixesPrint (Tol bright)Screen (Tol vibrant)
reqSTK, SYS, SWE, SRSBlue #4477AABlue #0077BB
specARC, SAD, ICDGreen #228833Teal #009988
testTST, VAL, SIT, SWTRed #EE6677Orange #EE7733
  • Print theme (Tol bright): default for PDF output. Softer tones, designed by Paul Tol for documents.
  • Screen theme (Tol vibrant): default for HTML output. More saturated, designed by Tol for data visualization / dashboards.

Both palettes are colorblind-safe by design (tested for protanopia, deuteranopia, tritanopia). No red-green confusion at the chosen mappings.

The print palette uses three colors from the Paul Tol bright qualitative scheme (7 colors). The screen palette uses three colors from the Paul Tol vibrant qualitative scheme (7 colors). Both are single-scheme picks — no cross-scheme mixing.

The type color is applied to:

  1. The 2px left border of the entry block.
  2. The display ID text on the title line.

No other element uses the type color. Body text, metadata, and pills use the document’s standard text/background colors.

Design tokens

The entries: section of theme/tokens.yaml is the canonical source:

entries:
  req: { print: "#4477AA", screen: "#0077BB" }
  spec: { print: "#228833", screen: "#009988" }
  test: { print: "#EE6677", screen: "#EE7733" }

Running just tokens regenerates all downstream files from this source:

Output fileToken names emitted
themes/light.typ (Typst)entry-req, entry-spec, entry-test
themes/dark.typ (Typst)entry-req, entry-spec, entry-test
theme/markspec.css--ms-entry-req, --ms-entry-spec, --ms-entry-test

Palette selection by output target:

  • PDF output — uses the Typst light theme (themes/light.typ), which takes the print value (Tol bright) for each type.
  • HTML output — uses the CSS --ms-entry-* custom properties, which always take the screen value (Tol vibrant).
  • Dark PDF theme (themes/dark.typ) takes the screen value (Tol vibrant).

The selection is implemented in scripts/gen_theme.ts as palette = name === "light" ? "print" : "screen". Edit tokens.yaml and run just tokens to change any color — never edit theme files directly.

Document elements

This module implements SRS_BRK_0107 . See ADR-003 for diagram rules. Inline code SRS_BRK_0107 is not clickable.

Figure: Resolved references vs inline code

ContextFontColorUnderlineClickable
[text](url) in proseSurrounding (Sans)accentPDF: always, HTML: on hoverYes
<url> autolinkMonoaccentPDF: always, HTML: on hoverYes
`code` inlineMonotextNoneNo
{{ref.ID}} resolvedSansaccentPDF: always, HTML: on hoverYes
URL inside code fenceMonotextNoneNo
Footnote markerSans, superscriptaccentNoneYes

Mustache references ({{namespace.id}}) are resolved by tooling before rendering. In the output they appear as regular cross-reference links, not as code. URLs and code literals inside fenced code blocks are never linked — code blocks are inert.

Tables

Table: Sensor thresholds Sensor Min Max Unit Pressure 0 1023 raw Temperature -40 150 °C

Figure: Table rendering with caption, header, and borders

PropertyValue
Header background#f5f5f5
Header weightSemiBold
Cell padding4pt vertical, 8pt horizontal
Border#d4d4d4, 0.5pt solid
Alternating rowsNone (clean default)
Caption positionAbove table, italic, text color

Code blocks

fun debounce(raw: Int ): Int { // filter noise below threshold return filtered } bold = keywords italic = comments regular = identifiers

Figure: Monochrome syntax highlighting

PropertyValue
FontIBM Plex Mono, 9pt
Background#f5f5f5
Border#d4d4d4, 0.5pt solid
Border radius3pt
Padding12pt
Line numbersOff by default
Syntax highlightingMonochrome — keywords bold, strings italic, comments secondary

Syntax highlighting uses weight and style variation rather than color to remain legible in B&W.

Captions

PropertyValue
FontIBM Plex Sans, 8.5pt, italic
Colorsecondary (#6b6b6b)
Table captionAbove the table
Figure captionBelow the image
Spacingspace-2 (8pt) between caption and content

Entry blocks

Entry blocks use admonition-style rendering: a 2px colored left border provides visual distinction from surrounding prose, with no background tint or horizontal rules.

SWE_BRK_0107 Median filter implementation ASIL-B The braking ECU shall apply a 5-sample median filter to the raw brake pressure sensor input before processing. Id: SWE_01HGW2Q8MNP3 · Satisfies: SYS_BRK_0042 title body metadata

Figure: Entry block anatomy — admonition-style rendering

Block layout

Each entry block is laid out top-to-bottom:

  1. Title line — display ID (body size, weight 500, type color) + title text (body size, weight 500, primary text color) + pill group (inline, wraps as block).
  2. Body — body size, regular weight, primary text color. Margin-top: space-1 (4pt) from title line.
  3. Metadata line — small size, italic, secondary text color. Margin-top: space-2 (8pt) from body. Attributes separated by · (middle dot with spaces). Attribute order: Id, then traceability (Satisfies / Verifies / Derived-from), then any other key-value attributes.

The 2px left border starts flush with the first line of the title — no top padding above the title. Inter-block margin is space-3 (12pt).

Label pills

Labels (e.g., ASIL-B, performance, real-time) are rendered as pills (rounded badges):

  • Font size: small (8.5pt), weight 500.
  • Padding: 1pt vertical, 7pt horizontal.
  • Border-radius: 9pt (fully rounded).
  • Background: code background color (neutral — no type color).
  • Text color: secondary text color.
  • Each pill has white-space: nowrap (a single label never breaks).

All pills for an entry are wrapped in a pill group:

  • Inline flex container with gap: space-1 (4pt) and flex-wrap: wrap.
  • The pill group sits on the title line after the title text.
  • If the group fits on the title line, it renders inline.
  • If it doesn’t fit, the entire group wraps to the next line as a block.
  • No labels = no pill group rendered.

Attribute values that reference other entries (Satisfies, Verifies, Derived-from) are rendered as links:

  • Dashed underline — underline color is lighter than the text (border secondary or equivalent, ~0.3 alpha).
  • No link color — the text stays in the same italic/secondary style as the rest of the metadata line.
  • Underline offset: 2pt.
  • In HTML: cursor: pointer, link target is the anchor of the referenced entry.
  • In PDF: dashed-underline style only. Typst internal link navigation (#link(<target>)) is deferred until req-block emits labeled anchors (see issue #181).

Non-reference values (Id value, other key-value pairs) have no underline — they are plain italic text.

PDF rendering (Typst)

Entry blocks in PDF output are rendered by req-block() in packages/markspec-typst/entry.typ. The block geometry matches the spec above:

  • Border: stroke: (left: 2pt + color) — left side only.
  • Inset: left: 12pt, top: 0pt, bottom: 4pt, right: 0pt.

The function signature:

#req-block(
  type: "req",         // "req" | "spec" | "test"
  display-id: "…",
  title: "…",
  body: render("…"),   // entry body rendered via cmarker
  attrs: (("Key", "Value"),),   // (key, value) pairs; trailing comma required
  labels: ("ASIL-B",),          // label strings; trailing comma required
  theme: theme,
)

The trailing comma on single-element arrays is required: (expr) is a parenthesised expression in Typst, not a 1-element array.

The body argument receives already-rendered Typst content: render("…") calls cmarker’s render to convert the entry’s Markdown body to Typst. The pipeline that builds this call lives in packages/markspec/render/typst/template.ts (renderEntryTypst).

Document layout

Page layout

A4 with 25 mm margins gives a 160 mm text width — comfortable for 10pt body text at roughly 80 characters per line, matching the Markdown source line width.

25 mm 25 mm 25 mm 25 mm Document title 1 Sensor noise filtering MarkSpec v0.0.3 2026-03-23 210 mm 297 mm

Figure: A4 page layout with margins, header, and footer

Default page size is A4 (210 × 297 mm), portrait orientation.

PropertyValue
Page sizeA4 (210 × 297 mm)
Top margin25 mm
Bottom margin25 mm
Left margin25 mm
Right margin25 mm
HeaderDocument title (left), page number (right)
FooterProject name and version (left), date (right)
Column count1
Paragraph spacing6pt after
Paragraph indentNone (block style)

Cover page

MarkSpec Language Specification v0.0.3 2026-03-23 INTERNAL

Figure: Cover page layout

The cover page is generated from project.yaml metadata. Layout:

ElementPositionStyle
Project nameUpper third, centered28pt, SemiBold
Document titleCenter, centered20pt, SemiBold
VersionBelow title12pt, Regular, secondary
DateBelow version10pt, Regular, secondary
ClassificationBottom third, centered10pt, SemiBold, accent

The cover page has no header or footer.

Diagrams

Format and embedding

Diagrams are stored as SVG files alongside the documents that reference them. Embedded using standard Markdown image syntax with relative paths:

![System overview](specification-overview.svg)

Always set the viewBox attribute. Omit fixed width/height — let the container control display size. The same SVG works across PDF, HTML, and slide output.

Set font-family on the root <svg> or on individual <text> elements using the Sans fallback chain from the Fonts section:

Component label

Figure: SVG component with font fallback chain

SVG sizing for documents (A4, ~25 mm margins)

TypeRatioviewBox (w × h)Use
Full width16:9700 × 400Architecture overviews, flow diagrams
Full width tall4:3700 × 525Detailed system diagrams
Full width square1:1700 × 700State machines, class diagrams
Half width4:3340 × 250Inline diagrams, small illustrations
Full page3:4700 × 900Complex diagrams needing a full page

Visual style

Braking System — Software Architecture Sensor Driver SRS_BRK_0107 Debounce Filter SRS_BRK_0108 Brake Controller SRS_BRK_0109 raw filtered CAN Bus Debounce window: 5ms (configurable) Satisfies: SYS_BRK_0042

Figure: Monochrome architecture diagram (document palette)

Requirement Layers STK — Stakeholder SYS — System ICD — Interface SRS — Software SAD — Architecture SWT — Test Arrows show Satisfies / Verified-by direction

Figure: Categorical component diagram (Tol palette)

Guidelines:

  • Monochrome preferred. Black, white, and shades of grey. Diagrams must be readable when printed in greyscale — color is decorative, not structural.
  • Categorical color. When distinction is needed (component types, data flows), use the Tol palette. Otherwise use the document palette.
  • High contrast. Black strokes on white background. Avoid light grey lines or low-contrast fills.
  • Stroke weight. 1.5–2px for primary lines, 1px for secondary. No hairlines (< 1px).
  • Text size. Minimum 12px for labels, 14px for titles.
  • Font. Use the Sans fallback chain from the Fonts section.
  • Whitespace. Generous padding between elements.

Tooling

Any tool that produces clean SVG (draw.io, Excalidraw, Inkscape, D2, Graphviz). The SVG output is what matters, not the authoring tool.

PlantUML is recommended for sequence and state machine diagrams — textual, diffable source that lives in the repository. Source files use .puml extension, generated SVG uses .plantuml.svg suffix.

PlantUML presets

Key sizing settings:

  • skinparam svgDimensionStyle false — removes fixed width/height from the SVG, enabling proper scaling via viewBox.
  • scale — controls overall diagram size. Use scale 1 for half-width diagrams, scale 2 for full-width.
  • skinparam ranksep / nodesep — vertical and horizontal spacing between elements. Scale proportionally with scale.

Monochrome (default) — uses the document palette:

Sequence diagram — monochrome preset

Figure: PlantUML sequence diagram with monochrome preset

@startuml
skinparam svgDimensionStyle false
skinparam shadowing false
scale 2
skinparam ranksep 30
skinparam nodesep 35
skinparam defaultFontName "IBM Plex Sans"
skinparam defaultFontSize 12
skinparam backgroundColor #ffffff
skinparam ArrowColor #1a1a1a
skinparam BorderColor #1a1a1a
skinparam ParticipantBackgroundColor #f5f5f5
skinparam ParticipantBorderColor #d4d4d4
skinparam NoteBackgroundColor #f5f5f5
skinparam NoteBorderColor #d4d4d4
skinparam SequenceLifeLineBorderColor #6b6b6b
@enduml

Categorical — Tol vibrant palette for multi-category diagrams:

Component diagram — categorical preset

Figure: PlantUML component diagram with Tol categorical preset

@startuml
skinparam svgDimensionStyle false
skinparam shadowing false
scale 2
skinparam ranksep 30
skinparam nodesep 35
skinparam defaultFontName "IBM Plex Sans"
skinparam defaultFontSize 12
skinparam backgroundColor #ffffff
skinparam ArrowColor #1a1a1a
' Tol vibrant palette (screen default)
skinparam component {
  BackgroundColor<<req>> #0077BB
  BackgroundColor<<arch>> #33BBEE
  BackgroundColor<<test>> #009988
  BackgroundColor<<ext>> #EE7733
  FontColor<<req>> #ffffff
  FontColor<<arch>> #1a1a1a
  FontColor<<test>> #ffffff
  FontColor<<ext>> #ffffff
  BorderColor #1a1a1a
}
@enduml

Slide decks

Braking System Review Sprint 12 — 2026-03-23 INTERNAL 1

Figure: Title slide — light theme

Sensor pipeline 2

Figure: Content slide — light theme

Braking System Review Sprint 12 — 2026-03-23 INTERNAL 1

Figure: Title slide — dark theme

Sensor pipeline 2

Figure: Content slide — dark theme

Slide decks use 16:9 aspect ratio (1920 × 1080 logical units), optimized for projectors, wide monitors, and video conferencing. Both light and dark themes are supported.

PropertyValue
Aspect ratio16:9 (1920 × 1080)
Margins60px top/bottom, 80px sides
Title slide font48pt, SemiBold
Slide heading (H2)36pt, SemiBold
Body text24pt, Regular
Code blocks20pt, Mono
FooterSlide number (right), 14pt

SVG sizing for slides (16:9)

TypeRatioviewBox (w × h)Use
Full slide16:91600 × 900Full bleed diagram
Content area16:91400 × 780With title and margins
Half slide9:10700 × 780Diagram + text side by side
Quarter16:9700 × 390Small inline diagram

Themes

MarkSpec supports two themes for HTML books, web output, and slide decks. PDF documents always use the light theme for print consistency.

Light theme (default)

TokenHex
background#ffffff
text#1a1a1a
secondary#6b6b6b
accent#0072B2
bg-code#f5f5f5
border#d4d4d4

Dark theme

TokenHex
background#1a1a1a
text#e4e4e4
secondary#999999
accent#56b4e9
bg-code#2a2a2a
border#404040

Light theme Sensor noise filtering The driver shall debounce inputs. See SRS_BRK_0107 for details. fun debounce(raw: Int): Int { // filter noise Figure: Debounce pipeline

Figure: Light theme

Dark theme Sensor noise filtering The driver shall debounce inputs. See SRS_BRK_0107 for details. fun debounce(raw: Int): Int { // filter noise Figure: Debounce pipeline

Figure: Dark theme

The dark accent color (#56b4e9) is the Tol bright blue — colorblind-safe and high contrast against dark backgrounds. Theme preference is detected from the user’s system setting (prefers-color-scheme) for HTML/web output, or set explicitly in project.yaml.

CSS custom properties

:root {
  --ms-bg: #ffffff;
  --ms-text: #1a1a1a;
  --ms-secondary: #6b6b6b;
  --ms-muted: #999999;
  --ms-accent: #0072b2;
  --ms-accent-dark: #005580;
  --ms-bg-code: #f5f5f5;
  --ms-bg-alert: #f0f4f8;
  --ms-border: #d4d4d4;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ms-bg: #1a1a1a;
    --ms-text: #e4e4e4;
    --ms-secondary: #999999;
    --ms-muted: #666666;
    --ms-accent: #56b4e9;
    --ms-accent-dark: #3a8fbf;
    --ms-bg-code: #2a2a2a;
    --ms-bg-alert: #242424;
    --ms-border: #404040;
  }
}

Specimen

Braking System — Software Requirements 3 Sensor noise filtering The braking system shall filter sensor noise to prevent spurious activation. See SYS_BRK_0042 for details. - [SRS_BRK_0107] Sensor input debouncing The sensor driver shall debounce raw inputs to eliminate electrical noise before processing. Id: SRS_01HGW2Q8MNP3 Satisfies: SYS_BRK_0042 Labels: ASIL-B Table: Sensor thresholds Sensor Min Max Unit Pressure 0 1023 raw Temperature -40 150 °C fun debounce(raw: Int): Int { // filter noise below threshold return filtered Warning— debounce < 2ms may cause false activations. Validated by SIT_BRK_0042. Sensor Filter raw Figure: Debounce pipeline Debounce values from bench testing[^1]. MarkSpec v0.0.3 2026-03-23

Figure: Specimen page — all typographic elements on a single page

References