Fixing PT Serif

Saturday 15 September 2018

When I redesigned this site last year, I chose PT Serif as the body typeface. It is narrow but readable, has character, but is not quirky. It wasn’t until I had finished the design and gotten it launched that I noticed something that bothered me. The curly quotes are designed so that the bulbs are at the same height, and the tails go up and down from there. It’s not that noticeable on a word, but on single characters they just look cockeyed to me:

Close-up of PT Serif curly quotes on a word and a character

I lived with it for a while, but I couldn’t stop seeing it, so I decided to fix it. All I had to do was to scooch the closing quotes up a little. This site is generated with Django, so I wrote some middleware that would find all of the closing curly quotes in the output, and wrap them with a span:

    ('&#8221;', '<span class="cq">&#8221;</span>'),
    ('&#8217;', '<span class="cq">&#8217;</span>'),

class TweakOutputMiddleware:
    def process_response(self, request, response):
        for before, after in REPLACEMENTS:
            response.content = response.content.replace(before, after)
        return response

Then I added a little CSS to move that span, and the quotes were fixed:

.cq {
    position: relative;
    top: -.1em;

But then I had lunch with David Jonathan Ross, an accomplished and acclaimed type designer. When I mentioned this tweak to him, he said, “Why not just fix the font?”

This was a stark illustration of different experts’ tool sets. I’m comfortable screwing around with Django, HTML, and CSS. Even though I am a huge fan of fonts and type technology, and know a lot about both, I have never modified a font. They have always been immutable to me. But David is a type designer. Typefaces are the clay in his hands, so his first reaction was to just change the font.

I was intrigued. We discussed how I might go about that. No shape changes were required. I just had to move some glyphs vertically.

First I retrieved the four TrueType font files for the four versions of PT Serif. My HTML pages had this line to get the fonts from Google:,400i,700,700i|Source+Sans+Pro:400,400i,700,700i

Retrieving that URL got me a small file of CSS @font declarations, with URLs for the individual font files that I could download.

There were two basic approaches I could try to changing the fonts: GUI font editors, or programmatically. There are a handful of GUI font editors available, some free, some not. They are a bit overwhelming, because you are suddenly confronted with a lot of fiddly Bézier curves, and it is very easy to make things look horrible.

Just adjusting the vertical height of a glyph isn’t hard. But the app might have other ideas. For example, I tried the Glyphs editor, and when I opened PT Serif, it immediately offered to fix four problems. I didn’t know there were problems! Do I want to change those things? I just wanted to move some quotes...

The programmatic option was looking more appealing, and I wanted to understand the possibilities there anyway. David had recommended fonttools, a suite of tools written in Python for manipulating fonts. This sounded like home!

I first tried using fonttools as a Python library that I could write code with to change the font. But the examples and docs were not immediately helpful in understanding the arcane internal details.

So I tried something that David had suggested to me: fonttools provides a ttx command that converts fonts to and from an XML format. You can then modify the XML, and convert it back into a font.

I converted PT Serif into a .ttx XML file, and opened it up. I found the definition of the right curly double-quote glyph:

<TTGlyph name="quotedblright" xMin="57" yMin="440" xMax="401" yMax="699">
  <component glyphName="quotesinglbase" x="182" y="585" flags="0x4"/>
  <component glyphName="quotesinglbase" x="0" y="585" flags="0x4"/>
      PUSHB[ ] /* 1 value pushed */
      CALL[ ] /* CallFunction */
      IF[ ] /* If */
        PUSHB[ ] /* 5 values pushed */
        63 6 79 6 2
        DELTAP1[ ] /* DeltaExceptionP1 */
        PUSHB[ ] /* 7 values pushed */

First, notice that there are instructions there that look a lot like a byte code of some kind. I had heard that font files had stuff like this in them, but had never dived into the details. A quick look at the instructions told me that the things I needed were not there.

The “component” pieces are the useful parts here. Fonts have repetitive elements. Rather than repeat those curves over and over, the font file can assemble a glyph from other parts. Here the quotedblright glyph is made with two copies of the quotesinglbase character, each with an x and y offset.

Could it be that just changing those offsets, and some other y-referencing values, will be enough? Based on my CSS tweaks, I knew that I wanted to raise the glyph by .1em. An “em” is a unit of measurement equal to the font size, so in a 10-point font, an em is 10 points. I knew that font designs usually are done on a grid with 1000 units to an em, so all I had to do was add 100 to the y values.

I changed those first three lines to:

<TTGlyph name="quotedblright" xMin="57" yMin="540" xMax="401" yMax="799">
  <component glyphName="quotesinglbase" x="182" y="685" flags="0x4"/>
  <component glyphName="quotesinglbase" x="0" y="685" flags="0x4"/>

After converting the .ttx to a .woff2 file, changing my CSS to refer to the new fonts, and uploading everything, it all works! Now my “quotes” are more balanced: “q”.

I’ve only just barely dipped my toe into these technologies. I’m afraid it’s a Pandora’s box of tempting traps. For example, the upper-case Q in PT Serif has a detached tail that I would rather were attached. But changing that is a whole other level of effort and skill, so probably best left as-is.


Sorry, but they still seem unbalanced to me. Are you sure you loaded the right font? :-]
I've heard reports that on a phone the old CSS is still in effect, so the changes aren't being used?
Interestingly enough, here (Chrome on Win 10) the look varies by zoom level. At 100% they look mildly off-balance; blown-up to 300% they look balanced, and at 75% zoom they look as bad as the original font's quotes.

Here's a screenshot at different zoom levels in the browser (300%, 100%, 75%)
Oh, and a couple additional comments: glad you went through the trouble of doing it, I too find this annoying; and personally I'd go the other way around, and lower the left quote, as I find they tend to be too high as they are (that or maybe make them both meet in the midpoint ...)
@Claudio: Hmm, I definitely see what you mean on Windows. The zoom level has a real effect on how the closing quote aligns. I tried it in Edge 42.something, and the zoom level effects weren't the same as yours with Chrome, but the zoom level definitely changed how they looked. I have no idea what to do about that...
Thanks for sharing your process and tools. It's really interesting to see the different approaches (html/css versus font).

It seems like the modified font doesn't quite line up for me on Chrome OS. See

Did you look at the similarities/differences between the left quote and right quote for clues on how to align them?
Very cool!

The old font is still loading on some browsers because you have src:local() which loads the font from the device if the device already has it. Just do src:url(etc) to always load the font from your website.

(or rename PTSerif-Bold and PTSerif-BoldItalic to also have -NB- in them like you do for regular and italic.)

Maybe you could create an issue/PR to maybe get your "fix" "upstream" :)
@Aron: yes, the quotes are not literally lined up now, partly because it's only a tiny amount off, and I didn't want to go crazy trying to find the precise amount to move it. Also, I figured I could keep a little amount of original intent. Honestly, I would move the opening quote down a little, but it turns out that glyph is a little harder to move, based on how it is constructed.

I did look at some other fonts to see how their quotes aligned. But I recognized a point of diminishing returns when I saw one...
@Collin, thanks. I meant to go read about what those font declarations actually meant, and forgot to. I've removed the local component, but it doesn't seem to have fixed Edge.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.