Code blocks in a PDF: monospace, long lines and the page edge

Use fenced blocks, keep every line under about 80 characters, and let the typesetter hold each block on one page. Monospace type is wider than body type at the same size, so a listing fits fewer characters on a line than prose does — the fix is shorter lines in the source, not smaller type in the PDF.

A code block is the one element in a Markdown manuscript that cannot reflow. Prose wraps wherever the measure says; a line of code has a meaning that depends on where it breaks. So every decision about code in a PDF comes back to one question: how many characters fit across the page, and what happens to the ones that do not.

Fence it, and say what it is

Three backticks on a line before and after. The four-space indented form still exists in CommonMark and still breaks inside lists and after block quotes; the fence does not.

```sh
curl -s https://example.com/api/books | jq '.[] | .title'
```

The language tag after the opening fence costs nothing and is worth putting in even when the renderer ignores it. It documents what the block is, it lets a future renderer highlight it, and it stops an editor guessing. A block with no tag is a block that will one day be highlighted as the wrong language.

Inside the fence nothing is interpreted. Asterisks stay asterisks, underscores stay underscores, and a < is a less-than sign. That is the reason to use a fence for anything that must appear exactly as typed — a config file, a command, a stack trace — even when it is not code in the strict sense.

Why monospace changes the measure

In a proportional face an i is narrow and an m is wide, and a typical line of English averages out to something like half an em per character. In a monospace face every glyph is the same width, usually about 0.6 em. At the same point size, then, a line of code holds fewer characters than a line of prose — and the 60-to-75-character measure that suits body text becomes a hard wall the code runs into.

Type sizeMonospace characters per line*Fits an 80-column listing?
11pt≈ 68No
10pt≈ 75Barely
9pt≈ 83Yes
8pt≈ 94Yes, and hard to read in print

*On an A4 page with 20mm margins — a text width of about 170mm. Wider margins, which a book usually wants, take a few characters off each row.

The table says what the tradeoff is. Ten point is the size at which a monospace face is still comfortable on paper and an 80-character line just about fits; going smaller buys width at the cost of legibility, and going larger means wrapping. Most technical books settle on 9 or 10 and shorten their lines to match.

The long line: three choices, one good one

A 120-character line will not fit at any readable size. Something has to give, and there are only three candidates.

  1. Let it wrap. The renderer breaks the line at the margin and continues underneath. The listing stays complete and stays on the page; it also no longer matches the file it was copied from, and a reader cannot tell a wrapped line from two lines. Acceptable as a safety net. Not acceptable as the plan.
  2. Shrink the block. Drop the code to 7pt so the line fits. Now the listing is a grey texture nobody reads, and the next block, at the same size for consistency, is unreadable for no reason.
  3. Reformat the code. Break the line in the source, the way a linter would — after a comma, before an operator, at a pipe. The listing in the book is now a valid program with shorter lines, and a reader who types it in gets the same result.

The third one is the answer nearly every time, and it is the one authors resist because it means touching the code. Touch the code. A book is an argument that the reader can follow; a line that ran off the page is a hole in it.

Keeping a block on one page

A listing that splits across a page break is the code-block version of the orphaned table header: the function signature at the foot of one page, the body at the top of the next, and the reader flipping back and forth to see whether the bracket closed. A typesetter avoids it by treating the block as a unit that moves whole to the next page if it does not fit at the bottom of this one.

That only works for blocks shorter than a page. A 90-line listing cannot be held on one A4 sheet at any size, so the block breaks somewhere and the question is where. Answer it in the source: split the listing into two fenced blocks at a natural seam, with a sentence between them saying what the second half does. Two 40-line blocks with prose between them read better than one 80-line block that happened to break at a good place this time and will break at a bad one after the next edit.

Tabs, spaces and smart quotes

Three small things that break more listings than any layout decision.

  • Tabs. A tab is rendered at whatever width the renderer decides, commonly eight columns, which turns a neatly indented block into a staircase that runs off the right edge. Convert to spaces before the manuscript is typeset. Two or four, consistently.
  • Smart quotes. A word processor that turns " into inside a fence has produced a command that fails when typed in. Write in a plain-text editor, or at least check every fence for curly quotes before export.
  • Trailing whitespace. Invisible in the file, and in some renderers a stray line break. Strip it. Most editors have a setting to do it on save.

Inline code in the prose

Single backticks around a word set it in the monospace face inside a sentence: git rebase, Ctrl-D, a filename. Use it for anything the reader would type or see on a screen, and for nothing else — a word set in monospace because it is *technical* rather than *literal* trains the reader to ignore the distinction.

Inline code has its own line-break problem. A long path or URL in backticks cannot hyphenate, so the typesetter either pushes it to the next line, leaving a gap, or lets it stick past the margin. Keep inline code short; put anything longer than a few words in a block of its own.

In bukpress

bukpress sets fenced blocks in a 10pt monospace face, padded, and keeps each block whole where the page allows rather than splitting it through a function. Lines that are still too long wrap — the safety net from the list above, not an invitation to stop shortening them. There is no syntax highlighting and no line-number gutter, which is the right default for print, where a highlight theme designed for a dark screen usually turns comments the colour of the paper.

The monospace face, like the body face, is embedded in the PDF and packaged into the EPUB, so 0 and O stay distinguishable on a machine that has never seen the font. If you are writing a whole book of the stuff, that and the measure are the two things to get right before anything else.

Read next

Guides · 5 min read

How to write a programming book in Markdown

Write the prose and the listings in the same Markdown file, in fenced code blocks, and typeset them as two different things: body text at a readable measure, code in a real monospace face that is embedded in the PDF, at a size that does not wrap a 72-character line into noise. The structure is chapters as `##`, listings kept short enough to fit on one page, and a command the reader can run without guessing at the elided bits.

Read it
Design · 5 min read

Book typography for people who are not typographers

Most of the gap between an amateur page and a professional one comes down to six things: line length, type size, line spacing, margins, how many typefaces you use, and whether the heading sizes form a clear hierarchy. None of them require taste to get right — they have known good ranges.

Read it
How-to · 5 min read

How to control page breaks in Markdown

Markdown has no page break because it describes structure, not pages — so every method is really an instruction to whatever renders it. The three that work are a raw HTML div with a CSS page-break rule, a LaTeX \newpage command through Pandoc, or a renderer that assigns a meaning to a plain-text marker.

Read it

Write it in Markdown. Ship a book.

bukpress turns the file you are already writing into a typeset PDF and EPUB — your type, your covers, no watermark on any plan.

Start free — no card required