Hexinverter ¬ Lightning Project: MakeWOFF

Yo fresh wanna put some Fonts on your Web Page? If you have a ttf that's open use, it can be a bit of a lot of unknowns to do it, which is why I haven't generally done it in the past. If you have a directory full of font variants, it can get a bit of extra typing to get everything into your CSS. So I wrote the script below to automate some of that annoyance! Also this addresses something I found really confounding, a lack of scripts or recipes for taking a TTF with all of its metadata and making the equivalent CSS for it (with font-weight and font-style information which is derivable from the font file).

Essentially, you copy your TTF files into a folder, run this on *.ttf, and it converts them to woff and woff2, and spits out the CSS for all of them. It requires woff-tools and woff2 (those are the Debian package names), and the fonttools Python library to run.

The script figures out whether the font should be decorated as oblique or italic, and the weight (approximately, if the value is accurate, there is no 1:1 mapping between TTF weight values, but they are similar range and seem to line up mostly). Magic!

#!/usr/bin/env python3

import os
import optparse
import math
import sys
import subprocess

from fontTools.ttLib import TTFont

WOFF_COMMAND = "sfnt2woff {font}"
WOFF2_COMMAND = "woff2_compress {font}"

CSS_TEMPLATE = """
@font-face {{
    font-family: '{ffamily}';
    src: url('{ffile}.woff2') format('woff2'),
         url('{ffile}.woff') format('woff');
    font-style: {fstyle};
    font-weight: {fweight};
}}
"""

def main():
    option_parser = optparse.OptionParser()
    option_parser.add_option('-p', type='string', action='store', dest='path', default='')
    options, args = option_parser.parse_args()

    for fontfile in args:
        font = TTFont(fontfile)
        fontbasename = os.path.splitext(fontfile)[0]

        try:
            fontname = font['name'].getName(1, 1, 0).string.decode('utf-8')
        except AttributeError:
            fontname = font['name'].getDebugName(1)
        fontweight = int(math.ceil(font['OS/2'].usWeightClass / 100.0) * 100) # best we can do for in between weights
        fontitalic = font['OS/2'].fsSelection & 1
        fontoblique = font['OS/2'].fsSelection & (1 << 9)

        subprocess.run(WOFF_COMMAND.format(font=fontfile), shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(WOFF2_COMMAND.format(font=fontfile), shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

        fstyle = []
        if fontitalic:
            fstyle.append('italic')
        if fontoblique:
            fstyle.append('oblique')

        if not fstyle:
            fstyle = 'regular'
        else:
            fstyle = ','.join(fstyle)

        if options.path:
            fontbasename = options.path+'/'+fontbasename

        print(CSS_TEMPLATE.format(ffamily=fontname, ffile=fontbasename, fstyle=fstyle, fweight=fontweight))

    return 0

if __name__ == '__main__':
    sys.exit(main())