markdown2pdf 1.6.0

Create PDF with Markdown files (a md to pdf transpiler)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
# Configuration guide

`markdown2pdf` ships with sensible defaults, six bundled theme presets, and a TOML configuration surface that controls every visual choice the renderer makes: fonts, colors, spacing, alignment, page setup, headers / footers, table of contents, title page, and per-block typography for every markdown construct.

You can ship a useful PDF with zero config. You can also write 10 lines of TOML and produce a document that looks like GitHub, a paper, or a corporate report, without touching code.

## Quick start

Pick a theme and render:

```sh
markdown2pdf -p input.md --theme github -o out.pdf
```

Bundled themes:

| Theme       | Feel                                                     |
|-------------|----------------------------------------------------------|
| `default`   | Clean readable baseline (used when no theme is set)      |
| `github`    | GitHub README rendering                                  |
| `academic`  | Justified body, conservative serif feel                  |
| `minimal`   | Stripped-down typography, lots of breathing room         |
| `compact`   | Tight spacing for high-density reference docs            |
| `modern`    | Sharper hierarchy with a contemporary heading scale      |

Inspect the effective configuration for any combination:

```sh
markdown2pdf --theme academic --print-effective-config
```

That prints the full resolved style as TOML. Copy any section into your own config and tweak.

## Your own config file

Copy [`docs/config.toml`](config.toml) from the repo to wherever you want and pass it with `-c`:

```sh
markdown2pdf -p input.md -c my-config.toml -o out.pdf
```

Every field is optional. `theme = "github"` (or any other preset) inherits a known-good baseline; you override only what you care about.

To make a config the default without passing `-c` every time, place it where the binary discovers it automatically: `markdown2pdf.toml` in the project directory, or `markdown2pdf/config.toml` under your user config directory (`~/.config/markdown2pdf/config.toml` on macOS/Linux). The `MARKDOWN2PDF_CONFIG` environment variable also points at one. See [cli.md](cli.md) for the full lookup order.

Any field below can also be overridden per-run from the command line (winning over the config file and `--theme`). See [cli.md](cli.md#config-overrides) for `--title` / `--font-size` / `--margin` / `-V key=value` and the dotted-key syntax.

## How configuration resolves

The renderer composes the final style by cascading values in this order, from lowest priority to highest:

1. **Bundled `default` theme**: the lowest-priority baseline, always present.
2. **`inherits = "..."` chain**: each preset can extend another (the `github` preset, for example, inherits from `default`). Cycles are detected and rejected with `InheritsCycle`.
3. **`[defaults]` block** in any config: fields in `[defaults]` cascade into every block that doesn't override them. Useful when you want one body font across the whole doc.
4. **Per-block overrides**: `[paragraph]`, `[headings.h1]`, etc. take precedence over `[defaults]`.
5. **CLI flag override**: `--theme NAME` overrides any `theme = "..."` value in the config file itself.

## Pages

```toml
[page]
size = "A4"              # A4 | Letter | Legal | A3 | A5
                         # or { width_mm = 100.0, height_mm = 150.0 }
orientation = "portrait" # portrait | landscape
margins = { top = 22.6, right = 22.6, bottom = 22.6, left = 22.6 }  # mm
columns = 1              # 1 (multi-column is a follow-up)
column_gap_mm = 6.0
```

A `Mm` margin like `22.6` is millimeters; the renderer converts to PDF points internally.

## Defaults cascade

Every per-block section inherits any unset field from `[defaults]`:

```toml
[defaults]
font_family = "Helvetica"
font_size_pt = 11.0
font_weight = "normal"  # normal | bold | numeric 100..=900
font_style = "normal"   # normal | italic
text_color = "#1B1F23"
line_height = 1.5       # multiplier of font_size_pt
text_align = "left"     # left | center | right | justify
padding = 0.0
margin_before_pt = 0.0
margin_after_pt = 0.0
indent_pt = 0.0
fallback_fonts = ["Noto Sans CJK SC", "Noto Sans Arabic", "Symbola"]
```

### Body font

`font_family` in `[defaults]` selects the font that is loaded and embedded: a built-in alias (`Helvetica`, `Times`, `Courier`), a system font name, or a path to a `.ttf` / `.otf` file. A built-in alias uses a PDF base-14 font (no embedding; non-ASCII glyphs transliterate to ASCII). Any other name is resolved against the system font directories and embedded, which is required for Unicode glyphs such as `•`.

The `--default-font` CLI flag overrides this; when it is omitted the config's `font_family` is used.

### Fallback fonts

`fallback_fonts` is an ordered list of font names consulted when the primary body / code font lacks a glyph for a codepoint. Mixed-script documents (Latin + CJK, Arabic, Hebrew, math symbols, emoji) render each codepoint in the first configured font that covers it; characters unmatched by every font degrade to `?` rather than panicking.

Names resolve the same way as `font_family`: built-in aliases, system font names, or paths to `.ttf` / `.otf` files. The field is only read from `[defaults]`; per-block tables ignore it.

Programmatic callers can set the same list on `FontConfig`:

```rust
let cfg = FontConfig::new()
    .with_default_font("Helvetica")
    .with_fallback_fonts(["Noto Sans CJK SC", "Symbola"]);
```

Colors accept hex strings (`"#RRGGBB"`, `"#RGB"`), structs (`{ r = 255, g = 0, b = 0 }`), or arrays (`[255, 0, 0]`).

Padding and margin can be a scalar (applies to all sides), a pair (`[vertical, horizontal]`), a quad (`[top, right, bottom, left]`), or a struct (`{ top, right, bottom, left }`).

## Block types

Every block type accepts the same superset of fields plus a few construct-specific ones. Set only what you want to override.

### Paragraph

```toml
[paragraph]
text_align = "justify"
margin_after_pt = 4.0
small_caps = false
```

`text_align = "justify"` distributes inter-word slack on non-last lines via the PDF `Tw` (word-spacing) operator. The last line of a paragraph always stays left-aligned (typographic convention). When slack exceeds 30% of the column width, the line silently falls back to left-alignment to avoid grotesque stretches.

`small_caps = true` renders originally-lowercase letters at 78% size in uppercase (faux small caps); digits, punctuation, and originally-uppercase letters stay full-size.

### Headings 1–6

Each heading level has its own section. Drop any subsection to inherit from `[defaults]` (and the active theme).

```toml
[headings.h1]
font_size_pt = 22.0
font_weight = "bold"
text_align = "left"
margin_before_pt = 8.0
margin_after_pt = 4.0
small_caps = false

[headings.h2]
font_size_pt = 17.0
font_weight = "bold"
```

Headings automatically:
- Register as PDF bookmarks (the viewer's outline panel)
- Generate a GitHub-style slug anchor for `[text](#slug)` links

### Code blocks (fenced ` ``` `)

```toml
[code_block]
font_family = "Courier"
background_color = "#F6F8FA"
text_color = "#1F2328"
padding = { top = 8.0, right = 10.0, bottom = 8.0, left = 10.0 }
margin_before_pt = 6.0
margin_after_pt = 6.0

[code_block.border]
all = { width_pt = 0.5, color = "#E1E4E8", style = "solid" }
```

`border` accepts per-side (`top`, `right`, `bottom`, `left`) or `all` for uniform borders. Styles: `solid`, `dashed`, `dotted`.

### Inline code (`` ` ``)

```toml
[code_inline]
font_family = "Courier"
background_color = "#EFF1F3"
text_color = "#1F2328"
```

### Block quotes (`>`)

```toml
[blockquote]
font_style = "italic"
text_color = "#57606A"
indent_pt = 17.0
padding = { top = 2.0, right = 6.0, bottom = 2.0, left = 8.0 }
margin_before_pt = 4.0
margin_after_pt = 4.0

[blockquote.border]
left = { width_pt = 3.0, color = "#D0D7DE", style = "solid" }
```

### Lists

Three flavors: `unordered`, `ordered`, `task`. All inherit from `[list.common]`.

```toml
[list.common]
margin_after_pt = 0.5
indent_per_level_pt = 17.0
item_spacing_tight_pt = 0.5  # CommonMark "tight" list (no blank lines)
item_spacing_loose_pt = 2.0  # CommonMark "loose" list (any blank line)
bullet_gap_pt = 5.67         # horizontal gap between the bullet/number and the item text

[list.unordered]
bullet = "•"   # any glyph

[list.ordered]
bullet = "1."  # numeric format hint: "1." or "1)"

[list.task]
# Renderer emits [x] / [ ] for task items automatically.
```

### Tables (GFM)

```toml
[table]
row_gap_pt = 2.0
cell_padding = { top = 3.0, right = 4.0, bottom = 3.0, left = 4.0 }
margin_before_pt = 4.0
margin_after_pt = 4.0
# alternating_row_background = "#FAFBFC"   # uncomment for zebra stripes

[table.header]
font_weight = "bold"

[table.cell]
# Inherits from defaults; override per-cell font / color here.

[table.border.all]
width_pt = 0.5
color = "#D0D7DE"
style = "solid"
```

Column alignment (`:---`, `:---:`, `---:` in markdown) is honored. Header rows repeat at the top of each page the table spans.

### Images

```toml
[image]
max_width_pct = 100.0  # 1..=100; cap as a fraction of content width
align = "center"       # left | center | right
margin_before_pt = 4.0
margin_after_pt = 4.0
```

Images support:
- **Local files**: PNG and JPEG via the bundled `image` crate.
- **URL fetching**: `![alt](https://...)` works when compiled with `--features fetch`. Uses rustls (pure-Rust TLS). The fetch has a 5-second timeout and 10 MB cap; failures degrade to italic alt text.
- **SVG**: vector images (`.svg`) rasterize via `resvg` when compiled with `--features svg`. Useful for README hero images served by GitHub.
- **Captions**: `![alt](url "Caption text")` renders the title as a small italic caption beneath the image, wrap-constrained to the image's width when the image is narrower than the column.

### Links

```toml
[link]
text_color = "#0969DA"
underline = false
```

Links support tooltips via the markdown title attribute:

```markdown
See [the spec](https://example.com/spec "Hover tooltip here").
```

The tooltip lands in the PDF's `/Contents` entry on the link annotation; supported PDF viewers display it on hover.

Inline HTML anchors are recognised too, which is handy when content comes from HTML-converted sources:

```markdown
Visit <a href="https://example.com" title="Hover tooltip here">the site</a>.
```

The `href` becomes the link target and the optional `title` flows through the same tooltip path. Hrefs may use single or double quotes and the tag name / attributes are case-insensitive (`<A HREF="…">…</A>` works). An `<a>` without `href`, a self-closing `<a … />`, an unclosed opener, or a stray `</a>` degrades to literal markup rather than producing a broken annotation.

Internal cross-references resolve automatically:

```markdown
See [the conclusion](#conclusion) for context.

# Conclusion
```

`#conclusion` matches the GitHub-style slug of the heading text. If two headings have the same text, the second gets `-2`, the third `-3`, etc. Unresolved anchors log a warning and emit no annotation.

WikiLinks resolve through the same anchor machinery:

```markdown
See [[Conclusion]] or [[conclusion|the wrap-up]].

# Conclusion
```

`[[Target]]` links to the heading whose slug matches `Target`; `[[Target|Label]]` shows `Label` instead. There is no `[wikilink]` config block: a WikiLink renders with the `[link]` style above and resolves like any `#slug`, so an unmatched target logs a warning and falls back to styled text rather than breaking the export.

### Footnotes (`[^id]` and `^[…]`)

Two syntaxes are supported. The GFM reference form defines the note separately:

```markdown
Tea rewards patience[^steep].

[^steep]: Two to three minutes is a sensible start.
```

The Pandoc inline form writes the note in place, with no separate definition and no label to invent:

```markdown
Water just off the boil^[around 90–95 °C for black teas] is plenty.
```

Both share one numbering sequence, assigned in first-reference order as they appear in the document, so inline and reference footnotes interleave correctly. Every marker renders as a superscript number linking to its entry, and all notes are collected into a single **Footnotes** section appended at the end of the document. A reference definition may span multiple lines (continuation lines indented at least four spaces); a defined-but-unreferenced `[^id]:` is still listed so it never silently vanishes.

There is no `[footnote]` config block. Markers use the body / `[link]` style above, and the section heading uses Heading 2 typography. Malformed input degrades to literal text rather than breaking the export: an unbalanced `^[`, an empty `^[]`, or a `[^id]` with no matching definition all render as plain characters.

### Highlight (`==text==`)

```toml
[mark]
background_color = "#FFF59D"
```

`==text==` paints `background_color` behind the run (a soft yellow by default). It nests with other inline styles, so `==**bold**==` is both bold and highlighted:

```markdown
Some ==important== text, and a ==**bold mark**==.
```

`==` is only a highlight mid-content: a line that is exactly `===` (or `---`) still underlines the paragraph above it as a Setext heading, and an unterminated `==` renders as literal text.

### Admonitions (`!!! kind` / `> [!KIND]`)

```toml
[admonition]
padding = { top = 8.0, right = 12.0, bottom = 8.0, left = 14.0 }
margin_before_pt = 4.0
margin_after_pt = 4.0

[admonition.note]
accent_color = "#448AFF"
background_color = "#E7F2FF"
```

Two authoring syntaxes are recognised and both produce the same styled box:

```markdown
!!! warning "Watch out"
    A MkDocs-style admonition. The body is indented at least four
    spaces; blank lines inside the body are preserved.

> [!NOTE]
> A GitHub-flavoured alert. The marker may stand alone on the
> first line, or carry inline content after a space.
```

**First-class kinds** are `note`, `info`, `tip`, `warning`, and `danger`. The obvious aliases collapse to those so a document authored against either ecosystem renders the same way: `caution` and `error` map to `danger`, `important` maps to `info`, `warn` and `attention` map to `warning`, `hint` maps to `tip`.

**Unknown kinds** (`!!! bug "Repro"`, `> [!QUESTION]`) fall back to a `generic` palette and surface the raw label uppercased as the header so the author's intent is never erased:

```markdown
!!! bug "Repro steps"
    Renders in the generic grey box with "Repro steps" as the
    header.

!!! quirk
    No title — the header reads "QUIRK" verbatim.
```

The `[admonition]` block holds shared shape (padding, margins, font defaults). Per-kind sub-blocks layer colour and label overrides on top:

```toml
[admonition.danger]
accent_color = "#FF1744"
background_color = "#FFEBEE"
label = "STOP"        # overrides the default "DANGER" header
```

`accent_color` drives both the left border and the per-kind icon (`note` ●, `info` ⓘ, `tip` 💡, `warning` ⚠ +!, `danger` ⊗, `generic` ≡); icons are drawn as vector glyphs so they don't depend on any font's coverage. Each bundled theme ships its own palette: `github` matches GitHub's alert colours, `academic` / `minimal` stay restrained, `modern` leans vibrant.

A custom `"…"` title on the MkDocs form replaces the default header; inline markdown inside the title (emphasis, code) is preserved. Admonition bodies are block sequences (lists, fenced code, tables, even nested admonitions all work), and inline `<a href="…">` anchors inside the body still become clickable links.

### Math (`$…$`, `$$…$$`)

`$…$` is inline math and `$$…$$` is a centered display block:

```markdown
The identity $a^2 + b^2 = c^2$ holds, and

$$\int_0^1 x\,dx$$
```

The content between the delimiters is opaque TeX: no markdown parsing or escape decoding happens inside. A built-in TeX engine typesets it (TeXbook Appendix-G layout over STIX Two Math's OpenType MATH metrics): real fraction bars, radicals with indices, sub/ superscript stacks, big operators with limits, delimiters that grow to their content, matrices, `cases`, accents, Greek, and `\mathbb`/`\mathbf`/`\mathcal`/… alphabets. Inline math sits on the text baseline and wraps as one indivisible box; display math is its own block. A command the engine doesn't know degrades to literal text rather than failing.

The glyphs are drawn as filled **vector outlines**, not text: no font is embedded and the equation is not selectable, so it behaves like a figure in every PDF viewer (this matches how LaTeX-, MathJax-, and KaTeX-to-PDF pipelines treat math; selectable math would require tagged-PDF `/ActualText`).

Delimiter handling follows Pandoc, so prose with dollar signs is not mistaken for math:

- The opening `$` must be followed by a non-space character, and the closing `$` must be preceded by one and not directly followed by a digit: `$5 and $6` stays literal text.
- `\$` is a literal dollar (`\$5.00` renders as `$5.00`).
- Inline math is single-line; display math may span lines but a blank line ends it.
- An unterminated `$` or `$$` degrades to literal text rather than breaking the export.

Display blocks are configurable via `[math]`:

```toml
[math]
align = "center"   # center (default) | left | right
scale = 1.08        # display size as a multiple of the body size
color = "#1A1A1A"   # math ink (defaults to the paragraph text color)
margin_before_pt = 6
margin_after_pt = 6
```

`scale` applies to display blocks; inline math always tracks the surrounding text size. With no `[math]` table, display math is centered at `1.08×` the body size in the paragraph color, with the paragraph's block spacing.

### Horizontal rule (`---`)

```toml
[horizontal_rule]
color = "#D0D7DE"
thickness_pt = 0.5
style = "solid"   # solid | dashed | dotted
width_pct = 100.0
margin_before_pt = 6.0
margin_after_pt = 6.0
```

## Document features

### Metadata (PDF Info dict)

```toml
[metadata]
title = "My Document"
author = "Author Name"
subject = "Subject line"
keywords = ["one", "two"]
creator = "markdown2pdf"
language = "en-US"
```

`language` is a BCP-47 tag emitted as the PDF Catalog `/Lang` entry, used by screen readers to select a pronunciation dictionary. It is omitted entirely when unset (no faked default).

Non-ASCII values are encoded as UTF-16BE with a FEFF BOM (PDF spec compliant).

### Headers and footers

Three slots per row (left / center / right) with template variables. Available variables: `{page}`, `{total_pages}`, `{title}`, `{date}`, `{author}`.

```toml
[header]
left = "{title}"
right = "{page} / {total_pages}"
show_on_first_page = false

[footer]
center = "Page {page}"
```

Page numbers substitute correctly because the renderer collects raw pages first, then assembles each `PdfPage` once the total page count is known.

### Title page

```toml
[title_page]
title = "Document Title"
subtitle = "Optional subtitle"
author = "Author Name"
date = "2026-05-15"
# cover_image_path = "cover.png"   # deferred follow-up
```

When `title` is set, the renderer prepends a dedicated first page with the title (2.4× the base font size, bold), subtitle (1.4×), author (1.1×), and date (1.0×), all centered both horizontally and vertically. Headers / footers are suppressed on title pages.

### Auto-generated table of contents

```toml
[toc]
enabled = true
title = "Contents"
max_depth = 3
```

When `enabled = true`, every heading at or above `max_depth` becomes a TOC entry between the title page (if any) and the body. Each entry is a clickable `GoTo` link to its target heading. The renderer runs a convergence loop on page count (bounded at 3 iterations) so the displayed page numbers match the final post-TOC offsets.

## Security — confining image reads (`[security]`)

`[security]` is operator-only configuration: it governs what the *renderer* is allowed to do on the host it runs on, not how a document looks. It has no `### ` peers among the document-authorable features above: it belongs alongside `Hyphenation` / `Page breaks` / `Inline HTML` below, not with metadata, headers/footers, the title page, or the TOC.

```toml
[security]
image_root = "/srv/uploads"
allow_absolute_image_paths = true
allow_remote_images = true
```

**When you need this**: markdown can reference a local image by any path (`![](/etc/ssl/certs/logo.png)`, `![](../../.env)`), and by default the renderer reads it straight off disk and embeds the bytes in the PDF. That is fine for a person converting their own document, but if you render markdown **you did not author** (a server accepting user-submitted documents, a pipeline over untrusted input), a crafted document can pull any server-local image the process can read into the output the attacker receives. If that's your situation, set `image_root` to a directory the document is allowed to pull images from, typically the same directory the markdown itself came from, or a dedicated uploads folder.

- `image_root` (default: unset). When set, every local image path is resolved against this directory and confined to it. A relative path resolves inside it; any path (relative or absolute) that escapes it (including via a symlink planted inside the root) is refused. Unset preserves the historical behavior: relative paths resolve against the process's working directory and absolute paths are read as given.
- `allow_absolute_image_paths` (default: `true`). Set `false` to reject any absolute local image path outright, independent of `image_root`. This check runs *before* root confinement, so an absolute path is refused even if it points at a file genuinely inside `image_root`; set both knobs expecting them to compose, not `image_root` alone to be the deciding factor.
- `allow_remote_images` (default: `true`). Set `false` to reject `http`/`https` image references. Independent of whether the crate was compiled with the `fetch` feature: without it, remote images already fail.

A refused image degrades exactly like a missing or undecodable one: the renderer logs a warning and falls back to the italic `[image: ALT]` placeholder rather than failing the whole render. A path that doesn't exist (a typo, a moved file) is logged separately from an actual policy refusal, so you're not sent hunting through security config for what's really a bad path.

These three all default to the permissive, pre-existing behavior. A document can never set them itself (frontmatter is metadata-only), so they only ever come from your own config file, `-c` flag, or `ConfigSource::Embedded`.

**Known limitations**: this is a containment check, not a sandbox. Hardlinks inside `image_root` aren't detected (though creating one already requires write access inside the root, a stronger primitive than the image read it would buy); there is a TOCTOU window between the path being resolved and the file actually being read; and, as above, `allow_absolute_image_paths = false` is checked before root confinement.

## Hyphenation

The `split_long_words` pre-pass consults a Knuth-Liang English dictionary (`hyphenation` crate) to find break points in any word that exceeds the column width. When a dictionary break fits in the remaining space, the renderer emits `prefix + "-"` and continues with the suffix on the next chunk. Words the dictionary doesn't know (long URLs, identifiers, repeated-char tokens) fall back to UTF-8 char boundaries.

## Page breaks

Force a page break with a standalone HTML comment:

```markdown
First page content.

<!-- pagebreak -->

Second page content.
```

The marker is case-insensitive and whitespace-tolerant.

## Inline HTML

markdown2pdf understands a small, deliberately conservative subset of inline HTML. Anything outside the subset passes through as literal text: no scripting, no arbitrary HTML execution.

**Inline styling tags** apply to the wrapped text:

| Tag | Effect |
| --- | ------ |
| `<sup>` | superscript |
| `<sub>` | subscript |
| `<u>` | underline |
| `<s>`, `<del>`, `<strike>` | strikethrough |
| `<small>` | smaller text |
| `<kbd>` | monospace (keyboard input) |
| `<br>` / `<br/>` | soft line break |

**Anchors**: `<a href="…" title="…">…</a>` becomes a clickable PDF link annotation; see the [Links](#links) section above.

**Structural block wrappers** drop out so their children render as normal paragraphs: `<div>`, `<section>`, `<figure>`, `<figcaption>`, `<p>`, and `<center>` (with or without attributes). This is what lets documents converted from HTML keep working without showing literal `<div>` markup.

```markdown
<section>

Inner **markdown** still renders, including [links](https://example.com).

</section>
```

**Comments** (`<!-- … -->`) are invisible per CommonMark, and the special marker `<!-- pagebreak -->` forces a page break (see [Page breaks](#page-breaks)).

Everything else (`<span>`, `<aside>`, custom elements, raw `<script>` / `<style>` / `<pre>` / `<textarea>` blocks) renders verbatim as a monospace HTML block, so the source stays visible rather than being silently dropped or interpreted.

## Loading methods

Three ways to feed the renderer a config:

### 1. CLI flag (`-c`)

```sh
markdown2pdf -p input.md -c path/to/config.toml -o out.pdf
```

### 2. CLI theme override (`--theme`)

```sh
markdown2pdf -p input.md --theme github -o out.pdf
```

The `--theme NAME` flag overrides any `theme = "..."` value in the config file itself, useful for quickly switching presets without editing the config.

### 3. Library API

The Rust API takes a `ConfigSource`:

```rust
use markdown2pdf::config::ConfigSource;
use markdown2pdf::parse_into_bytes;

// Default style.
let bytes = parse_into_bytes(md, ConfigSource::Default, None)?;

// Load from a file path.
let bytes = parse_into_bytes(md, ConfigSource::File("config.toml"), None)?;

// Embed at compile time.
let cfg = include_str!("../my-config.toml");
let bytes = parse_into_bytes(md, ConfigSource::Embedded(cfg), None)?;
```

## Error messages

The schema uses `#[serde(deny_unknown_fields)]`, so typos in field names are caught at parse time. The error message includes the file path, line, column, the unknown field name, and a typo suggestion when a close match exists:

```text
error in config.toml at line 5, column 1: unknown field `text_colr`,
expected one of `font_family`, `font_size_pt`, ..., `text_color`, ...
  hint: did you mean `text_color`?
```

Unknown theme names get the same treatment:

```text
unknown theme preset `githb`
  did you mean `github`?
```

## See also

- [`docs/config.toml`](config.toml): the annotated reference config, drop in any of its sections to start tweaking.