Pandoc PDF Engines: Choose by Source Format

Choose Pandoc PDF engines by the content that must reach the renderer: use a TeX path for semantic Markdown and TeX math, or an HTML path for CSS-dependent content. The decision has three branches: structured source that can become LaTeX, HTML-dependent source that needs an HTML renderer, and existing PDFs that need extraction before Pandoc can read them. Changing engines cannot restore content already lost during conversion. 12

Classify the content before choosing an engine

Choose the intermediate format from the document's content dependencies, then choose a compatible PDF engine.

Feature How to identify it Route
Headings, Markdown tables, and TeX math The source contains Markdown structure and math delimiters, without layout-critical raw HTML Generate LaTeX, then use a compatible TeX engine
CSS or embedded HTML controls the layout The source contains table tags, HTML-only blocks, or styles required for the printed result Generate HTML and inspect it before selecting an HTML-to-PDF engine
An existing PDF is the source Pandoc rejects the input as an unknown PDF format Extract or OCR the PDF into a supported source format first

Pandoc reads a source format into its document model, writes an intermediate representation, and invokes an external program to produce the PDF. Its source code places the writer before the renderer dispatch. A missing table can therefore originate before any typesetting begins. The inspected source is Pandoc's main branch. 3

A source filename alone does not settle the route. Markdown can contain raw HTML, while an HTML document can contain a table that Pandoc parses into a native table structure. The distinction that changes the result is whether the writer receives a structured table or HTML-specific material. Our controlled conversion produced different LaTeX from those cases. 4[E1]

Use the LaTeX path for portable document structure

Use a TeX engine when the content survives as semantic LaTeX, and fix missing structure before switching between TeX engines.

Engine choice Documented reason to consider it Execution status here
pdfLaTeX The default LaTeX PDF route described by Pandoc not executed for this guide
XeLaTeX or LuaLaTeX A TeX route when font selection or Unicode requirements change the setup not executed for this guide
Tectonic Another supported LaTeX compilation route Attempted. Blocked by a cache-directory permission error

Pandoc's font FAQ illustrates XeLaTeX with a font containing the required glyphs. That example does not establish that changing the engine installs missing fonts. Font availability belongs to the rendering environment, while missing table markup belongs to the conversion stage. 2

The controlled sample contained an HTML table embedded in Markdown, a separate explicitly raw HTML paragraph, and a math expression. In the LaTeX output, the table's labels and value remained as separate text paragraphs. The raw paragraph disappeared. The math expression still had TeX display-math delimiters. No PDF renderer was needed to observe those differences. [E1]

Output from our embedded-table sample, unedited:

Semantic prose remains readable.
        
        Region
        
        Units
        
        North
        

The HTML table tags had not become a native table in this Markdown-reader configuration. The native document dump showed raw HTML tags interspersed with plain text. Replacing that source table with a Markdown pipe table produced LaTeX table markup. Reading the table as an HTML document also produced table markup. These controls isolate the input representation as the difference, rather than a property of the eventual TeX engine. [E1]

Failure condition Visible symptom Action
HTML-specific structure reaches the LaTeX writer Labels survive, but no table environment surrounds them Express the table as a semantic table or use the HTML route
A format-specific raw block targets HTML Its content is absent from generated LaTeX Provide a portable representation or keep an HTML output path
Fonts or TeX resources are unavailable The generated LaTeX contains the content, but compilation fails Repair the named font, package, or resource dependency

Keep the HTML path when CSS determines the result

Choose an HTML renderer for CSS-dependent content, while treating Pandoc conversion and browser-style rendering as separate operations. 5

Intermediate format Compatible examples Execution status here
HTML WeasyPrint, Prince, pagedjs-cli not executed for this guide

Our HTML intermediate retained the embedded table and the explicitly raw paragraph. That establishes content presence before rendering, without establishing pagination, CSS fidelity, or final PDF appearance. The LaTeX path had already removed structure from the same source. Selecting an HTML engine changes which intermediate language reaches the renderer. [E1]

For a Pandoc HTML-to-Markdown workflow, specify the HTML reader and an explicit Markdown dialect. The command below targets GitHub Flavored Markdown and writes a file the reader can edit. It was executed against the semantic table fixture described in the testing section. [E1]

