Something truly monumental happened in the world of software development in 2025. Safari shipped a
reasonable implementation of text-wrap: pretty:
https://webkit.org/blog/16547/better-typography-with-text-wrap-pretty/. We are getting
closer and closer to the cutting-edge XV-century technology. Beautiful paragraphs!
We are not quite there yet, hence the present bug report.
A naive way to break text into lines to form a paragraph of a given width is greediness: add the
next word to the current line if it fits, otherwise start a new line. The result is unlikely to be
pretty — sometimes it makes sense to try to squeeze one more word on a line to make the lines more
balanced overall. Johannes Gutenberg did this sort of thing manually, to produce a beautiful page
above. In 1981, Knuth and Plass figured out a way to teach computer to do this, using dynamic
programming, for line breaking in TeX.
Inexplicably, until 2025, browsers stuck with the naive greedy algorithm, subjecting generations of
web users to ugly typography. To be fair, the problem in a browser is harder version than the one
solved by Gutenberg, Plass, and Knuth. In print, the size of the page is fixed, so you can compute
optimal line breaking once, offline. In the web context, the window width is arbitrary and even
changes dynamically, so the line-breaking has to be “online”. On the other hand, XXI century
browsers have a bit more compute resources than we had in 1980 or even 1450!
Making lines approximately equal in terms of number of characters is only half-way through towards a
beautiful paragraph. No matter how you try, the length won’t be exactly the same, so, if you want
both the left and the right edges of the page to be aligned, you also need to fudge the spaces
between the words a bit. In CSS,
text-wrap: pretty
asks the browser to select line breaks in an intelligent way to make lines roughly equal, and
text-align: justify
adjusts whitespace to make them equal exactly.
Although Safari is the first browser to ship a non-joke implementation of text-wrap, the
combination with text-align looks ugly, as you can see in this very blog post. To pin the ugliness
down, the whitespace between the words is blown out of proportion. Here’s the same justified
paragraph with and without text-wrap: pretty:
The paragraph happens to look ok with greedy line-breaking. But the “smart” algorithm decides to add
an entire line to it, which requires inflating all the white space proportionally. By itself, either
of
p { text-wrap: pretty;text-align: justify;}
looks alright. It’s just the combination of the two that is broken.
This behavior is a natural consequence of implementation. My understanding is that the dynamic
programming scoring function aims to get each line close to the target width, and is penalized for
deviations. Crucially, the actual max width of a paragraph is fixed: while a line can be arbitrary
shorter, it can’t be any longer, otherwise it’ll overflow. For this reason, the dynamic programming
sets the target width to be a touch narrower than the paragraph. That way, it’s possible to both
under and overshoot, leading to better balance overall. As per
original article:
The browser aims to wrap each line sooner than the maximum limit of the text box. It wraps
within the range, definitely after the magenta line, and definitely before the red line.
But if you subsequently justify all the way to the red line, the systematic overshoot will manifest
itself as too wide inter-word space!
WebKit devs, you are awesome for shipping this feature ahead of everyone else, please fix this small
wrinkle such that I can make my blog look the way I had intended all along ;-)