October 9, 2008 9:37 am

Comparing Font Binaries

Yesterday I had to dive into a large number of fonts to fix a tiny bug. It sounds easy enough, but font mastering these days is a process of educated guessing, pure guessing, crashing software, MacGyver like problem solving and finger crossing. As a result of this chaos, I’ve gotten into the habit of testing font binaries. In this case, I wanted to compare the new files to the old files to make sure that the only differences were related to the bug fix. This is actually very easy to do. I usually dump the OpenType files to XML with Just van Rossum’s indispensable TTX and compare the files with TextWrangler/BBEdit/FileMerge/Whatever. However, in this case I had a large number of files to look at and it would have taken me days to sort through all of them. Python to the rescue.

I put together a script (below) that dumped all files to XML with FontTools, compared them and reported the results. You’ll need to have FontTools installed and you’ll need to run this in Terminal (or something that pipes a script to Terminal like TextMate) on the Mac. (Somewhere else on Windows.)

import os
import glob
from difflib import Differ
from fontTools.ttLib import TTFont

# Paths to the directories containing the fonts to compare.
oldDirectory = "/path/to/a/directory/of/old/fonts"
newDirectory = "/path/to/a/directory/of/new/fonts"

# Find all OTF file names.
pattern = os.path.join(oldDirectory, "*.otf")
fileNames = glob.glob(pattern)
fileNames = [os.path.basename(path) for path in fileNames]

# A function that filters out unwanted reports.
# It returns True if the line should be ignored
# and False if the line is relevant.
def ignoreLine(line):
    # OS/2 checksum
    if line.startswith("-     <checkSumAdjustment value="):
        return True
    if line.startswith("+     <checkSumAdjustment value="):
        return True
    # OS/2 created
    if line.startswith("-     <created value="):
        return True
    if line.startswith("+     <created value="):
        return True
    # OS/2 modified
    if line.startswith("-     <modified value="):
        return True
    if line.startswith("+     <modified value="):
        return True
    # fallback
    return False

# A place to store all reports.
report = []

# Run through all fonts.
for fileName in fileNames:
    # Construct the needed paths.
    oldFontPath = os.path.join(oldDirectory, fileName)
    newFontPath = os.path.join(newDirectory, fileName)
    oldTtxPath = os.path.splitext(oldFontPath)[0] + ".ttx"
    newTtxPath = os.path.splitext(newFontPath)[0] + ".ttx"
    # Create ttx for both of the fonts.
    ttxText = []
    paths = [
        (oldFontPath, oldTtxPath),
        (newFontPath, newTtxPath)
    ]
    for fontPath, ttxPath in paths:
        # Open the font.
        font = TTFont(fontPath)
        # Write ttx.
        font.saveXML(ttxPath)
        # Close the font.
        font.close()
        # Read the ttx.
        f = open(ttxPath)
        ttx = f.read()
        f.close()
        # Store the ttx.
        ttxText.append(ttx)
        # Remove the ttx path.
        os.remove(ttxPath)
    oldTtx, newTtx = ttxText
    # Find differences between the two ttx reports.
    diffs = []
    differ = Differ()
    oldTtx = oldTtx.splitlines()
    newTtx = newTtx.splitlines()
    result = differ.compare(oldTtx, newTtx)
    for line in result:
        # Only pay attention to the lines beginning
        # with - and +. (See the difflib documentation
        # for information output.)
        if not line[0] in ("-", "+"):
            continue
        # Pass the line to the line filter to see
        # if it is relevant.
        if not ignoreLine(line):
            diffs.append(line)
    # If diffs were found, add them to the report.
    if diffs:
        report.append("-" * len(fileName))
        report.append(fileName)
        report.append("-" * len(fileName))
        report += diffs

# Finally, print the report.
print "\n".join(report)

The output looks like this:

-------------
Some-Font.otf
-------------
-     <fontRevision value="2.0"/>
+     <fontRevision value="2.001"/>
-       Version 2.000;PS 002.000;hotconv 1.0.50;makeotf.lib2.0.16970
+       Version 2.001;PS 002.001;hotconv 1.0.50;makeotf.lib2.0.16970
-       Version 2.000;PS 002.000;hotconv 1.0.50;makeotf.lib2.0.16970
+       Version 2.001;PS 002.001;hotconv 1.0.50;makeotf.lib2.0.16970
-       <version value="002.000"/>
+       <version value="002.001"/>
Filed under: OpenType, Programming