pandoc input.html -f html -t gfm --wrap=none -o output.md
        

Output from our semantic HTML table sample, unedited:

| Region | Units |
        |--------|-------|
        | North  | 37    |
        

That conversion preserves a simple table's content as a Markdown table. It does not promise to reproduce the original stylesheet. Historical Pandoc discussions distinguish HTML parsed through the HTML reader from raw HTML carried inside Markdown. An older table-class issue also shows why stylesheet selectors can require their own diagnosis. Neither issue is treated as a reproduced defect in the installed version. 812

Failure condition Visible symptom Action
The conversion drops a class or changes table markup The generated HTML no longer matches the CSS selector Adjust the conversion or selector before rendering
HTML math needs a different representation The intermediate contains math markup the chosen renderer does not handle Select a supported math-output mode and inspect that intermediate
The intermediate is correct but page layout fails Content exists in HTML, but the PDF clips or breaks it Investigate the HTML renderer's print-layout behavior

Extract existing PDFs before handing content to Pandoc

Existing PDFs need an extraction stage before Pandoc, and the extracted table syntax can affect the later PDF output. 2

Pandoc rejected our mixed PDF before reaching a writer. The input error explicitly said that PDF input was unsupported. Changing a PDF output engine does not introduce a PDF reader. The local extraction paths below were tested on the same PDF corpus to expose what would enter the next conversion stage. [E1][E2]

MarkItDown produced a pipe table from our simple grid, but the column sample contained interleaved fragments. Its default local configuration left the scanned page empty in the mixed document. The table issue reported against an older version therefore cannot stand in for the behavior of the installed version. 69[E2]

Output from our column sample, unedited:

Invoice reconciliation starts with the export file, not
        
        Demand forecasts fail upward more often than they
        
        the ledger. Teams that reconcile weekly pull the
        
        fail downward. Optimistic sales inputs push safety
        

PyMuPDF4LLM kept the column sample's prose in order and emitted a Markdown table for the simple grid. Its default conversion also left the mixed document's scanned page empty. These results describe the executed configuration, without an additional OCR setup. 7[E2]

Output from our simple table sample, unedited:

# **Quarterly invoice summary** 
        
        |**Item**|**Qty**|**Unit Price**|**Total**|
        |---|---|---|---|
        |Consulting hours|12|85.00|1020.00|
        

Markovo's PDF-to-Markdown path retained the scanned text in the supplied API output, but its formula sample contained altered mathematical markup and matched fewer expected expressions than the local paths. Its simple-table output used HTML table markup, which can require normalization before a LaTeX workflow. [E2]

Output from our formula sample, unedited:

$$
        \[\mathsf{E}=\mathsf{m}\mathsf{c}\mathsf{^2}\]
        $$
        
Extraction failure Visible symptom in our output Consequence before Pandoc
No OCR in the executed local configuration The scanned page contributes no text Supply an OCR result before rebuilding the document
Column interleaving Sentences alternate between unrelated passages Restore reading order before exporting
Table representation differs One path emits pipe syntax, another emits HTML tags Choose a compatible writer or normalize the table
Formula recognition alters notation Display math contains changed symbols or nested delimiters Correct the expression before typesetting

Change the renderer after locating the first loss

Fix content where it first disappears, and change the PDF renderer when the intermediate is intact but the rendered output fails.

Generate a standalone intermediate document for the chosen route and search it for a phrase or table label missing from the PDF. If the phrase is absent there, changing the engine cannot restore it. If the phrase exists but the table environment is absent, repair the table representation. If both content and structure exist, the renderer's fonts, packages, or page-layout rules become the next diagnostic target. 5[E1]

The standalone commands below were executed against the controlled Markdown sample. [E1]

pandoc input.md -f markdown -s -t latex -o output.tex
        pandoc input.md -f markdown -s -t html -o output.html
        

Our Tectonic attempt stopped at a cache-directory permission error. It produced no PDF, so it supplies no evidence about font coverage, pagination, or rendering speed. [E1]

Use semantic Markdown when the document must travel through LaTeX. Keep an HTML route when its structure and CSS are part of the required result. When the source is already a PDF, repair extraction losses before passing the document to either route.

How we tested

These tests locate conversion losses. They do not rank PDF rendering quality. Pandoc generated intermediate text, while a Tectonic compilation attempt failed before producing a PDF. Other PDF engines named above were not executed for this guide.

Markovo is the service we build. It ran through the same corpus and scorer as the other paths; its row is reported in full, including where it scored lower.

The local execution and scoring date was 2026-10-18. Python was 3.11.14, MarkItDown 0.1.8, PyMuPDF4LLM 1.28.2, Pandoc 3.11, and Tectonic 0.17.0. MarkItDown ran with plugins disabled, PyMuPDF4LLM used the default to_markdown call. No extra OCR plugin or explicit OCR setup was added. The supplied Markovo outputs are reused API artifacts: the mixed-document job snapshot records creation on 2026-10-06. The supplied snapshots record rate-card version 2026-09-19.v3-media-unified. This is not a model version or a claim of a fresh API call on the scoring date.

Corpus and commands

PDF file in lab/corpus/ Represented shape
prose-single.pdf Single-column prose
table-simple.pdf A simple grid table
table-merged-header.pdf A table with grouped headers
two-column.pdf Prose in columns
scan-image.pdf An image-only scanned page
mixed.pdf Prose, a table, and a scanned page
equation.pdf Displayed mathematical expressions
rotated-header.pdf Rotated table headers

Each PDF has a matching .truth.json. The Pandoc-specific fixtures are lab/engine-probe/input.md, portable.md, and semantic.txt. The semantic HTML is also saved as input.html for the command example. The Markdown fixture combines embedded HTML table tags, an explicitly raw HTML block, and display math. The controls use a pipe table or the HTML reader. These fixtures measure representation boundaries, not print appearance.

Run from the repository root:

tools/lab-venv/bin/python tools/lab_run.py \
          --lab runs/2026-10-18-markovo-pandoc-pdf-engines/lab \
          --tools markitdown,pymupdf4llm \
          --samples prose-single,table-simple,table-merged-header,two-column,scan-image,mixed,equation,rotated-header
        tools/lab-venv/bin/python runs/2026-10-18-markovo-pandoc-pdf-engines/lab/engine_probe.py
        tools/lab-venv/bin/python tools/lab_score.py \
          --lab runs/2026-10-18-markovo-pandoc-pdf-engines/lab \
          --out runs/2026-10-18-markovo-pandoc-pdf-engines/lab/scores.json
        

The standalone diagnostic commands and text copies of their outputs are recorded in lab/engine-probe/diagnose.json, standalone-latex.txt, and standalone-html.txt. The Pandoc probe script records commands, exit status, elapsed time, and stdout/stderr in lab/engine-probe/results.json. Its commands include -f markdown -t html, -f markdown -t latex, -t native, -f html -t latex, and the pipe-table control. Tectonic ran with --pdf-engine-opt=--only-cached. The error named a denied write to the user cache directory. No successful PDF was produced. The probe outputs are not extraction-score rows and must not be read as an engine benchmark. Pandoc's main-branch source was inspected on the research date. It was not pinned to the installed binary's commit.

Scoring definitions and complete selected-corpus results

The table below is a direct projection of lab/scores.json for the eight PDFs and the three extraction paths discussed here. It includes every selected tool–sample pair. Other supplied Office and heavy-tool outputs are outside this article's comparison. The label n/a means the metric does not apply.

The definitions come from tools/lab_score.py: prose_recall and scan_text_recall measure the fraction of normalized expected sentences recovered, considering sentences with at least four words, reading_order compares the positions of adjacent paragraph anchors. table_rows_intact requires every expected cell in a row to occur on the same output line. table_header_intact tests leaf-header cells sharing a line, not the meaning of spanning groups. table_pipe_detected recognizes pipe-table syntax. pages_covered checks expected page anchors, not PDF page objects. equation_recall matches expressions after mathematical-markup normalization. It does not certify mathematical equivalence or valid LaTeX. elapsed_s is the runner's recorded duration. These are fixture checks, not general accuracy estimates.

tool sample prose_recall reading_order table_rows_intact table_header_intact table_pipe_detected pages_covered scan_text_recall equation_recall elapsed_s
markovo equation n/a n/a n/a n/a n/a n/a n/a 0.5 0.0
markovo mixed 1.0 n/a 1.0 1.0 False 1.0 1.0 n/a 0.0
markovo prose-single 1.0 1.0 n/a n/a n/a n/a n/a n/a 0.0
markovo rotated-header n/a n/a 1.0 1.0 False n/a n/a n/a 0.0
markovo scan-image n/a n/a n/a n/a n/a n/a 1.0 n/a 0.0
markovo table-merged-header n/a n/a 1.0 1.0 False n/a n/a n/a 0.0
markovo table-simple n/a n/a 1.0 1.0 False n/a n/a n/a 0.0
markovo two-column 1.0 1.0 n/a n/a n/a n/a n/a n/a 0.0
markitdown equation n/a n/a n/a n/a n/a n/a n/a 1.0 0.0605
markitdown mixed 1.0 n/a 1.0 1.0 True 0.6667 0.0 n/a 0.0267
markitdown prose-single 1.0 1.0 n/a n/a n/a n/a n/a n/a 0.0206
markitdown rotated-header n/a n/a 1.0 0.0 True n/a n/a n/a 0.0107
markitdown scan-image n/a n/a n/a n/a n/a n/a 0.0 n/a 0.0088
markitdown table-merged-header n/a n/a 1.0 0.0 True n/a n/a n/a 0.0132
markitdown table-simple n/a n/a 1.0 1.0 True n/a n/a n/a 0.0115
markitdown two-column 0.1111 0.8 n/a n/a n/a n/a n/a n/a 0.0355
pymupdf4llm equation n/a n/a n/a n/a n/a n/a n/a 0.75 0.1992
pymupdf4llm mixed 1.0 n/a 1.0 1.0 True 0.6667 0.0 n/a 0.2945
pymupdf4llm prose-single 1.0 1.0 n/a n/a n/a n/a n/a n/a 0.1297
pymupdf4llm rotated-header n/a n/a 1.0 0.0 True n/a n/a n/a 0.1471
pymupdf4llm scan-image n/a n/a n/a n/a n/a n/a 0.0 n/a 0.1872
pymupdf4llm table-merged-header n/a n/a 1.0 0.0 True n/a n/a n/a 0.1466
pymupdf4llm table-simple n/a n/a 1.0 1.0 True n/a n/a n/a 0.1497
pymupdf4llm two-column 1.0 1.0 n/a n/a n/a n/a n/a n/a 0.1429

The Markovo rows contain supplied artifact-reuse durations of zero. End-to-end API timing is unavailable, so those values cannot support a speed comparison. The local durations include the executed conversion call, without a repeated timing protocol.

In lab/markovo/mixed.json, job.pages_ocr is 3, while job.billing_metrics.pagesOcr is 1. The queued response's billing_metrics.ocrPageIndexes is [2]. Those fields have different reported values. We do not infer a definitive OCR-routing count from them. The retained scan text is observed in the downloaded Markdown. This article does not use quality_score as a substitute for the shared scorer.

Raw output locations and limits

The extraction outputs are lab/markitdown/<sample>.md, lab/pymupdf4llm/<sample>.md, and lab/markovo/<sample>.md, using the eight sample stems in the corpus table. The matching Markovo JSON snapshots are in lab/markovo/. lab/execution-record.md records the first twenty output lines per pair. The Pandoc excerpts come from lab/pandoc/engine-probe.md and lab/engine-probe/html-to-gfm.txt. The first is an unchanged copy of latex.txt. The exact HTML command execution is logged in lab/engine-probe/html-command.txt, with its result in lab/engine-probe/output.md.

Historical issues supplied failure cases for research, not original attachments rerun in this lab. The synthetic table fixture cannot establish the behavior of every merged table. The equation scorer cannot establish general mathematical correctness. No final PDF font, pagination, accessibility, or visual-quality comparison was completed.

Sources

  1. Pandoc writer and PDF engine options
  2. Pandoc FAQs
  3. Pandoc makePDF source, main branch
  4. Pandoc raw HTML and math rules
  5. Pandoc: Creating a PDF
  6. MarkItDown official README
  7. PyMuPDF4LLM official documentation
  8. Pandoc HTML table discussion, historical
  9. MarkItDown table report, version 0.1.3
  10. Pandoc table CSS class report, historical
  11. Local Pandoc boundary probes
  12. Shared PDF corpus extraction scores