Another script to batch process fonts with TTFAutohint

Ramiro Espinoza
Ramiro Espinoza Posts: 839
edited August 2014 in Technique and Theory
Hi there,

I've been writing some simple scripts to learn a bit more about Python. Yesterday I wrote this one to batch process font families with TTFAutohint in the Mac. It is useful for me and maybe for someone else.
#(re-type.com) Generate and execute the code to batch process a bunch of fonts with TTFAutohint
#IMPORTANT: You must name your fonts this way: fontname-weight.ttf 

import os

font_name = raw_input("Write the font name: ")
font_family = []
number_of_weights = int(raw_input("Write the number of weights: "))
while True:
    weight = raw_input ("Enter the name of your font weights one by one: ")
    font_family.append(weight)
    if len(font_family) == number_of_weights:
        break
input_directory = raw_input("Write the directory where your input fonts are located: ")

for n in font_family:
    run_ttfautohint = "ttfautohint -v -f latn -n -w gGD "+input_directory+font_name+"-"+n+".ttf"+" "+input_directory+font_name+"-"+n+"-HINTED"+".ttf"
    os.system(run_ttfautohint) 

Comments

  • Kent Lew
    Kent Lew Posts: 944
    edited August 2014
    FYI — for that first loop, you could simply structure it like this instead:
    while len(font_family) < number_of_weights:
        weight = raw_input("Enter the name of your font weights one by one: ")
        font_family.append(weight)
    More “pythonic” than using a conditional and break statement within the loop.
  • Ramiro Espinoza
    Ramiro Espinoza Posts: 839
    edited August 2014
    Thanks for the tip, Ken.
  • Ramiro Espinoza
    Ramiro Espinoza Posts: 839
    edited August 2014
    Tutorial for non Python-savy people: You can use this script in the following way:

    1. Copy-paste the code in a text editor.
    2. Save the file with a .py suffix.
    3. Open Terminal
    4. Type "python" and DO NOT press "Enter".
    5. Drop the .py file on the Terminal window and press "Enter".
    6. Follow the instructions of the program. The new hinted files should show up in the same directory with the word "HINTED" added to their names.

    TIP: You can easily get the exact directory where your input files are located by dropping one of the files in a new Terminal window.
    TIP2: It is very easy to find the TTFAutohint parameters in the code (just in case: line 17). You can modify them to fit your needs.
  • Paul van der Laan
    Paul van der Laan Posts: 242
    edited August 2014
    Hi Ramiro,

    Instead of letting the user input every filename one by one, you could also use the os module in Python to get access to the filesystem. Here is an example of a function that will collect all files with a ‘.ttf’ extension in a pre-defined directory .
    import os

    # small function to collect all files with (a) particular extension(s) in a directory
    def getFiles(root, ext):
    files = []
    allfiles = os.listdir(root)
    for myfile in allfiles:
    if os.path.splitext(myfile)[1] in ext:
    files.append(os.path.join(root, myfile))
    return files

    root = '/Users/paulus/Desktop/generated/truetype' # ! define your own directory here
    if not os.path.exists(root):
    print 'Not a valid directory'
    else:
    ext = ['.ttf']
    filenames = getFiles(root, ext)
    print filenames
    print 'done'
  • Mark Simonson
    Mark Simonson Posts: 1,739
    Two other ways without requiring the script to ask the user to input file names and paths, or requiring the path to be hard-coded into the script:

    - Have the user cd to the directory containing the TTFs before running and rewrite the script to assume that the TTFs are in the current working directory.
    - Or, rewrite it so that it assumes the TTFs are in the same folder as the Python script. That way, there's nothing to input and no need to modify in the script. Just copy or move the script to the folder containing the TTFs and run it.
  • Paul van der Laan
    Paul van der Laan Posts: 242
    edited August 2014
    Alternatively, you could also run the script from a RoboFab environment (i.e. FontLab, RoboFont) and make use of its built-in dialogs:
    from robofab.interface.all.dialogs import GetFolder
    root = GetFolder('Please select a folder')
    print root
  • Wait, wait! Now I need another month to learn these modules and methods :)
  • I am trying to emulate the ttfautohinting batch from FontLab or Glyphs, and almost everything is working except for the bash command. Here is what I have done from all your comments:
    # Runing from FontLab or Glyphs
    import os
    import subprocess
    from robofab.interface.all.dialogs import GetFolder

    # small function to collect all files with (a) particular extension(s) in a directory
    def getFiles(root, ext):
    files = []
    allfiles = os.listdir(root)
    for myfile in allfiles:
    if os.path.splitext(myfile)[1] in ext:
    files.append(os.path.join(myfile))
    return files

    setting = "-c -i -W"

    root = GetFolder('Please select a folder') # ! define your own directory here

    ext = ['.ttf']
    filenames = getFiles(root, ext)
    newRoot = root+"/hinted/"

    if not os.path.exists(newRoot):
    os.makedirs(newRoot)
    for font in filenames:
    run_ttfautohint = " ttfautohint "+setting+" "+root+"/"+font+" "+newRoot+font
    #print run_ttfautohint # control point
    bashCommand = subprocess.Popen(run_ttfautohint)
    bashCommand.wait()
    if not os.path.exists(newRoot+font):
    print "not hinted", font
    else:
    print "hinted", font, "@", newRoot
    print 'Finished'
  • Why do this on a Mac?
  • I thought that when TTFAutohint is running in Windows or Mac OS environments, it makes the same work. But outside of the choice of the operating system; Isn't the goal of this discussion to create a batch process through a python script that use Robofab environment in order to run TTFAutohint? I think that if this script is possible, such script could be used as a part of other scripts for generating and processing fonts. Sorry, I am a kind of naif in this topics, so I apologize.
  • LeMo aka PatternMan aka Frank E Blokland
    edited September 2014
    Dave: ‘Why do this on a Mac?

    IMHO shell scripts are a very convenient alternative, irrespective the platform. Because reproducibility is a key factor for font production –especially with tools like ttfautohint that are constantly updated– it must be easy to repeat the process. If WOFF, EOT, SVG, and SVGZ have to be generated, subsequently shell scripts can be applied too, of course.

    ---------------------------------------------------

    #!/bin/bash
    TTFA -f -i -v /Volumes/FontProduction/TrueType/Web/West/Argo/Base/A057012T.ttf /Volumes/FontProduction/Web/West/TTFA_output/Argo/West/A057012T.ttf
    TTFA -f -i -v /Volumes/FontProduction/TrueType/Web/West/Argo/Base/A057013T.ttf /Volumes/FontProduction/Web/West/TTFA_output/Argo/West/A057013T.ttf

    et cetera

    ---------------------------------------------------

    For this a fixed directory system makes sense. Because we use an eight-character file-naming system throughout the production (like URW[++] did already in the 1970s) the scripts are easy to duplicate and rename for the different typefaces. At the ATypI 2008 conference in St. Petersburg I gave a brief talk about this and the slides can be found here as PDF. The file-naming part starts at page 65.


  • I can’t comment on why the bash command is not working, but I suggest to do all the operations on file paths via the os.path module. This will make your code easier to recycle, and less Mac-centric.

    For instance:
    newRoot = root+"/hinted/"

    Would be better written as:
    newRoot = os.path.join(root, 'hinted')
  • I'd also like to pitch in for using the format function and/or % operator for strings, which would allow the ttfautohint command to be rewritten in a more readable way:
    "ttfautohint {0} {1} {2}".format(setting,
                                     os.path.join(root, font),
                                     os.path.join(newRoot, font)
    
    Or perhaps more maintainably:
    ttfcmd = "ttfautohint {0} {1} {2}"
    setting = "-c -i -W"
    inputPath = os.path.join(root, font)
    outputPath = os.path.join(newRoot, font)
    run_ttfautohint = ttfcmd.format(setting, inputPath, outputPath)
    
  • And lastly, a python feature that I've only recently fully understood (and might be interesting to someone else here): list comprehensions. getFiles can be rewritten simply:
    def getFiles(root, ext):
       return [f for f in os.listdir(root) if os.path.splitext(f)[1] in ext]
    
    I've found that this makes my Python a lot more succinct, maintainable, and easy to understand (after I got the syntax). Here's a brief overview for those so inclined: http://carlgroner.me/Python/2011/11/09/An-Introduction-to-List-Comprehensions-in-Python.html
  • It makes the same result, yes; but you can't see it without Windows. The drag and drop workflow Ramiro explained is much more convenient with the GUI that ttfautohint provides..?
  • Ramiro Espinoza
    Ramiro Espinoza Posts: 839
    edited September 2014
    Thanks for the advices. I implemented some of them in a very simplistic way (the only one I can afford for the moment :)
    import os
    
    # small function to collect all files with (a) particular extension(s) in a directory
    def getFiles(root, ext):
       files = []
       allfiles = os.listdir(root)
       for myfile in allfiles:
          if os.path.splitext(myfile)[1] in ext:
             files.append(os.path.join(root, myfile))
       return files
    
    root = '/Users/ramiro/Desktop/test-TTF'   # ! define your own directory here
    settings = "-v -f latn -n -w gGD "    # ! define your settings for TTFAutohint
    
    new_root = "mkdir " + root +"/Hinted-Fonts"
    os.system(new_root) 
    
    if not os.path.exists(root):
       print 'Not a valid directory'
    else:
       ext = ['.ttf']
       filenames = getFiles(root, ext)
    
    for n in filenames:
    	run_ttfautohint = "ttfautohint "+ settings + n +" "+ n +"-HINTED"
    	move_files = "mv " + root + "/*-HINTED" +" "+ new_root+"/"
    	os.system(run_ttfautohint),os.system(move_files)