Monospaced fonts and column units
Question: In CSS, how can we use columns to specify the width of a block of preformatted text?
Read on for an answer.
This bugged me, perhaps because I’m a relic of the old Apple and DOS days when text was always 40 or 80 columns. The column just feels to me like a natural unit of measure when I’m dealing with fixed-width type. I wanted my code to line up nicely:
1 2 3 4 5 6 7 8 |
#lang racket ;; Finds Racket sources in all subdirs (for ([path (in-directory)]) (when (regexp-match? #rx"[.]rkt$" path) (printf "source file: ~a\n" path))) (symbol->string 'foo) ; 345678901234567890123456789012345678901234567890123456789012345678901234567890 ; 10 20 30 40 50 60 70 80 |
More generally, code blocks in web sites are a pet peeve, because so many otherwise great-looking sites wind up with code spilling out of its borders when the author later puts in some code that’s too wide.1 So I’ve worked out the necessary math, and the resulting technique is what I’ve used for sizing the fonts in highlighted sample code blocks here.
My first thought was of ems—the em was originally the width of an “M”—but it turns out an em is a good bit wider than any character in most—all?—fixed-width type. Why? It was actually defined as the height of a block of type, which should explain why it’s too wide: capitals in a face such as Courier are taller than wide, and the height includes some whitespace above and below the character itself as well. Bottom line: equating columns to ems just isn’t right.
However, columns are proportional to height, which is sufficient. We just need the aspect ratio of the character glyph: w/h, where w is width and h height.2 Once we know that ratio, we have two ways to proceed:
- Given a font size, compute the width for a given number of columns.
- Given a desired width, compute an appropriate font size.
Since the aspect ratio is width over height (i.e., long dimension over short), we can think of it as units of height per column, yielding these calculations for element width and font size3:
- (element width) = (columns) * (font size) * (aspect ratio)
- (font size) = (element width * aspect ratio) / (columns)
The translation to CSS is straightforward. I’ll aim for 80 columns. Here’s the HTML I want to style:
1 2 3 4 5 6 7 8 9 10 11 |
<!-- Specifying a block of fixed-width text in columns --> <pre class="width-in-cols"> 12345678901234567890123456789012345678901234567890123456789012345678901234567890 10 20 30 40 50 60 70 80 </pre> <!-- Specifying a block width, then matching the font size to that width --> <pre class="font-to-match"> 12345678901234567890123456789012345678901234567890123456789012345678901234567890 10 20 30 40 50 60 70 80 </pre> |
For the fixed font I use here (Triplicate) as well as the Mac’s Courier system font and Source Code Pro, some careful measurement determined the aspect ratio to be 0.6.4 For other faces it may differ; e.g., Lucida Console has a ratio of about 0.61. Here’s the CSS:
1 2 3 4 5 6 7 8 9 |
pre { font-family: Courier; border: thin solid red; } pre.width-in-cols { width: calc(0.6 * 80 * 1em); /* aspect * cols * font size */ } pre.font-to-match { width: 400px; font-size: calc(400px / 80 / 0.6); /* (width / cols) / aspect */ } |
To conclude, here’s a codepen demonstrating the same technique with LESS. If you know a better way, please let me know!
-
I don’t mind a column limit, if I know what that limit is. Having only a vague idea—that’s what bugs me. ↩
-
OK, technically the aspect ratio is longer side over shorter side, but w/h serves us better here than h/w, and I’m using the term consistently with that meaning in this article. ↩
-
“Font size” here means CSS
font-size
, which means height, regardless of units. ↩ -
Well, almost. Desktop Safari by default won’t show fonts smaller than 9pt, and even if you disable that option, it winds up off by a few columns. I’d love to know why, but I’m willing to tolerate a little scrolling for the occasional long line. ↩