Sashmark / Blog

Markdown to PDF: one pandoc command with the typst engine

Bobby Huang ·

Under Make the PDF with one typst command, read the pandoc line from this test and the file checks after it. A pdflatex message from the run that named no engine is quoted under Why the plain pandoc command stops. For reader steps once your file exists, work through Checks before you share the PDF.

Key takeaways

  • See Why the plain pandoc command stops for the T1 run that named no engine.
  • Read What pandoc hands to typst for the out3.typ lines quoted from the T3 run.
  • Look at Ask pandoc for a contents list for the T4 command line.
  • Turn to Set paper size and margins for the T5, T5b and T5c runs.
  • Check A missing image still gives a PDF for the T6 warning line.

How can I turn a Markdown file into a PDF? Add --pdf-engine=typst to a pandoc line that names your .md file and an output .pdf file. Then open the PDF yourself and read it through, since the test behind this post never looked at a page.

This post takes the pandoc command route with a small sample file. The results below all sit in the log of one test run on one machine. Its top lines give typst as 0.15.1, while the pandoc version line reads 3.8.3. Need a file to edit afterward? See the Markdown to Word guide.

Why the plain pandoc command stops

The input is notes.md, 32 lines long. Inside are a front matter title, four headings, two short lists, a three-column table, a fenced Python block, a block quote, one web link and a picture line. The picture it names, diagram.png, sat in the same folder at 2905 bytes. Here is the whole file:

---
title: Meeting notes
---

# Meeting notes

## Decisions

- Ship the draft on **Friday**.
- Keep the *old* layout for now.

1. Write the summary.
2. Send it to Jane Doe.

## Table

| Item | Owner | Due |
|:-----|:-----:|----:|
| Draft | Jane | Fri |
| Review | John | Mon |

## Code

```python
print("hello")
```

> A quoted line.

See [the guide](https://example.com/guide).

![Diagram](diagram.png)

The first run, T1, asked for a PDF and named no engine:

pandoc notes.md -o out1.pdf

Pandoc printed one line:

pdflatex not found. Please select a different --pdf-engine or install pdflatex

T1 ended with rc=47. A check for out1.pdf right after it found no such file. So the plain command gave no PDF on this machine, and the message asks you to select a different --pdf-engine. The next section names one.

Make the PDF with one typst command

T2 is the T1 line with the engine flag added:

pandoc notes.md --pdf-engine=typst -o out2.pdf

Between its command line and its exit code, the log holds no other output from T2. T2 returned rc=0. The file listing showed out2.pdf at 34471 bytes, and the first five bytes of that file read %PDF-. The test also counted /Type /Page objects inside out2.pdf, and the count came to 2.

Those three checks tell you pandoc wrote a file that starts like a PDF. They tell you nothing about what it looks like, so opening it is still on you.

As a control, T7 ran the same shape of command on nofile.md, a name with no such file in the folder:

pandoc nofile.md --pdf-engine=typst -o miss.pdf

Pandoc printed several lines. The short one that names the problem reads:

nofile.md: withBinaryFile: does not exist (No such file or directory)

T7 returned rc=1, and a check for miss.pdf found no file. So a wrong input name in this test gave a nonzero exit code and no PDF, while the good input in T2 gave rc=0 and a file.

What pandoc hands to typst

T3 asked pandoc for typst markup and saved it as out3.typ:

pandoc notes.md -t typst -o out3.typ

Its exit code was rc=0, and the log printed out3.typ in full. Here is the table part:

#figure(
  align(center)[#table(
    columns: 3,
    align: (left,center,right,),
    table.header([Item], [Owner], [Due],),
    table.hline(),
    [Draft], [Jane], [Fri],
    [Review], [John], [Mon],
  )]
  , kind: table
  )

The image part closes the file:

#figure(image("diagram.png"),
  caption: [
    Diagram
  ]
)

Set these beside notes.md and walk through them.

  • Table. The dash row in notes.md puts colons on the left of column one, on both ends of column two and on the right of column three. In out3.typ, that came through as align: (left,center,right,) with columns: 3. The header cells and the two data rows kept their text.
  • Code block. The out3.typ text carries a fenced block with the python label and the line print("hello"), matching lines 24 to 26 of notes.md.
  • Image. The image line became a #figure that loads diagram.png. Its caption reads Diagram, the alt text that notes.md gives the picture on line 32.
  • Bold, italic and lists. **Friday** turned into #strong[Friday] and *old* into #emph[old]. The two numbered items start with + in out3.typ.

The out3.typ file is pandoc's typst markup for notes.md. It can't show you where the table or the figure ends up on a page; that part is a reader check. For the image line syntax itself, see writing images in Markdown.

Ask pandoc for a contents list

T4 took the T2 line and added --toc:

pandoc notes.md --pdf-engine=typst --toc -o out4.pdf

