markdown2pdf 1.4.0

Create PDF with Markdown files (a md to pdf transpiler)
Documentation
# Config fidelity audit

Issues of one class: a config field is accepted by the schema and
resolves into `ResolvedStyle` (visible in `--print-effective-config`)
but the renderer never applies it. Internal scratchpad — not for commit.

## Method (every finding is tested, not assumed)

Renders are byte-deterministic, so: render a doc with the field at an
extreme value, render the baseline, `cmp` the PDFs. **Byte-identical =
the field had zero effect.** Each "IGNORED" below was also confirmed to
*resolve* correctly in `--print-effective-config`, so it is genuinely
"resolved but not applied", not a rejected config. "HONORED" entries
were confirmed by visual inspection of the rendered PDF.

## IGNORED — resolved but never applied

### Block style (`ResolvedBlock`: paragraph / headings / code_block / blockquote)

| Field | Scope tested | Status |
|---|---|---|
| `font_weight` | paragraph, code_block, blockquote | FIXED for paragraph + code_block (variant-usage walk now flags the mono bold/italic face from `[code_block]`); blockquote still routes text through `render_paragraph` |
| `font_style` (italic) | paragraph | FIXED for paragraph (`base_flags_from_block` helper) |
| `underline` | paragraph, headings, code_block | FIXED — `base_flags_from_block` applied in render_paragraph/heading/code_block |
| `strikethrough` | paragraph, headings, blockquote | FIXED for paragraph + headings |
| `letter_spacing_pt` | paragraph, headings, code_block, blockquote, lists, tables | FIXED — `Tc` (`SetCharacterSpacing`) on emit + `measure_text` adds `N * ls` so measurement matches; one `letter_spacing_pt` field scoped by `begin_block`/`end_block` (and `render_list`/`render_table`) |
| `indent_pt` | paragraph | FIXED — `[paragraph].indent_pt` now applies as a first-line indent (consumed once by `write_wrapped_runs`) |
| `text_align` | code_block, blockquote | FIXED for code_block (`render_code_block` now sets `current_text_align`); blockquote still routes text through `render_paragraph` |

Note: blockquote and admonition body text is now FIXED — a
`text_style_override` makes `render_paragraph` inherit the container's
typography (font / colour / weight / slant / size / alignment /
decorations) while keeping paragraph's structural fields.

### Page (`ResolvedPage`)

| Field | Result | Root cause |
|---|---|---|
| `columns` | multi-column layout never happens | `columns` not referenced in `src/lib/render/` |
| `column_gap_mm` | no effect | not referenced |

### Table (`ResolvedTable`)

| Field | Result | Root cause |
|---|---|---|
| `cell_padding` | FIXED — `draw_row` / `measure_row_height` now read `style.table.cell_padding` instead of hardcoded 4/3pt ||
| `alternating_row_background` | FIXED — `render_table` now tints alternate data rows via `draw_filled_rect` ||

### Inline (`ResolvedInline`: code_inline / mark)

| Field | Result |
|---|---|
| `code_inline.font_family` | FIXED — new `RunFlags.inline_code` bit routes inline-code / `<kbd>` runs to a dedicated `external_code_inline` family loaded from `[code_inline].font_family`; falls back to `external_code` (and then builtin Courier) when unset or equal to `[code_block].font_family` |
| `code_inline.text_color` | FIXED — inline code paints with `[code_inline]` colour (code blocks keep theirs via the `in_code_block` guard) |
| `code_inline.background_color` | FIXED — inline code paints on a `[code_inline]` background box (HighlightBox now carries its own fill) |
| `code_inline.underline` / `strikethrough` | IGNORED |
| `code_inline.padding` | FIXED — `padding.left` / `.right` apply to the first / last word of each contiguous inline-code span (threaded through wrap + emit via `TextItem::Offset` so selection stays in one BT); `padding.top` / `.bottom` extend the inline-code background box vertically |
| `mark.text_color` | FIXED — highlighted runs now paint with `style.mark` colour |
| `mark.underline` / `strikethrough` | IGNORED |

### List / image / admonition

| Field | Result |
|---|---|
| `list.common.indent_per_level_pt` | FIXED — a nested list now steps in by `indent_per_level_pt` from its parent list's bullet column (was an implicit `bullet_width + bullet_gap`) |
| `admonition.text_color` | FIXED — callout body paragraphs inherit the admonition text style via `text_style_override` |

Correction: `image.max_width_pct` was first flagged IGNORED — that was a
false positive. It is a *cap*; the initial test image was smaller than
the cap so it correctly produced no change. Re-tested with a wide image:
HONORED.

## HONORED — verified working

- paragraph: `background_color`, `border`, per-side `border.left`,
  `padding`, `small_caps`, `line_height`, `margin_before/after_pt`,
  `text_align` (left/right/justify)
- headings: `font_weight`, `font_style`, `font_size_pt`, `text_align`,
  `text_color`, `margin_*`
- `horizontal_rule`: `style` (dashed verified), `width_pct`
- `table`: `row_gap_pt`
- `mark`: `background_color`
- `link`: `text_color`, `underline` (fixed this branch)
- `list`: `bullet_gap_pt` (added this branch), `item_spacing_tight_pt`
- `header` / `footer`: `center` text, `gap_pt` — verified rendered
- `toc`: `enabled` — verified TOC page rendered
- `title_page`: `title` — verified title page rendered
- `admonition`: `accent_color`, `background_color`, `padding`
- `image`: `align`
- `math`: `scale`, `align`, `color`

## PREVIOUSLY NOT-AUDITED — now audited

All audited empirically (extreme value + byte-compare; PDF `/Info`
inspected for metadata). HONORED: `toc.max_depth`; `header`/`footer`
`show_on_first_page` + per-side `style`; `title_page`
`subtitle`/`author`/`date`; admonition per-kind `label` + block
fields; `metadata` title/author/subject/keywords (UTF-16BE in
`/Info`); `code_block` and `heading` `background_color`/`border`/
`padding`.

Two bugs were found and FIXED:

- `[image.caption]` — caption rendered with hardcoded style; the
  `caption` block is now lowered into `ResolvedImage` and `render_image`
  uses it (font/size/colour/align/gap).
- `title_page.cover_image_path` — resolved but never drawn; the title
  page now renders the cover image centered above the title stack
  (decode logic shared with `render_image` via `decode_image_file`).

## Suggested priority

1. **High**`font_weight` / `font_style` ignored on paragraph (can't
   bold/italicise body text via config); `page.columns` (a documented
   feature that does nothing); `table.cell_padding`.
2. **Medium**`letter_spacing_pt`, `indent_pt`, block-level
   `underline`/`strikethrough`, `code_inline` colour/font,
   `mark.text_color`, `alternating_row_background`.
3. **Low / by-design?**`text_align` on code_block & blockquote.

## Likely common fix

Most block-level misses share one cause: `render_paragraph` /
`render_code_block` / `render_blockquote` build runs with
`RunFlags::default()` and never fold in the block's resolved style,
unlike `render_heading`. A shared "block base flags from style" helper
(weight, style, underline, strikethrough, letter spacing) applied in
`write_wrapped_runs` would close most of the list at once.