Skip to content

feature request: render footnotes ([^1]) #126

Description

summary
footnotes are on the README's "Not yet supported" list. unlike task lists (#118), the parser gives us nothing here: swift-markdown attaches only the table, strikethrough, and tasklist cmark-gfm extensions and has no footnote node types at all, so footnote syntax survives parsing as plain literal text. i'd like to contribute rendering support via a preprocessing pass, following the same approach the library already uses for LaTeX — and i'd love a design ack before sending the PR, since there's an architectural choice involved.

minimal markdown input

here is a footnote reference.[^1]

[^1]: and here is the definition.

(the kitchen-sink fixture already exercises this — [^streaming] around lines 229–231.)

current behavior
the reference renders inline as the literal text "here is a footnote reference.[^1]", and the definition renders as a regular paragraph "[^1]: and here is the definition." — readable, as the README promises, but far from the GitHub rendering.

proposed change
a FootnotePreProcessor alongside the existing LaTexPreProcessor (whose own doc comment describes preprocessing as "a less heavy-weight approach than forking commonmark-gfm and swift-markdown" — same reasoning applies here):

  • collect single-line definitions ([^id]: text) and remove them from the source;
  • number references by order of first appearance (GFM semantics — labels like [^note] render as numbers);
  • replace each reference with a specially-marked inline construct that the inline layer renders as a real superscript (smaller font + baseline offset) — the same marker technique the LaTeX preprocessor already uses for inline math;
  • append a footnotes section at the end of the document: thematic break + ordered list with the definition texts (inline formatting inside definitions is preserved, since the emitted text goes through the normal markdown parse);
  • skip anything inside fenced code blocks or inline code spans, so code samples containing [^1] aren't transformed.

GFM parity for the edges: references without a definition stay literal; definitions never referenced are dropped.

v1 scope / open questions

  • display-only: no tap-to-scroll between reference and definition (the block-based architecture makes that a separate, bigger conversation — can be a follow-up).
  • single-line definitions only; GFM's multi-line/indented continuations are out of scope for v1 and keep today's behavior.
  • streaming: the preprocessor is a pure function of each snapshot, so numbering stays stable as text grows; a partially streamed [^ renders as literal text until the bracket closes (same class of jitter as other constructs).
  • your architecture notes describe preprocessing as "not ideal" with in-parser math on the roadmap — if you'd rather not grow this stage, happy to discuss alternatives (a post-parse rewriter, or waiting on upstream swift-markdown support). could also gate it behind MarkdownParseOption like latexMatchingRules if you want it opt-out.

validation plan

  • unit tests for the preprocessor (pure string → string: numbering, missing definitions, unreferenced definitions, code-fence immunity, idempotence on already-processed text);
  • a unit test for the superscript inline conversion;
  • snapshot tests (iOS + macOS variants) for a document with references and the footnotes section;
  • kitchen-sink fixture update once supported, plus the README support-list move.

if this sounds good, i'll send a focused PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions