markdown2pdf 1.5.0

Create PDF with Markdown files (a md to pdf transpiler)
Documentation
_Mirror of open issues at github.com/theiskaa/markdown2pdf, ordered easiest → hardest so we work top to bottom._

_Already fixed locally (pending PR / close): pre-existing widow/orphan work._

---

## Admonition rendering and layout gaps (GH #107)

**Built-in labels and auto-section headings render with substituted glyphs.** The renderer's auto-generated kind labels (NOTE, INFO, TIP, WARNING, DANGER, ABSTRACT, EXAMPLE, QUOTE) and the auto "Footnotes" section heading are rendering with many letters replaced by box glyphs (`□`). In `admonition_all_kinds.pdf`: `NOTE` shows as `□□TE`, `INFO` as `□□□□`, `TIP` as `T□□`, `WARNING` as `□A□□□□G`, `DANGER` as `□A□GE□`, `ABSTRACT` as `A□□T□ACT`, `EXAMPLE` as `E□A□□□E`, `QUOTE` as `□□□TE`. All five GitHub-style alerts (`> [!NOTE]` etc.) show the same garbling. The default theme's `theme_probe.md` renders the auto "Footnotes" heading as `□ootnotes`. User-supplied custom admonition titles like `!!! note "Plain note"` or the `Outer`/`Inner` titles in `admonition_nested.md` render correctly. The bug is specifically in the built-in label / auto-heading codepath. The pattern (some letters survive, others don't) suggests glyph IDs aren't being mapped correctly for that font. Worth bisecting the commits between the May 20 release and `eda6735`.

**`caution` is relabelled "DANGER"; `important` is collapsed into "INFO".** `!!! caution` renders with the DANGER red style; `!!! important` and `> [!IMPORTANT]` both render as INFO (blue). MkDocs treats caution and danger as distinct kinds with different intent. Either accept caution / important as their own kinds or, at minimum, keep the user-typed label string instead of substituting an aliased one.

**Admonition body splits from its kind label at a page break.** In `admonition_all_kinds.pdf` the GFM `> [!IMPORTANT]` admonition is broken across pages: page 1 ends with the kind-label strip (no body), page 2 begins with the body text inside a tinted box but no kind label above it. The reader sees an unattributed tinted box on page 2. Admonitions should keep at least the label plus the first line of body together (keep-with-next), or repeat the label on continuation.

**Footnote markers inside a blockquote or admonition reset to 1.** In `footnote_in_blocks.md` the blockquote references `[^five]` (the 5th defined footnote) and the admonition references `[^eight]` (the 8th). In the rendered PDF, both markers show as `¹` instead of `⁵` and `⁸`. Inline-paragraph markers (`[^one]`), list-item markers (`[^two]`, `[^three]`, `[^four]`), and table-cell markers (`[^six]`, `[^seven]`) all show their correct numbers. The Footnotes definition list at the bottom of the page is correctly ordered 1-8. So the footnote indexer is fine — the marker renderer for blockquote and admonition contexts is hardcoding `1` instead of looking up the assigned number.

---

## HTML and link rendering (GH #108)

**Inline HTML inconsistency: `<span>` / `<strong>` literal, `<br/>` eaten, `<hr/>` literal, `<div>` block shown literally.** Source paragraph `A paragraph with inline raw HTML: a <span>span element</span> and a <strong>strong</strong> tag and a line<br/>break.` renders with the angle brackets visible for span and strong, the `<br/>` silently dropped (`"a line break"` with a single space), `<hr/>` shown as literal text instead of a horizontal rule, and `<div><p>...</p></div>` rendered inside a gray code-style box with all tags visible. Comments and `<a href="...">` work correctly. README claims raw HTML passthrough; the actual behaviour is split between literal display and silent eating. Pick one — render semantically (`<strong>` → bold) or strip (`<span>X</span>` → X).

**Unresolved wikilinks are styled identically to resolved ones.** `[[Target heading]]` (resolves to a heading in the same doc) and `[[Nowhere]]` / `[[one]]` / `[[two]]` / `[[three]]` (do not) all render as underlined blue links with identical styling. A reader cannot tell which links are dead. Common convention: mark unresolved wikilinks with red color and/or no underline. The escaped `\[\[NotALink\]\]` correctly renders as literal text, so the escape path works.

**Long URLs overflow the right page margin.** The bare URL `https://example.com/very/long/path/segment/that/goes/further/and/further/until/we/run/out/of/room/in/the/column/measure` renders on a single line that extends past the right page margin. Long URLs should break at slashes, hyphenate, or at minimum hard-wrap.

---

## Math engine: macro and layout gaps (GH #105)

**Adjacent `\begin{pmatrix}` blocks inside one `$$...$$` dump raw LaTeX into the PDF.** Individual matrices render fine; the matrix-multiplication pattern bails out of the engine. Fixture:

```
$$
\begin{pmatrix} a & b \\ c & d \end{pmatrix}
\begin{pmatrix} x \\ y \end{pmatrix}
=
\begin{pmatrix} ax + by \\ cx + dy \end{pmatrix}
$$
```

The PDF shows literal text `$$ \begin{pmatrix} a & b \ c & d \end{pmatrix} \begin{pmatrix} x \ y \end{pmatrix}` in body font, then `\begin{pmatrix} ax + by \ cx + dy \end{pmatrix} $$` on the next line. The `\\` row separators got collapsed to `\` in the leaked text, so the tokenizer consumed part of the source before falling back.

**`\lvert` and `\rvert` not recognized.** In `math_in_table.md` the geometric-series cell `$\sum_{n=0}^{\infty} r^n = \frac{1}{1-r}, \, \lvert r \rvert < 1$` renders the macros as literal text — the cell ends with `\lvertr\rvert < 1` visible verbatim. Other commands in the same expression (`\sum`, `\frac`, `\infty`, `\,`) render correctly. Likely just missing from the macro table.

**`\omicron` not recognized.** In `math_long_eq.md` the third equation's denominator `ν + ξ + \omicron + π + ρ + …` renders every other Greek letter as a glyph but `\omicron` appears as literal `\omicron` text. Same fix shape as `\lvert`.

**A single-line polynomial equation overflows the right page margin.** In `math_long_eq.md` the first equation `f(x) = a_0 + a_1 x + ... + a_{14} x^{14}` renders as a single horizontal line extending past the right page edge. Other equations in the same fixture (triple sum + product, and the long fraction) center within the page and don't overflow because the renderer can wrap the surrounding `+` operators. The single-line polynomial has nothing to break on, so it bleeds off the page. Either scale to fit or wrap on operators / spaces.

**4×4 matrix outer parens render as diagonal slashes.** In `math_matrices.md` the 2×2 and 3×3 `\begin{pmatrix}` blocks render with visibly curved parens; the 4×4 pmatrix shows thin diagonal-looking strokes for the outer fence — more like `\` and `/` than parentheses. At 4 rows tall the paren extender either misses a glyph or stretches the wrong way.

---

## Definition lists: continuation indent misclassified (GH #106)

The deflist parser treats the 4-space continuation indent as an indented-code-block marker instead of recursing block parsing inside the definition body.

**Block-level content inside a definition body renders as an indented code block.** Fenced code, GFM tables, blockquotes and nested lists all show up as raw source inside a gray monospace box. The first paragraph of a definition renders correctly; anything after that uses the 4-space deflist continuation indent regresses. Fixture:

```
Term A
:   Definition with a code block inside:

    ```rust
    let x = 42;
    ```

    And a trailing sentence in the same definition.

Term B
:   Definition with a table:

    | x | y |
    | --- | --- |
    | 1 | 2 |
    | 3 | 4 |

Term C
:   Definition with a blockquote:

    > a quote inside the definition body.

Term D
:   Definition with a nested list:

    - bullet one
    - bullet two
    - bullet three
```

The rendered PDF shows the backtick fences, the pipe rows, the `>` marker and the `-` lines all as plain monospace text on gray background.

**Multi-paragraph definitions: the 2nd and 3rd paragraphs render as code blocks.** Same root cause — `deflist_multi_def.md` (Epsilon) and `col_3_deflist.md` (Term four) both show the first body paragraph correctly, then any subsequent paragraph (still 4-space indented) becomes a monospace gray box.

**Multi-term deflist: only the last term is bold.** Source:

```
Alpha
Beta
:   Shared definition for both Alpha and Beta. …
```

Renders Alpha as a plain body line and only Beta as a bold term. Standard deflist syntax says both terms share the definition and both should be bold; the parser is consuming Alpha as a regular paragraph instead of as a term.

**Second `:` block under one term rendered as a literal colon.** Source:

```
Epsilon
:   First definition.

:   A separate second definition for Epsilon.
```

The second one renders as a literal line `:  A separate second definition for Epsilon.` — the leading colon appears in body font, and the second-definition pattern is not recognized.

---

## Unicode characters silently render as `?` without a Unicode font configured (GH #111)

Without `--default-font 'Noto Sans'`, every character outside Helvetica's glyph set renders as `?`: not just CJK / Arabic / Hebrew / Devanagari / Thai (expected) but also basic Latin-1 supplement (`café`, `naïve`, `résumé`, `jalapeño` all render with `?` for the accented letters), em-dashes, en-dashes, smart quotes, mathematical symbols outside `$..$`, arrows, and emoji. The library does emit a warning to stderr (`⚠️ Document contains Unicode characters`), so this is documented behaviour — but the fallback character is hostile and silently destroys text fidelity. Either ship a default fallback font in the binary or auto-detect a system Unicode font when one is installed.

---

## Implement multi-column page layout (page.columns / page.column_gap_mm) — feature (GH #102)

`[page] columns` and `[page] column_gap_mm` are accepted by the schema and resolved into `ResolvedStyle` (visible in `--print-effective-config`), but the renderer ignores them — the body always flows in a single column.

_Note: recent commits `5147739` and `af10022` ("feat(render): flow body content into N side-by-side columns") plus `tests/render/columns.rs` suggest this is implemented; issue likely closeable. Verify and close on github rather than re-implementing._

### Repro

```bash
markdown2pdf -p doc.md -V page.columns=2 -V page.column_gap_mm=8 -o out.pdf
```

`out.pdf` renders identically to the single-column render. `columns` and `column_gap_mm` aren't referenced anywhere under `src/lib/render/`.

### Acceptance

- `-V page.columns=N` (1..=4) flows body content in N equal-width columns separated by `column_gap_mm`.
- A block taller than the remaining column space breaks to the next column; when the last column on a page is full, page break.
- Headers, footers, header furniture, TOC, title page, page numbering stay one-column.
- All existing single-column tests stay byte-identical with `columns=1` (the default).
- New tests under `tests/render/` cover: 2-column wrap, column-break vs page-break interaction, tables / images / code blocks straddling columns.