Exporting Markdown to PDF from a browser
There are two ways a browser can produce a PDF from a web page, and they are not equally good.
The first is to render the page to an image and wrap the image in a PDF. Libraries that do this are easy to drop in, and the result is a file where the text is no longer text — you cannot select it, search it, or copy a line out of it, and the file is several times larger than it needs to be.
The second is to print the page and choose “Save as PDF” as the destination. The browser lays the document out for paper, honours the print stylesheet, and writes real vector text. It costs one extra click and produces a genuinely better file, so that is what happens here when you press Save as PDF.
The four settings that matter
Everything below is in the print dialog, not on the page, because no stylesheet can set them.
Destination: Save as PDF. Not a physical printer.
Background graphics: on. It is under More settings and it is the one people miss. With it off the browser discards every background fill — code blocks lose their shading, table headers go white, and a dark-themed PDF comes out as white paper with white text on it, which looks like a broken export rather than a setting.
Headers and footers: off. That option is the browser adding the page URL and the date of its own accord. If you have set your own header or footer, the two collide. Turn the browser’s off and keep yours — or do the reverse if you want page numbers, which is covered below.
Scale: 100% or Default. “Fit to page” rescales the type and throws the margins out, and the margins are what the header and footer spacing depends on.
Why the Scale control sometimes vanishes
If a document declares a fixed paper size, Chrome treats the paper as already decided by the author and hides the Scale control entirely. That is not a bug in the browser or the page — it is the consequence of the document being specific.
So paper is a choice: A4, Letter, or Printer default. The first two ask for a size and you lose Scale. The third asks for nothing and Scale comes back. Margins are set by the document in all three cases, so a header or footer keeps its space either way.
Running headers and footers
A running header repeats on every page. In a browser this is done with a fixed-position element, which the specification says repeats on each sheet of paged media.
The subtlety is reserving space for it. The obvious approach — padding at the top of the document body — reserves space once, because the body is one box however many pages it prints across. It works on page one and then the header sits on top of the text from page two onwards.
The fix is to reserve the space in the @page margin, which applies per page, and position
the bar into that margin with a negative offset. That is what the stamping bay does, and it
widens the margin only on the edge that actually carries a bar.
Each of the four fields — header left, header right, footer left, footer right — takes any text you like, plus tokens filled in when you print:
{title}— the first heading or first line of the document{date},{time},{datetime}— when the PDF was made
No page numbers, and why
There is deliberately no {page} token. Counting pages inside a running element is not
something a browser can do: counter(page) only resolves inside @page margin boxes, and
no major browser implements those. A token that silently printed nothing would be worse
than no token.
If you need page numbers, the browser’s own headers-and-footers option adds them. Use that instead of a stamp, or accept both and let them sit on opposite edges.
Dark PDFs
The dark option inverts the page — black paper, white ink — and leaves other colours alone. One deliberate exception: the link colour is lightened, because the light theme’s blue sits at about 2.5:1 against black and is unreadable. It keeps its hue and gains contrast.
Remember background graphics. A dark PDF printed without it is a blank white page.