Its exit code was rc=0 as well. The listing showed out4.pdf at 40355 bytes, which is 5884 bytes more than out2.pdf from T2. The count of /Type /Page objects in out4.pdf was 2, the same count as in out2.pdf.

The log counts page objects in out4.pdf but records no check of the contents list itself. Open out4.pdf to see the list for yourself. For page breaks, see the post on Markdown page breaks.

Set paper size and margins

T5 asked for A4 paper and a 2cm margin, with two -V variables on one line:

pandoc notes.md --pdf-engine=typst -V papersize=a4 -V margin=2cm -o out5.pdf

That run failed. The opening line of its output was error: unexpected comma, and a later line read Error producing PDF. The log ends T5 with rc=43, and the listing found no out5.pdf.

To find the comma, the test ran the same two variables again with -t typst -s and saved the source:

pandoc notes.md -t typst -s -V papersize=a4 -V margin=2cm -o out5.typ

This source run ended with rc=0. The log then printed the lines in out5.typ that name paper or margin. Two of them, with their line numbers, read:

147:  margin: (: ,),
148:  paper: "a4",

The paper value reached the source as "a4". The margin line pandoc built holds a colon and a comma with no name or value inside the parentheses.

T5b kept the paper size and dropped the margin:

pandoc notes.md --pdf-engine=typst -V papersize=a4 -o out5b.pdf

This one returned rc=0. The listing put out5b.pdf at 34486 bytes.

T5c moved the margin into a small file named margin.yaml:

margin:
  x: 2cm
  y: 2cm

Then it passed that file with --metadata-file:

pandoc notes.md --pdf-engine=typst --metadata-file=margin.yaml -o out5c.pdf

T5c returned rc=0. Its PDF, out5c.pdf, came to 34517 bytes. The source run used the same file:

pandoc notes.md -t typst -s --metadata-file=margin.yaml -o out5c.typ

That source run returned rc=0 too. Line 147 of out5c.typ read:

147:  margin: (x: 2cm,y: 2cm,),

So the YAML file gave pandoc an x and a y value to write into that line. The -V margin=2cm source run gave that line no values at all. T5c set no paper size, and the log has no run that sets paper size and the margin file together. Whether the margins come out the way you want is something to check in the PDF.

A missing image still gives a PDF

For T6, the test made noimg.md, a copy of notes.md with its image line pointed at missing.png. The diff in the log shows line 32 as the one change. Then it ran:

pandoc noimg.md --pdf-engine=typst -o out6.pdf

Pandoc printed:

[WARNING] Could not fetch resource missing.png: replacing image with description

T6 still returned rc=0. A check right after it found out6.pdf on disk. So in T6, rc=0 gave no hint that a picture had gone missing. The warning text was the one place it showed up. A script that runs pandoc should scan the printed lines as well, since rc=0 by itself let this one through. To repair it, put the picture at the path the image line names, or point the line at the right file, then run the command again.

Checks before you share the PDF

The log looked at return codes, file sizes, the first five bytes and page object counts. These steps are yours.

  1. Go over the output pandoc printed, line by line. In T6, a [WARNING] line was the sign of a missing image while rc stayed at 0.
  2. Open the PDF and go through it once from start to finish.
  3. Check the headings and both lists against your .md file.
  4. Check the table: its header, the text in its cells and how each column lines up.
  5. Check each image. Look for the picture itself, and watch for a text description sitting where a picture belongs.
  6. Compare the code block text with your .md file.
  7. If you used --toc, look for the contents list and compare it with your headings.
  8. If you set paper size or margins, check both in the PDF.
  9. Treat the .md file as your master copy. Fix problems there and run the command again.

For a docx that needs to become Markdown, read the Word to Markdown guide.

To skip the command line, the free Markdown to PDF tool makes the PDF through your browser's print window, and the file stays on your computer.

Where Sashmark fits

Before pandoc ever runs, the Markdown file itself has to be right. Sashmark, the Markdown app this site is about, is still being built; it has not been released. The plan is to show the source file as a clean page you can read through, fix a heading or a table cell on that page, and have the Round-Trip design write back that one fix while every other line stays as it was. If an agent edits the file between your sessions, the planned Changes View should flag those spots for you, git or not. The app itself has no AI features.

See the planned features, the help pages and the roadmap for where each part stands.

sashmark.com

Questions

Why does pandoc name pdflatex when I ask for a PDF?
Under Why the plain pandoc command stops, read the T1 message that names it.
Where do the margin settings go?
Go to Set paper size and margins for the margin.yaml file and the T5c run that used it.
What happens if an image file is missing?
The T6 warning line is quoted in A missing image still gives a PDF.
What if pandoc can't find the input file?
Find the T7 run and its error line in Make the PDF with one typst command.

What Sashmark plans for v1: Features · Roadmap

View as Markdown