# Copyright 2014 Adobe. All rights reserved.

from decimal import Decimal
import functools
import io
import os
import re
import sys

from fontTools.ttLib import TTFont, TTLibError
from fontTools.ttLib.tables.DefaultTable import DefaultTable

from afdko import convertfonttocid, fdkutils, ufotools

# Used for compiling post table of TTF fonts. We need to stay in sync with the
# std names in the fontTools, as we use ttx to import/export the post table.
from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder as kStdNames

__doc__ = """
makeotf.py.
Wrapper for the makeotfexe C program. The C program requires that all the
important input files be explicitly specified on the command-line. This wrapper
supplies default values, and can read values in from a text project file. It
will also use the tx program to convert the input font file to a Type 1 font,
if needed.
"""

__version__ = """\
makeotf.py v2.9.0 June 1 2021
"""

__methods__ = """
Methods:
 - if the -rp option is used without a file name, read input from
   'current.fpr', and always save the final set of options.
 - if the -rp option is used with a file name, use those options,
   but save the final option.
 - save the current options only if requested with -sp, or if the
   file did not exist before.
 - override all options from a project file with any other options
   that are specified.
 - if the input font is not specified, look for, in order, 'font.ufo,
   'font.pfa', and cidfont.ps, all in the current directory.
 - if the FontMenuNameDB file is not specified look in:
      1) the current directory
      2) one up from the current
      3) two up from the current directory
      4) in afdko/resources
 - if the GlyphOrderAndAliasDB file is not specified look in:
      1) the current directory
      2) one up from the current
      3) two up from the current directory
      4) in afdko/resources
 - if the input font is a CID font and the Mac Adobe CMAP files are
   not specified: look in afdko/resources/<R-O-S>.
   Look up the file name based on R-O from a hard-coded table.

Project file
  This is a simple text file of key/value pairs, one pair per line. The
  keys may not contain white-space. The value is the remainder of the
  line. Boolean values are either 'true' or 'false'. Paths are UTF-8
  strings with whitespace removed from both ends. See the definitions
  below for the fields.
"""

__usage__ = __version__ + """
-f <input font>     Specify input font path. Default is to search
                    for 'font.ufo, 'font.pfa', and cidfont.ps, all in the
                    current directory.
                    When a UFO file is specified, makeotf will prefer glyphs
                    from the glyphs.com.adobe.type.processedglyphs layer, which
                    is created by checkoutlinesufo and/or autohint tools.

-o <output font>    Specify output font path. Default is
                    '<PostScript-Name>.otf'. If the path is an existing
                    folder, a default-named font will be saved to it.

-b                  Set style to Bold. Affects style-linking.
                    Default is not Bold

-nb                 Turn off the -b option, if it has previously
                    been turned on.

-i                  Set style to Italic. Affects style-linking.
                    Default is not Italic

-ni                 Turn off the -i option, if it has previously
                    been turned on.

-ff <feature file>  Specify path to feature file. Default is 'features';
                    'features.fea' is also accepted.

-fs                 Make stub GSUB table if there are no glyph substitution
                    rules in the features file.

-mf <FMNDB>         Specify path to FontMenuNameDB file.
                    Default is 'FontMenuNameDB'.

-gf <GOADB>         Specify path to GlyphOrderAndAliasDB file.
                    Default is 'GlyphOrderAndAliasDB'. If this is
                    specified, and the -r or -ga options are NOT
                    specified, the effect is to use the Unicode
                    assignments from the third column of the GOADB
                    file without renaming the glyphs.

-r                  Set release mode. This turns on subroutinization,
                    applies the GlyphOrderAndAliasDB file, and
                    removes the word "Development" from the name
                    table Version (name.ID=5) string.

-nr                 Set the option option -r off, if it has been
                    previously set.

-S                  Turn on subroutinization, a form of
                    compression of the CFF font data.

-nS                 Turn off subroutinization, if it has
                    previously been turned on.

-maxs <number>      Limit the number of subroutines generated by
                    glyph subroutinization to that number. Used for
                    testing with rasterizers that don't process
                    subroutinization correctly.

-ga                 Apply the GlyphOrderAndAliasDB file.

-nga                Turn off the -ga option, if it has previously
                    been turned on.

-gs                 Omit from the font any glyphs not mentioned in the
                    GlyphOrderAndAliasDB file.
                    Only works if -ga or -r is set.

-rev [version]      Without the optional version number, increments the
                    version number by 5. With an integer argument,
                    it increments the minor version by that
                    number. With a fractional argument, it sets
                    the version to the fractional argument; the
                    number must then be decimal with three decimal
                    places, e.g. '1.045', not '1.45'.

-oldNameID4         Set Windows name.ID=4 to the PS name, and Mac
                    name.ID=4 to Preferred Family + space +
                    Preferred Style name, as in all releases of
                    makeotf before Dec 2010.

-newNameID4         Set Windows and Mac name.ID=4 to Preferred
                    Family + space + Preferred Style name, as in
                    the current OpenType spec. This is the default
                    behavior, and is needed only to override a
                    fontinfo or project file setting.

-osbOn <number>     Set a bit to 'on' in the OS/2 table
                    fsSelection field at the specified bit index.

-osbOff <number>    Set a bit to 'off' in the OS/2 table
                    fsSelection field at the specified bit index.
                    Note: if any of the bits 7,8,9 are set on or
                    off, they must all three be explicitly
                    set. See "New OS/2 Bits" in the
                    "MakeOTFUserGuide.pdf" file.

-osv <number>       Set the OS/2 table version to the specified
                    value. Must be greater than 0. If no bits >6
                    are specified for the OS/2 table fsSelection
                    field, then the default version number is 3;
                    else the default is 4.

-addn               Replace the .notdef glyph in the source data
                    (if any) with a standard notdef glyph,
                    matching font weight and width.

-naddn              Turn off the -addn option, if it has previously
                    been turned on.

-adds               If the font is missing any of the Apple Mac
                    Symbol glyphs, create them, matching the font
                    weight and width.
                    Will be disabled for fonts without both a zero
                    and a lowercase 'x'.

-nadds              Turn off the -adds option, if it has previously
                    been turned on.

-serif              Specify that any added glyph use the serif
                    built-in multiple master glyph data.

-sans               Specify that any added glyphs will use the
                    sans-serif built-in multiple master glyph data.

-cs                 Override the heuristics, and specify the
                    ScriptID for the Macintosh 'cmap' subtable.

-cl                 Override the heuristics, and specify the
                    LanguageID for the Macintosh 'cmap' subtable.

-cm <path>          Path to the Macintosh CMap resource; CID-keyed
                    fonts only.

-ch <path>          Path to the UTF-32 CMap resource for horizontal
                    glyphs; CID-keyed fonts only.

-ci <path>          Path to the Unicode Variation Sequences
                    specification file; if present, a 'cmap'
                    subtable will be added with PlatformID = 0,
                    ScriptID = 5, and Format = 14. See MakeOTF User's Guide.

-dbl                For a short list of glyphs, double map them to
                    two Unicode values if there is no other glyph
                    mapped from the same Unicode value. This used to
                    be default behavior for makeotf. The glyph
                    list is as follows:

                    Delta (U+2206, U+0394)
                    Omega (U+2126, U+03A9)
                    Scedilla (U+015E, U+F6C1)
                    Tcommaaccent (U+0162, U+021A)
                    fraction (U+2044, U+2215)
                    hyphen (U+002D, U+00AD)
                    macron (U+00AF, U+02C9)
                    mu (U+00B5, U+03BC)
                    periodcentered (U+00B7, U+2219)
                    scedilla (U+015F, U+F6C2)
                    space (U+0020, U+00A00)
                    tcommaaccent (U+0163, U+021B)

-dcs                Set OS/2.DefaultChar to the Unicode value for
                    'space' rather than '.notdef'. The latter is
                    correct by the OT spec, but QuarkXPress 6.5
                    requires the former in order to print OTF/CFF
                    fonts.

-fi [<path>]        Path to the fontinfo file. If no path is given,
                    the default is to look for first 'fontinfo', then
                    'cidfontinfo', in the current directory. Used
                    to set some default values. This are
                    overridden by any conflicting settings in the
                    project file and then by command-line options.
                    This option is processed before any others, so
                    if the path is relative, it is relative to the
                    current working directory. All other relative
                    paths are relative so the source font's parent
                    directory.

-fp [<path>]        Path to the project file. If no path is given,
                    the default is 'current.fpr'; If used, it sets
                    default options that can be overridden by
                    command-line options.

-sp [<path>]        Save the current options to the project file
                    named in the "-fp" option; not needed unless
                    you specified a name other than 'current.fpr'.
                    If you supply the optional path argument, then
                    options are saved to that file.

-cn                 As a post-processing step, convert the CFF
                    table to a CID-keyed CFF that specifies the
                    Adobe-Identity-0 ROS. The resulting font
                    should be considered experimental, and may not
                    work in some applications.

-ncn                Turn off converting the CFF table to a CID-keyed
                    CFF that specifies the Adobe-Identity-0 ROS;
                    otherwise has no effect.

-shw/-nshw          Show/Hide warnings about unhinted glyphs.
                    This is useful when processing unhinted fonts.

-swo/-nswo          Suppress CFF width optimization (use of defaultWidthX
                    and nominalWidthX). Useful when poking at charstrings
                    with other tools.

-stubCmap4          Build only a "stub" Format 4 'cmap' subtable, with
                    only two segments. This is useful only for special-
                    purpose fonts such as AdobeBlank, whose size is an
                    issue. Windows requires a Format 4 'cmap' subtable
                    to be present, but it is not used.

-omitMacNames       Write only Windows platform menu names in name table,
                    apart from the names specified in the feature file.

-useMacNames        Write Mac platform menu names in name table,
                    as well as Windows names.

-overrideMenuNames  Allow feature file name table entries to override
                    default values and the values from the font menu name DB
                    for name IDs. Name ID's 2 and 6 cannot be overridden.
                    Use this with caution, and make sure you have provided
                    feature file name table entries for all platforms.

-skco/nskco         do/do not suppress kern class optimization by using
                    left side class 0 for non-zero kern values. Optimizing
                    saves a few hundred to thousand bytes, but confuses
                    some programs. Optimizing is the default behavior,
                    and previously was the only option.

-addDSIG,-omitDSIG  Add or omit minimal empty DSIG table. This is added
                    by default in release mode.

-V                  Show warnings about common, but usually not
                    problematic issues, such a glyph being unhinted,
                    or having conflicting GDEF classes because it is used in
                    more than one class type in a layout table. An example
                    is being used as a base glyph in one replacement rule,
                    and as a mark glyph in another.

-v                  Print the tool's version.

-showFinal          In error messages, show glyph final name rather
                    than source name.

Note that options are applied in the order in which they are
specified: "-r -nS" will not subroutinize a font, but "-nS -r" will
subroutinize a font. See the document 'MakeOTFUserGuide.pdf' for the
format and contents of the fontinfo and project files.
"""

__help__ = __usage__ + """

makeotf parts.

makeotf now comes in two parts. One is the C program 'makeotfexe',
which requires that all input files be specified as command-line
parameters. This is the program that reads in all the data, and build
the final OpenType font. You can build OpenType fonts using only this
program.

The other part is the 'makeotf.py' Python script, which is invoked
indirectly by the command-shell script 'makeotf'. The script is a
wrapper for the 'makeotfexe', and does several useful things for you:

- Uses the 'tx' tool to convert the input font into the Unix Type 1
  format that is required by 'makeotfexe'.
- Saves the parameters used for makeotfexe in a project file. This can
  later be referenced by the single option '-fp', which saves a lot of
  typing. It also allows saving standard settings for release end
  development builds.
- verifies that the input files exist. This avoids crashes in
  makeotfexe.


Project files.

Every makeotf is run, it saves the options used to build the font
in a project file named "current.fpr". The font can be rebuilt with
exactly the same options by using the option -fp, or use most of
the options by following '-fp' with additional options to set
different parameters.

Note that if you specify a project file other than "current.fpr", the
final set of parameters used will not be saved to the project file
unless you also specify the "-sp" option.
"""

# REQUIRED PROJECT FILE FIELD NAMES

# path to input font file.
# Example: "font.pfa"
kInputFont = "InputFontPath"
# if not an empty string, sets the font name of the output font.
kOutputFont = "OutputFontPath"
# Boolean: whether Bold style bits should be set
kBold = "IsBoldStyle"
# Boolean: whether Italic style bits should be set.
kItalic = "IsItalicStyle"
# path to feature file
kFeature = "FeaturePath"
kGSUBStub = "AllowStubGSUB"
# path to DB file for font menu names.
# Example: "../../FontMenuNameDB"
kFMB = "FontMenuDBPath"
# path to DB file for glyph order and alias DB file.
# Example: "../../GlyphOrderAndAliasDB"
kGOADB = "GlyphAliasDBPath"
# Boolean: turns on subroutinization and aliasing,
# and omits "Development" from name table Version string
kRelease = "ReleaseMode"
# Boolean: whether or not to subroutinize font.
# Overrides the effect of "ReleaseMode"
kDoSubr = "DoSubroutinization"
# applies the GOADB file order and names.
# Overrides the effect of "ReleaseMode"
kDoAlias = "DoAliasAndOrder"
# skip all glyphs not referenced in the GOADB.
kDoSubset = "DoSubsetByGOADB"
# increments the font OTF version number,
# in the head table and the in name ID 5.
kRenumber = "Renumber"
# set a maximum limit on the number of CFF charstring subroutines.
kMaxSubrs = "MaxSubrs"
# Integer. Takes a numeric value. Sets the bits
# indicated in the OS/2 table fsSelection field.
kSetfsSelectionBitsOn = "kSetfsSelectionBitsOn"
# Integer. Takes a numeric value. Clears the bits
# indicated in the OS/2 table fsSelection field.
# Takes effect after kSetfsSelectionBitsOn.
kSetfsSelectionBitsOff = "kSetfsSelectionBitsOff"
# Integer. Takes a numeric value. If 0, does nothing;
# else sets the OS/2 table version to the value.
kSetOS2Version = "kSetOS2Version"
# Boolean: if true, will replace the original notdef
# with the Adobe standard notdef.
kNotdef = "OverrideNotdef"
# Integer. if not None, will add any missing glyphs from the
# set of Apple symbol glyphs in the MacRoman std glyph set.
# If not zero, it will use the value as the design weight for
# the synthetic glyphs. overriding the built-in heuristics.
kAddSymbol = "AddAppleSymbolGlyphs"
# if true, any added glyphs will be serif.
# Else, they will be sans-serif.
kSerif = "AddedGlyphsAreSerif"
# Integer. Sets the XUID value in the font.
# Default is to provide no XUID value.
kXUID = "XUID"
# Boolean: turns on converting name-keyed
# font to CID with and identity CMAP.
kConvertToCID = "ConvertToCID"
kSuppressHintWarnings = "SuppressHintWarnings"
kSuppressWidthOptimization = "SuppressWidthOptimization"
kStubCmap4 = "StubCmap4"
kSuppressKernOptimization = "SuppressKernOptimization"

# REQUIRED PROJECT FILE FIELD NAMES FOR CID-KEYED FONTS

# Integer. Used to override the heuristics used to determine
# the script id for the cmap table Mac encoding sub-table.
kMacScript = "MacCmapScriptID"
# Integer. Used to override the heuristics used to determine
# the language id for the cmap table Mac encoding sub-table.
kMacLang = "MacCmapLanguageID"
# path to the CID CMAP file which specifies the mapping
# from a Mac encoding to CID values.
kMacCMAPPath = "MacEncodingCMAPPath"
# path to the CID CMAP file which specifies the mapping
# from Unicode UTF-32 encoding to CID values for horizontal glyphs
kHUniCMAPPath = "H_UniEncodingCMAPPath"
# path to the specification file for Unicode variation Sequences.
kUVSPath = "UVSPath"
# InDesign 2 and earlier expected the backtrack sequence in
# contextual lookups to be the reverse of what is no spec'd to be.
kWriteWrongBacktrack = "WriteWrongBackTrack"
kDoubleMapGlyphs = "DoulbeMapGlyphs"
kDefaultCharAsSpace = "DefaultCharAsSpace"
# Use old logic for name ID 4, i.e. what was done
# for name ID 4 for all FDK fonts before 11/2010.
kUseOldNameID4 = "UseOldNameID4"
# omit all Mac platform names from the name table
kOmitMacNames = "OmitMacNames"
kOverrideMenuNames = "OverrideMenuNames"
# Arbitrary string appended at the end of name ID 3.
# Used by Adobe for font license code.
kLicenseCode = "LicenseCode"
# Will look for all.
kDefaultFontPathList = ["font.ufo", "font.pfa", "font.ps", "font.txt"]
kDefaultFeaturesList = ["features", "features.fea"]
kDefaultOptionsFile = "current.fpr"
kDefaultFMNDBPath = "FontMenuNameDB"
GOADB_NAME = "GlyphOrderAndAliasDB"
kAddStubDSIG = "AddStubDSIG"
kShowFinalNames = "ShowFinalNames"
kVerboseWarnings = "VerboseWarnings"
kOptionNotSeen = 99

kMOTFOptions = {
    # key : [argument order as specified by user (kOptionNotSeen
    #        means not specified), option-on, option-off]
    kInputFont: [kOptionNotSeen, "-f", None],
    kOutputFont: [kOptionNotSeen, "-o", None],
    kBold: [kOptionNotSeen, "-b", "-nb"],
    kItalic: [kOptionNotSeen, "-i", "-ni"],
    kFeature: [kOptionNotSeen, "-ff", None],
    kGSUBStub: [kOptionNotSeen, "-fs", "-nfs"],
    kFMB: [kOptionNotSeen, "-mf", None],
    kGOADB: [kOptionNotSeen, "-gf", None],
    kRelease: [kOptionNotSeen, "-r", "-nr"],
    kDoSubr: [kOptionNotSeen, "-S", "-nS"],
    kMaxSubrs: [kOptionNotSeen, "-maxs", None],
    kDoAlias: [kOptionNotSeen, "-ga", "-nga"],
    kDoSubset: [kOptionNotSeen, "-gs", "-ngs"],
    kRenumber: [kOptionNotSeen, "-rev", None],
    kNotdef: [kOptionNotSeen, "-addn", "-naddn"],
    kSetfsSelectionBitsOn: [kOptionNotSeen, "-osbOn", None],
    kSetfsSelectionBitsOff: [kOptionNotSeen, "-osbOff", None],
    kSetOS2Version: [kOptionNotSeen, "-osv", None],
    kAddSymbol: [kOptionNotSeen, "-adds", "-nadds"],
    kSerif: [kOptionNotSeen, "-serif", "-sans"],
    kMacScript: [kOptionNotSeen, "-cs", None],
    kMacLang: [kOptionNotSeen, "-cl", None],
    kMacCMAPPath: [kOptionNotSeen, "-cm", None],
    kHUniCMAPPath: [kOptionNotSeen, "-ch", None],
    kUVSPath: [kOptionNotSeen, "-ci", None],
    kDoubleMapGlyphs: [kOptionNotSeen, "-dbl", None],
    kDefaultCharAsSpace: [kOptionNotSeen, "-dcs", None],
    kUseOldNameID4: [kOptionNotSeen, "-oldNameID4", "-newNameID4"],
    kOmitMacNames: [kOptionNotSeen, "-omitMacNames", "-useMacNames"],
    kOverrideMenuNames: [kOptionNotSeen, "-overrideMenuNames",
                         "-donotOverrideMenuNames"],
    kLicenseCode: [kOptionNotSeen, "-lic", None],
    kWriteWrongBacktrack: [kOptionNotSeen, "-fc", None],
    kConvertToCID: [kOptionNotSeen, "-cn", "-ncn"],
    kSuppressHintWarnings: [kOptionNotSeen, "-shw", "-nshw"],
    kSuppressWidthOptimization: [kOptionNotSeen, "-swo", "-nswo"],
    kStubCmap4: [kOptionNotSeen, "-stubCmap4", None],
    kSuppressKernOptimization: [kOptionNotSeen, "-skco", "-nsko"],
    kAddStubDSIG: [kOptionNotSeen, "-addDSIG", "-omitDSIG"],
    kShowFinalNames: [kOptionNotSeen, "-showFinal", None],
    kVerboseWarnings: [kOptionNotSeen, "-V", "-nV"],
}

# The options which should NOT be passed to
# makeotfexe, as they are for local use only.
kSkipOptions = {
    "": None,
    kRenumber: None,
    kConvertToCID: None,
}

# The paths in this list of keys need to be 'fixed' when written
# to a project file; they need to be converted from relative to
# the current dir to being relative to the fpr directory, and the
# reverse when being read.
kFileOptList = [kInputFont, kOutputFont, kFeature, kFMB, kGOADB, kMacCMAPPath,
                kHUniCMAPPath, kUVSPath]

# The prefix appended to option keys from the options file, so I
# can differentiate the fields in MakeOTFParams which derived from
# kMOTFOptions, and which are defined and used only at run-time.
kFileOptPrefix = "opt_"


class FDKEnvironmentError(Exception):
    pass


class MakeOTFOptionsError(Exception):
    pass


class MakeOTFShellError(Exception):
    pass


class MakeOTFRunError(Exception):
    pass


class MakeOTFParams(object):
    def __init__(self):
        self.makeotfPath = None
        self.txPath = None
        self.currentDir = None
        self.optionFilePath = None
        # option path when different then the input option path.
        self.newOptionFilePath = None
        self.fontinfoPath = None
        self.saveOptions = 'false'
        # default assumption: font home dir is current working directory.
        self.fontDirPath = "."
        # temp T1 font file, made when source is UFO, OTF, TTF, or txt
        self.tempFontPath = None
        # CID Registry, Order, Supplement.
        self.ROS = None
        # for bits 7, 8, 9.
        self.seenOS2v4Bits = [0, 0, 0]
        self.srcIsTTF = 0
        self.srcIsUFO = 0
        self.ufoFMNDBPath = None
        self.ufoGAODBPath = None
        self.verbose = False
        for item in kMOTFOptions.items():
            setattr(self, kFileOptPrefix + item[0], None)
        # USE_TYPO_METRICS Remove comment from next
        # line to turn on bits 7 and 8 by default.
        # setattr(self, kFileOptPrefix + kSetfsSelectionBitsOn, [7,8])

    def __repr__(self):
        text = f'{self.__class__.__name__}:\n'

        for key, val in sorted(vars(self).items()):
            text += f'  {key}: {val}\n'

        return text


def _check_remove_bad_output(outpath):
    """
    Checks for non-existing or "bad" (< 500 bytes) output file and attempts
    to delete.
    """
    if not os.path.exists(outpath) or (os.path.getsize(outpath) < 500):
        print("makeotf [Error] Failed to build output font file '%s'." %
              outpath)
        if os.path.exists(outpath):
            os.remove(outpath)

        raise MakeOTFRunError


def readOptionFile(filePath, makeOTFParams, optionIndex):
    """ Paths in this file are stored relative to it's directory.
    However, we want to set the paths relative to the current directory.
    """
    error = False
    fprDir = os.path.dirname(os.path.abspath(filePath))
    currentDir = makeOTFParams.currentDir
    try:
        with open(filePath, "r", encoding='utf-8') as fp:
            data = fp.read()
    except (OSError):
        print("makeotf [Error] Could not read the options file %s. "
              "Please check the file protection settings." % filePath)
        error = True
        return error, optionIndex

    i = optionIndex
    entries = re.findall(r"[ \t]*(\S+)\s+([^\r\n]+)", data)
    for entry in entries:
        key, value = entry
        value.strip()
        # skip comment lines.
        if key[0] == "#":
            continue
        attrName = kFileOptPrefix + key
        if hasattr(makeOTFParams, attrName):
            kMOTFOptions[key][0] = i
            try:
                value = int(value)
            except ValueError:
                pass
            if key in kFileOptList:
                # Convert fpr path to a relative path if
                # possible, else an absolute path.
                path = os.path.abspath(os.path.join(fprDir, value))
                fileDir, fileName = os.path.split(path)
                relativeDirPath = getRelativeDirPath(
                    fileDir, os.path.abspath(currentDir))
                if relativeDirPath:
                    path = os.path.normpath(os.path.join(relativeDirPath,
                                                         fileName))
                # Now let's see if we can reduce this to
                # a path relative to the current directory.
                value = path
                if key == kInputFont:
                    newFontDirPath = os.path.dirname(path)
                    if newFontDirPath:
                        makeOTFParams.fontDirPath = newFontDirPath

            if key == kSetfsSelectionBitsOn:
                makeOTFParams.seenOS2v4Bits = [1, 1, 1]
                value = str(value)
            setattr(makeOTFParams, attrName, value)
        else:
            print("makeotf [Error] When reading options file, did not "
                  "recognize the keyword %s." % key)
            error = True
        i += 1

    return error, i


def writeOptionsFile(makeOTFParams, filePath):
    """ Note that we have to convert the current options from
    relative to the current directory to relative to the fpr
    file we are writing."""
    fprDir = os.path.dirname(os.path.abspath(filePath))
    currentDir = makeOTFParams.currentDir

    data_list = []
    for field, value in vars(makeOTFParams).items():
        if value is None:
            continue
        if field.startswith(kFileOptPrefix):
            fieldName = field[len(kFileOptPrefix):]
            optionOrder = kMOTFOptions[fieldName][0]

            if fieldName in kFileOptList:
                # Convert fpr path to a relative path
                # if possible, else an absolute path.
                path = os.path.abspath(os.path.join(currentDir, value))
                fileDir, fileName = os.path.split(path)
                relativeDirPath = getRelativeDirPath(fileDir, fprDir)
                if relativeDirPath:
                    path = os.path.normpath(os.path.join(relativeDirPath,
                                                         fileName))
                # Now let's see if we can reduce this to
                # a path relative to the current directory.
                value = path

            data_list.append([optionOrder, "%s\t%s" % (fieldName, value)])

    if data_list:
        # reduce list to just the strings,
        # sorting by order in which the options were added
        data = '\n'.join([entry[1] for entry in sorted(data_list)])
        if makeOTFParams.verbose:
            print("makeotf [Note] Writing options file %s" % filePath)
        try:
            with open(filePath, "w", encoding='utf-8') as fp:
                fp.write(data)
        except (OSError):
            print("makeotf [Warning] Could not write the options file %s. "
                  "Please check the file protection settings for the file "
                  "and for the directory." % filePath)


def saveOptionsFile(makeOTFParams):
    filePath = os.path.join(makeOTFParams.fontDirPath, kDefaultOptionsFile)
    writeOptionsFile(makeOTFParams, filePath)
    if (makeOTFParams.newOptionFilePath is not None and
       makeOTFParams.newOptionFilePath != kDefaultOptionsFile):
        writeOptionsFile(makeOTFParams, makeOTFParams.newOptionFilePath)


def setOptionsFromFontInfo(makeOTFParams):
    """ This sets options from the fontinfo file
        *if they have not already been set *
    """
    fontinfo_path = makeOTFParams.fontinfoPath
    if not fontinfo_path:
        # The user did not specify the fontinfo file
        # path with an option. We will look for it in the
        # font directory, then in the current directory.
        fipath1 = os.path.join(makeOTFParams.fontDirPath, "fontinfo")
        fipath2 = "cidfontinfo"
        fipath3 = os.path.join(makeOTFParams.fontDirPath, fipath2)
        for fiPath in [fipath1, fipath2, fipath3]:
            if os.path.exists(fiPath):
                fontinfo_path = fiPath
                break

    data = ''
    if fontinfo_path and os.path.exists(fontinfo_path):
        try:
            with open(fontinfo_path, "r", encoding='utf-8') as fi:
                data = fi.read()
            makeOTFParams.fontinfoPath = fontinfo_path
        except (OSError):
            print("makeotf [Error] Failed to find and open fontinfo file: "
                  "FSType, OS/2 V4 bits, and Bold and Italic settings may "
                  "not be correct.")
    if not data:
        return

    if kMOTFOptions[kBold][0] == kOptionNotSeen:
        if re.search(r"IsBoldStyle\s+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kBold, 'true')
            print("makeotf [Note] adding the style Bold from fontinfo "
                  "keyword.")

    if kMOTFOptions[kBold][0] == kOptionNotSeen:
        if re.search(r"IsWindowsBold\s+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kBold, 'true')
            print("makeotf [Note] adding the style Bold from fontinfo "
                  "keyword.")

    if kMOTFOptions[kItalic][0] == kOptionNotSeen:
        if re.search(r"IsItalicStyle\s+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kItalic, 'true')
            print("makeotf [Note] adding the style Italic from fontinfo "
                  "keyword.")

    if kMOTFOptions[kConvertToCID][0] == kOptionNotSeen:
        if re.search(r"ConvertToCID\s+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kConvertToCID, 'true')
            print("makeotf [Note] setting output to be CID-keyed from "
                  "fontinfo keyword.")

    if kMOTFOptions[kUseOldNameID4][0] == kOptionNotSeen:
        if re.search(r"UseOldNameID4\s+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kUseOldNameID4, 'true')
            print("makeotf [Note] using old name ID 4 logic.")

    if kMOTFOptions[kOmitMacNames][0] == kOptionNotSeen:
        if re.search(r"OmitMacNames+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kOmitMacNames, 'true')
            print("makeotf [Note] omitting Mac platform names from the "
                  "name table.")

    if kMOTFOptions[kOverrideMenuNames][0] == kOptionNotSeen:
        if re.search(r"OverrideMenuNames+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kOverrideMenuNames, 'true')
            print("makeotf [Note] allowing overrides of font menu names in "
                  "name table.")

    if kMOTFOptions[kSuppressKernOptimization][0] == kOptionNotSeen:
        if re.search(r"SuppressKernOptimization+([Tt]rue|1)", data):
            setattr(makeOTFParams,
                    kFileOptPrefix + kSuppressKernOptimization,
                    'true')
            print("makeotf [Note] Suppressing Kern Optimization.")

    if kMOTFOptions[kAddStubDSIG][0] == kOptionNotSeen:
        if re.search(r"AddStubDSIG+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kAddStubDSIG, 'true')

    if kMOTFOptions[kVerboseWarnings][0] == kOptionNotSeen:
        if re.search(r"kVerboseWarnings+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kVerboseWarnings, 'true')
            makeOTFParams.verbose = True

    if kMOTFOptions[kLicenseCode][0] == kOptionNotSeen:
        m = re.search(r"LicenseCode\s+([^\r\n]+)", data)
        if m:
            setattr(makeOTFParams,
                    kFileOptPrefix + kLicenseCode,
                    str(m.group(1)))

    if kMOTFOptions[kSuppressHintWarnings][0] == kOptionNotSeen:
        if re.search(r"SuppressHintWarnings\s+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kSuppressHintWarnings,
                    'true')

    if kMOTFOptions[kSuppressWidthOptimization][0] == kOptionNotSeen:
        if re.search(r"SuppressWidthOptimization\s+([Tt]rue|1)", data):
            setattr(makeOTFParams,
                    kFileOptPrefix + kSuppressWidthOptimization,
                    'true')

    if kMOTFOptions[kStubCmap4][0] == kOptionNotSeen:
        if re.search(r"stubCmap4\s+([Tt]rue|1)", data):
            setattr(makeOTFParams, kFileOptPrefix + kStubCmap4, 'true')

    if kMOTFOptions[kMacScript][0] == kOptionNotSeen:
        m = re.search(r"Language\s+\(([^)]+)\)", data)
        if m:
            lang = m.group(1).strip()
            if lang == "Japanese":
                script = '1'
            elif lang == "Chinese":
                script = '2'
            elif lang == "TraditionalChinese":
                script = '25'
            elif lang == "Korean":
                script = '3'
            else:
                script = None
            if script is not None:
                setattr(makeOTFParams, kFileOptPrefix + kMacScript, script)

    if kMOTFOptions[kSetfsSelectionBitsOn][0] == kOptionNotSeen:
        m = re.search(r"PreferOS/2TypoMetrics"
                      r"\s+(([Tt]rue|1)|([Ff]alse)|0|1)", data)
        if m:
            makeOTFParams.seenOS2v4Bits[0] = 1
            if m.group(1) in ["True", "true", "1"]:
                val = '7'
                bitsOn = getattr(makeOTFParams,
                                 kFileOptPrefix + kSetfsSelectionBitsOn)
                if bitsOn is None:
                    setattr(makeOTFParams,
                            kFileOptPrefix + kSetfsSelectionBitsOn, [val])
                else:
                    setattr(makeOTFParams,
                            kFileOptPrefix + kSetfsSelectionBitsOn,
                            bitsOn + [val])
            elif not m.group(1) in ["False", "false", "0"]:
                print("makeotf [Error] Failed to parse value for "
                      "PreferOS/2TypoMetrics in file '%s'." % fontinfo_path)
            print("makeotf [Note] setting the USE_TYPO_METRICS OS/2 "
                  "fsSelection bit 7 from fontinfo keyword.")

        m = re.search(r"IsOS/2WidthWeightSlopeOnly"
                      r"\s+(([Tt]rue|1)|([Ff]alse)|0|1)", data)
        if not m:
            m = re.search(r"IsOS/2WidthWeigthSlopeOnly"
                          r"\s+(([Tt]rue|1)|([Ff]alse)|0|1)", data)

        if m:
            makeOTFParams.seenOS2v4Bits[1] = 1
            if m.group(1) in ["True", "true", "1"]:
                val = '8'
                bitsOn = getattr(makeOTFParams,
                                 kFileOptPrefix + kSetfsSelectionBitsOn)
                if bitsOn is None:
                    setattr(makeOTFParams,
                            kFileOptPrefix + kSetfsSelectionBitsOn, [val])
                else:
                    setattr(makeOTFParams,
                            kFileOptPrefix + kSetfsSelectionBitsOn,
                            bitsOn + [val])
            elif not m.group(1) in ["False", "false", "0"]:
                print("makeotf [Error] Failed to parse value for "
                      "PreferOS/2TypoMetrics in file '%s'." % fontinfo_path)
            print("makeotf [Note] setting the WEIGHT_WIDTH_SLOPE_ONLY OS/2 "
                  "fsSelection bit 8 from fontinfo keyword.")

        m = re.search(r"IsOS/2OBLIQUE\s+(([Tt]rue|1)|([Ff]alse)|0|1)", data)
        if m:
            makeOTFParams.seenOS2v4Bits[2] = 1
            if m.group(1) in ["True", "true", "1"]:
                val = '9'
                bitsOn = getattr(makeOTFParams,
                                 kFileOptPrefix + kSetfsSelectionBitsOn)
                if bitsOn is None:
                    setattr(makeOTFParams,
                            kFileOptPrefix + kSetfsSelectionBitsOn, [val])
                else:
                    setattr(makeOTFParams,
                            kFileOptPrefix + kSetfsSelectionBitsOn,
                            bitsOn + [val])
            elif not m.group(1) in ["False", "false", "0"]:
                print("makeotf [Error] Failed to parse value for "
                      "PreferOS/2TypoMetrics in file '%s'." % fontinfo_path)
            print("makeotf [Note] setting the OBLIQUE OS/2 fsSelection bit 9 "
                  "from fontinfo keyword.")

    # For historical reasons: if fsType is set here, save it and
    # compare with the value in the features file.
    # Use of FSType here is deprecated; the value here will be ignored.
    m = re.search(r"FSType\s+(\S+)", data)
    if m:
        try:
            val = str(m.group(1))
            makeOTFParams.FSType = val
        except (NameError, SyntaxError):
            print("makeotf [Error] Failed to parse value '%s' for FSType in "
                  "file '%s'." % (m.group(1), fontinfo_path))


def getRelativeDirPath(absPath1, abspath2):
    """
    Return a relative path to the first dir from the second directory.
    If the paths have no common prefix, return None
    """
    norm_paths = [os.path.normpath(os.path.realpath(p)) + os.path.sep
                  for p in (absPath1, abspath2)]

    root = os.path.dirname(norm_paths[0])
    while os.path.basename(root):
        root = os.path.dirname(root)

    if os.path.dirname(os.path.commonprefix(norm_paths)) == root:
        return None
    else:
        return os.path.normpath(os.path.relpath(absPath1, abspath2))


def getOptions(makeOTFParams, args):
    """
    Fill in the makeotf parameters from first the defaults,
    then the project file, and finally any other options.
    We require that the -fp option, if used, be the first;
    that way the project file is read in before any other
    options are applied.

    All file paths are taken to be relative to the current
    working directory. However, relative paths saved in a
    project file are relative to the project file location.
    """
    numArgs = len(args)
    i = 0
    error = False
    # We use this to allow for the indices used
    # when reading the options file.
    optionIndex = 0
    if "-v" in args:
        print(__version__)
        sys.exit()

    if "-h" in args:
        print(__help__)
        sys.exit()

    if "-u" in args:
        print(__usage__)
        sys.exit()

    # Get the absolute path to the current directory.
    makeOTFParams.currentDir = os.getcwd()
    # makeOTFParams.fontDirPath is currently set to "."

    while i < numArgs:
        arg = args[i]
        i += 1

        if arg == "-sp":
            makeOTFParams.saveOptions = 'true'
            try:
                file_path = args[i]
                if file_path[0] == "-":
                    # its another option.
                    continue
            except IndexError:
                continue
            i += 1
            makeOTFParams.newOptionFilePath = file_path

        elif arg == "-fi":
            try:
                file_path = os.path.abspath(os.path.realpath(args[i]))
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by "
                      "the path to the fontinfo file." % arg)
                continue
            i += 1

            if not os.path.exists(file_path):
                error = 1
                print("makeotf [Error] Could not find the fontinfo file at "
                      "'%s'." % file_path)
                continue
            makeOTFParams.fontinfoPath = file_path

        elif arg == "-fp":
            if i > 1:
                print("makeotf [Warning] Any options specified before '-fp' "
                      "may be overridden by settings in the file.")
            try:
                opt_path = args[i]
                # if the next argument in not another option,
                # then it is the path to the options file.
                if not opt_path[0] == "-":
                    i += 1
                else:
                    opt_path = kDefaultOptionsFile
            # there is no argument after -fp
            except IndexError:
                opt_path = kDefaultOptionsFile

            if not os.path.exists(opt_path):
                print("makeotf [Warning] Could not find the options file at "
                      "'%s'." % opt_path)
                error = 1
                continue

            error, optionIndex = readOptionFile(opt_path, makeOTFParams, i)
            makeOTFParams.optionFilePath = opt_path
            if not makeOTFParams.newOptionFilePath:
                makeOTFParams.newOptionFilePath = opt_path

        elif arg == kMOTFOptions[kInputFont][1]:
            kMOTFOptions[kInputFont][0] = i + optionIndex
            try:
                file_path = os.path.abspath(os.path.realpath(args[i]))
                setattr(makeOTFParams, kFileOptPrefix + kInputFont, file_path)
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by "
                      "the path to the input file." % arg)
                continue
            i += 1

            if not os.path.exists(file_path):
                error = 1
                print("makeotf [Error] Could not find the input font file at "
                      "'%s'." % file_path)
                continue

        elif arg == kMOTFOptions[kOutputFont][1]:
            kMOTFOptions[kOutputFont][0] = i + optionIndex
            try:
                file_path = os.path.abspath(os.path.realpath(args[i]))
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed the "
                      "path to the output font file." % arg)
                continue
            i += 1
            setattr(makeOTFParams, kFileOptPrefix + kOutputFont, file_path)

        elif arg == kMOTFOptions[kBold][1]:
            kMOTFOptions[kBold][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kBold, 'true')

        # makeotfexe does not have a not-bold option;
        # we need to just suppress the bold option.
        elif arg == kMOTFOptions[kBold][2]:
            kMOTFOptions[kBold][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kBold, None)

        elif arg == kMOTFOptions[kItalic][1]:
            kMOTFOptions[kItalic][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kItalic, 'true')

        # makeotfexe does not have a not-italic option;
        # we need to just suppress the italic option.
        elif arg == kMOTFOptions[kItalic][2]:
            kMOTFOptions[kItalic][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kItalic, None)

        elif arg == kMOTFOptions[kFeature][1]:
            kMOTFOptions[kFeature][0] = i + optionIndex
            try:
                file_path = args[i]
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed the "
                      "path to the features file." % arg)
                continue
            i += 1
            if not os.path.exists(file_path):
                error = 1
                print("makeotf [Error] Could not find the features file at "
                      "'%s'." % file_path)
                continue
            setattr(makeOTFParams, kFileOptPrefix + kFeature, file_path)

        # makeotfexe does not have a no-stub-GSUB option;
        # we need to just suppress the stub-GSUB option.
        elif arg == kMOTFOptions[kGSUBStub][1]:
            kMOTFOptions[kGSUBStub][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kGSUBStub, 'true')

        # makeotfexe does not have a no-stub-GSUB option;
        # we need to just suppress the stub-GSUB option.
        elif arg == kMOTFOptions[kGSUBStub][2]:
            kMOTFOptions[kGSUBStub][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kGSUBStub, None)

        elif arg == kMOTFOptions[kFMB][1]:
            kMOTFOptions[kFMB][0] = i + optionIndex
            try:
                file_path = args[i]
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by "
                      "the path to the FontMenuNameDB file." % arg)
                continue
            i += 1
            if file_path == "None":
                setattr(makeOTFParams, kFileOptPrefix + kFMB, file_path)
            else:
                if not os.path.exists(file_path):
                    error = 1
                    print("makeotf [Error] Could not find the FontMenuNameDB "
                          "file at '%s'." % file_path)
                    continue
                setattr(makeOTFParams, kFileOptPrefix + kFMB, file_path)

        # -gf
        elif arg == kMOTFOptions[kGOADB][1]:
            kMOTFOptions[kGOADB][0] = i + optionIndex
            try:
                file_path = args[i]
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by "
                      "the path to the %s file." % (arg, GOADB_NAME))
                continue
            i += 1
            if not os.path.exists(file_path):
                error = 1
                print("makeotf [Error] Could not find the "
                      "%s at '%s'." % (GOADB_NAME, file_path))
                continue
            setattr(makeOTFParams, kFileOptPrefix + kGOADB, file_path)

        # -r
        elif arg == kMOTFOptions[kRelease][1]:
            kMOTFOptions[kRelease][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kRelease, 'true')
            # If the -r option is specified, we don't want
            # the -q and -S options showing up unless the
            # user specified them later.
            setattr(makeOTFParams, kFileOptPrefix + kDoSubr, None)
            setattr(makeOTFParams, kFileOptPrefix + kDoAlias, None)

        # -nr
        # makeotfexe does not have a not-release option;
        # we need to suppress the release option.
        elif arg == kMOTFOptions[kRelease][2]:
            kMOTFOptions[kRelease][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kRelease, None)
            # If the -r option is specified being off,
            # we also turn off the -q and -S options.
            kMOTFOptions[kDoSubr][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kDoSubr, None)
            kMOTFOptions[kDoAlias][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kDoAlias, None)
            kMOTFOptions[kGOADB][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kGOADB, None)

        elif arg == kMOTFOptions[kDoSubr][1]:
            kMOTFOptions[kDoSubr][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kDoSubr, 'true')

        elif arg == kMOTFOptions[kDoSubr][2]:
            kMOTFOptions[kDoSubr][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kDoSubr, 'false')

        elif arg == kMOTFOptions[kMaxSubrs][1]:
            try:
                val = int(args[i])
                i += 1
                setattr(makeOTFParams, kFileOptPrefix + kMaxSubrs, val)
            except (IndexError, ValueError):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by an "
                      "integer value." % arg)

        # -ga
        elif arg == kMOTFOptions[kDoAlias][1]:
            if kMOTFOptions[kDoAlias][0] == kOptionNotSeen:
                kMOTFOptions[kDoAlias][0] = i + optionIndex
                setattr(makeOTFParams, kFileOptPrefix + kDoAlias, 'true')

        # -nga
        elif arg == kMOTFOptions[kDoAlias][2]:
            if kMOTFOptions[kDoAlias][0] == kOptionNotSeen:
                kMOTFOptions[kDoAlias][0] = i + optionIndex
                setattr(makeOTFParams, kFileOptPrefix + kDoAlias, 'false')
                kMOTFOptions[kGOADB][0] = i + optionIndex
                setattr(makeOTFParams, kFileOptPrefix + kGOADB, None)

        # -gs
        elif arg == kMOTFOptions[kDoSubset][1]:
            kMOTFOptions[kDoSubset][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kDoSubset, 'true')

        # -ngs
        elif arg == kMOTFOptions[kDoSubset][2]:
            setattr(makeOTFParams, kFileOptPrefix + kDoSubset, 'false')

        elif arg == kMOTFOptions[kRenumber][1]:
            try:
                fontRevisionString = args[i]
                if fontRevisionString[0] == "-":
                    fontRevisionString = "5"
                else:
                    i += 1
            except IndexError:
                fontRevisionString = "5"

            # It is not another option. Must be a valid number
            formatOK = False
            frs_parts = fontRevisionString.split(".")
            formatOK = frs_parts[0].isnumeric()

            if formatOK and len(frs_parts) == 2:
                frac_part = frs_parts[1]
                formatOK = len(frac_part) == 3 and frac_part.isnumeric()

            if not formatOK:
                print("makeotf [Error] The '%s' option must be followed by an "
                      "integer or a fractional value with three decimal "
                      "places, e.g. 1.001." % arg)
                error = 1
                continue

            kMOTFOptions[kRenumber][0] = i + optionIndex
            setattr(makeOTFParams,
                    kFileOptPrefix + kRenumber,
                    fontRevisionString)

        elif arg == kMOTFOptions[kNotdef][1]:
            kMOTFOptions[kNotdef][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kNotdef, 'true')

        # makeotfexe does not have a "don't make notdef"
        # option; we need to just suppress it.
        elif arg == kMOTFOptions[kNotdef][2]:
            kMOTFOptions[kNotdef][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kNotdef, None)

        elif arg == kMOTFOptions[kSetfsSelectionBitsOn][1]:
            kMOTFOptions[kSetfsSelectionBitsOn][0] = i + optionIndex
            try:
                val = int(args[i])
                i += 1
                if val in [7, 8, 9]:
                    makeOTFParams.seenOS2v4Bits[val - 7] = 1

            except (IndexError, ValueError):
                print("makeotf [Error] The '%s' option must be followed by an "
                      "integer between 0 and 15." % arg)
                error = 1
                val = 0
            bitsOn = getattr(makeOTFParams,
                             kFileOptPrefix + kSetfsSelectionBitsOn)
            if bitsOn is None:
                setattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOn,
                        [str(val)])
            else:
                setattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOn,
                        bitsOn + [str(val)])

        elif arg == kMOTFOptions[kSetfsSelectionBitsOff][1]:
            kMOTFOptions[kSetfsSelectionBitsOff][0] = i + optionIndex
            try:
                val = int(args[i])
                i += 1
                if val in [7, 8, 9]:
                    makeOTFParams.seenOS2v4Bits[val - 7] = 1
            except (IndexError, ValueError):
                print("makeotf [Error] The '%s' option must be followed by an "
                      "integer between 0 and 15." % arg)
                error = 1
                val = 0
            bitsOff = getattr(makeOTFParams,
                              kFileOptPrefix + kSetfsSelectionBitsOff)
            if bitsOff is None:
                setattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOff,
                        [str(val)])
            else:
                setattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOff,
                        bitsOff + [str(val)])

        elif arg == kMOTFOptions[kSetOS2Version][1]:
            kMOTFOptions[kSetOS2Version][0] = i + optionIndex
            try:
                val = int(args[i])
                i += 1
            except (IndexError, ValueError):
                print("makeotf [Error] The '%s' option must be followed by "
                      "an integer greater than 0." % arg)
                error = 1
                val = 0
            setattr(makeOTFParams, kFileOptPrefix + kSetOS2Version, str(val))

        elif arg == kMOTFOptions[kAddSymbol][1]:
            # If the next argument is a valid integer, use it as
            # the override for the design weight of the synthetic glyphs.
            kMOTFOptions[kAddSymbol][0] = i + optionIndex
            try:
                val = int(args[i])
                i += 1
            except (IndexError, ValueError):
                val = 'true'
            setattr(makeOTFParams, kFileOptPrefix + kAddSymbol, str(val))

        elif arg == kMOTFOptions[kAddSymbol][2]:
            kMOTFOptions[kAddSymbol][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kAddSymbol, 'false')

        # For -serif and -sans, the meaning is to override the heuristics;
        # makeOTFParams..kSerif ==== None is different than false; the latter
        # means make it sans, the former means 'depend on the heuristics.
        elif arg == kMOTFOptions[kSerif][1]:
            kMOTFOptions[kSerif][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kSerif, 'true')

        elif arg == kMOTFOptions[kSerif][2]:
            kMOTFOptions[kSerif][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kSerif, 'false')

        elif arg == kMOTFOptions[kMacScript][1]:
            # The next arg needs to a valid integer
            kMOTFOptions[kMacScript][0] = i + optionIndex
            try:
                val = int(args[i])
                i += 1
                setattr(makeOTFParams, kFileOptPrefix + kMacScript, str(val))
            except (IndexError, ValueError):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by a "
                      "valid Mac script ID value." % arg)

        elif arg == kMOTFOptions[kMacLang][1]:
            kMOTFOptions[kMacLang][0] = i + optionIndex
            # The next arg needs to a valid integer
            try:
                val = int(args[i])
                i += 1
                setattr(makeOTFParams, kFileOptPrefix + kMacLang, str(val))
            except (IndexError, ValueError):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by a "
                      "valid Mac language ID value." % arg)

        elif arg == kMOTFOptions[kMacCMAPPath][1]:
            kMOTFOptions[kMacCMAPPath][0] = i + optionIndex
            try:
                file_path = args[i]
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by "
                      "the path to the CID CMAP Mac encoding file." % arg)
                continue
            i += 1
            if not os.path.exists(file_path):
                error = 1
                print("makeotf [Error] Could not find the CID CMAP Mac "
                      "encoding file at '%s'." % file_path)
                continue
            setattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath, file_path)

        elif arg == kMOTFOptions[kHUniCMAPPath][1]:
            kMOTFOptions[kHUniCMAPPath][0] = i + optionIndex
            try:
                file_path = args[i]
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by "
                      "the path to the CID CMAP horizontal Unicode encoding "
                      "file." % arg)
                continue
            i += 1
            if not os.path.exists(file_path):
                error = 1
                print("makeotf [Error] Could not find the CID CMAP horizontal "
                      "Unicode encoding file at '%s'." % file_path)
                continue
            setattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath, file_path)

        elif arg == kMOTFOptions[kUVSPath][1]:
            kMOTFOptions[kUVSPath][0] = i + optionIndex
            try:
                file_path = args[i]
            except IndexError:
                file_path = None
            if (file_path is None) or (file_path[0] == "-"):
                error = 1
                print("makeotf [Error] The '%s' option must be followed by "
                      "the path to the Unicode Variation Sequence "
                      "specification file." % arg)
                continue
            i += 1
            if not os.path.exists(file_path):
                error = 1
                print("makeotf [Error] Could not find the Unicode Variation "
                      "Sequence specification file at '%s'." % file_path)
                continue
            setattr(makeOTFParams, kFileOptPrefix + kUVSPath, file_path)

        elif arg == kMOTFOptions[kWriteWrongBacktrack][1]:
            kMOTFOptions[kWriteWrongBacktrack][0] = i + optionIndex
            setattr(makeOTFParams,
                    kFileOptPrefix + kWriteWrongBacktrack,
                    'true')

        elif arg == kMOTFOptions[kDoubleMapGlyphs][1]:
            kMOTFOptions[kDoubleMapGlyphs][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kDoubleMapGlyphs, 'true')

        elif arg == kMOTFOptions[kDefaultCharAsSpace][1]:
            kMOTFOptions[kDefaultCharAsSpace][0] = i + optionIndex
            setattr(makeOTFParams,
                    kFileOptPrefix + kDefaultCharAsSpace,
                    'true')

        elif arg == kMOTFOptions[kUseOldNameID4][1]:
            kMOTFOptions[kUseOldNameID4][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kUseOldNameID4, 'true')

        elif arg == kMOTFOptions[kUseOldNameID4][2]:
            kMOTFOptions[kUseOldNameID4][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kUseOldNameID4, None)

        elif arg == kMOTFOptions[kOmitMacNames][1]:
            kMOTFOptions[kOmitMacNames][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kOmitMacNames, 'true')

        elif arg == kMOTFOptions[kOmitMacNames][2]:
            kMOTFOptions[kOmitMacNames][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kOmitMacNames, None)

        elif arg == kMOTFOptions[kOverrideMenuNames][1]:
            kMOTFOptions[kOverrideMenuNames][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kOverrideMenuNames, 'true')

        elif arg == kMOTFOptions[kOverrideMenuNames][2]:
            kMOTFOptions[kOverrideMenuNames][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kOverrideMenuNames, None)

        elif arg == kMOTFOptions[kSuppressKernOptimization][1]:
            kMOTFOptions[kSuppressKernOptimization][0] = i + optionIndex
            setattr(makeOTFParams,
                    kFileOptPrefix + kSuppressKernOptimization,
                    'true')

        elif arg == kMOTFOptions[kSuppressKernOptimization][2]:
            kMOTFOptions[kSuppressKernOptimization][0] = i + optionIndex
            setattr(makeOTFParams,
                    kFileOptPrefix + kSuppressKernOptimization,
                    None)

        elif arg == kMOTFOptions[kLicenseCode][1]:
            kMOTFOptions[kLicenseCode][0] = i + optionIndex
            try:
                licenseCode = args[i]
            except IndexError:
                licenseCode = None
            setattr(makeOTFParams, kFileOptPrefix + licenseCode, licenseCode)
            i += 1

        elif arg == kMOTFOptions[kConvertToCID][1]:
            kMOTFOptions[kConvertToCID][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kConvertToCID, 'true')

        # makeotfexe does not have a not-release option;
        # we need to suppress the release option.
        elif arg == kMOTFOptions[kConvertToCID][2]:
            kMOTFOptions[kConvertToCID][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kConvertToCID, 'false')

        # -shw
        elif arg == kMOTFOptions[kSuppressHintWarnings][1]:
            kMOTFOptions[kSuppressHintWarnings][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kSuppressHintWarnings,
                    None)
        # -nshw
        elif arg == kMOTFOptions[kSuppressHintWarnings][2]:
            kMOTFOptions[kSuppressHintWarnings][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kSuppressHintWarnings,
                    'true')

        elif arg == kMOTFOptions[kSuppressWidthOptimization][1]:
            kMOTFOptions[kSuppressWidthOptimization][0] = i + optionIndex
            setattr(makeOTFParams,
                    kFileOptPrefix + kSuppressWidthOptimization,
                    'true')

        elif arg == kMOTFOptions[kSuppressWidthOptimization][2]:
            kMOTFOptions[kSuppressWidthOptimization][0] = i + optionIndex
            setattr(makeOTFParams,
                    kFileOptPrefix + kSuppressWidthOptimization,
                    None)

        elif arg == kMOTFOptions[kStubCmap4][1]:
            kMOTFOptions[kStubCmap4][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kStubCmap4, 'true')

        elif arg == kMOTFOptions[kAddStubDSIG][1]:
            kMOTFOptions[kAddStubDSIG][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kAddStubDSIG, 'true')

        elif arg == kMOTFOptions[kAddStubDSIG][2]:
            kMOTFOptions[kAddStubDSIG][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kAddStubDSIG, 'false')

        elif arg == kMOTFOptions[kShowFinalNames][1]:
            kMOTFOptions[kShowFinalNames][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kShowFinalNames, 'true')

        elif arg == kMOTFOptions[kVerboseWarnings][1]:
            kMOTFOptions[kVerboseWarnings][0] = i + optionIndex
            setattr(makeOTFParams, kFileOptPrefix + kVerboseWarnings, 'true')
            makeOTFParams.verbose = True

        elif arg == kMOTFOptions[kVerboseWarnings][2]:
            kMOTFOptions[kVerboseWarnings][0] = None
            setattr(makeOTFParams, kFileOptPrefix + kVerboseWarnings, None)
            makeOTFParams.verbose = False

        else:
            error = 1
            print("makeotf [Error] Did not recognize option '%s'." % arg)

    if error:
        raise MakeOTFOptionsError

    checkInputFile(makeOTFParams)


def checkInputFile(makeOTFParams):
    # The path to the original src font file
    inputFilePath = getattr(makeOTFParams, kFileOptPrefix + kInputFont)
    # path to font file was not specified.
    if not inputFilePath:
        return

    file_path = inputFilePath
    newFontDirPath = os.path.dirname(file_path)
    if newFontDirPath:
        makeOTFParams.fontDirPath = newFontDirPath
    setattr(makeOTFParams, kFileOptPrefix + kInputFont, file_path)

    convertFontIfNeeded(makeOTFParams)

    # If we need to convert the original source,
    # makeOTFParams.tempFontPath = fontPath = the path to the converted data,
    # while file_path = "makeOTFParams.%s%s = file_path" % (kFileOptPrefix,
    # kInputFont)) = path to original source.
    if makeOTFParams.tempFontPath:
        fontPath = makeOTFParams.tempFontPath
    else:
        fontPath = file_path

    # For non CID fonts, you can refer to the font.pfa file by a path,
    # and all other files will be found relative to the font's parent
    # directory. However, with CID-keyed fonts at Adobe, the font file
    # is usually kept a level or two down from the face source data
    # and we can only assume that the current directory is the starting
    # point for all relative paths.
    Reg, Ord, Sup = getROS(fontPath)
    if Reg:
        makeOTFParams.ROS = (Reg, Ord, Sup)
        # Since we have the ROS, let's set default Mac Script value.
        script = getattr(makeOTFParams, kFileOptPrefix + kMacScript)
        if not script:
            if Ord.startswith("Japan"):
                script = "1"
            elif Ord.startswith("CN"):
                script = "2"
            elif Ord.startswith("GB"):
                script = "25"
            elif Ord.startswith("Korea"):
                script = "3"
            else:
                print("makeotf [Warning] Did not recognize the ordering '%s'. "
                      "The Mac cmap script id will be set to Roman (0)." % Ord)
            setattr(makeOTFParams, kFileOptPrefix + kMacScript, script)


def lookUpDirTree(fileName):
    """
    This is called when we are using a default name for either the
    FontMenuNameDB or the GlyphOrderAndAliasDB files.
    These are often located several dir levels above the font file,
    as they are shared by the font family.
    """

    MAX_LEVELS = 4
    path = None
    i = 0
    dirPath, fileName = os.path.split(fileName)
    while i <= MAX_LEVELS:
        path = os.path.join(dirPath, fileName)
        if os.path.exists(path):
            return path
        dirPath = os.path.join(dirPath, "..")
        i += 1
    return path


def getROS(fontPath):
    """
    Check to see if font is a CID-keyed font by
    looking for the keys Registry, Ordering, Supplement.
    We are looking for the text:
      /Registry (Adobe)  def
      /Ordering (Japan1) def
      /Supplement 3 def

    """
    Reg = Ord = Sup = None
    with open(fontPath, "r", encoding='macroman') as fp:
        data = fp.read(5000)
    match = re.search(r"/Registry\s+\((\S+)\)", data)
    if not match:
        return Reg, Ord, Sup
    Reg = match.group(1)
    data = data[match.end():match.end() + 200]

    match = re.search(r"/Ordering\s+\((\S+)\)", data)
    if not match:
        print("makeotf [Error] found /Registry but not /Ordering in CID font.")
        Reg = None
        return Reg, Ord, Sup
    Ord = match.group(1)

    match = re.search(r"/Supplement\s+(\d+)", data)
    if not match:
        print("makeotf [Error] found /Registry but not /Supplement in CID "
              "font.")
        Reg = None
        return Reg, Ord, Sup
    Sup = match.group(1)

    return Reg, Ord, Sup


def setCIDCMAPPaths(makeOTFParams, Reg, Ord, Sup):
    """ This is called when one or more of the three Adobe CMAP files
    wanted for CID fonts have not been specified on the command-line.
    Only the Uni-H file is required.
    """
    error = False

    cmapPath = fdkutils.get_resources_dir()
    ROPath = os.path.join(cmapPath, "%s-%s" % (Reg, Ord))
    ROSPath = "%s-%s" % (ROPath, Sup)
    foundIt = False

    if os.path.exists(ROPath):
        foundIt = True
        ROSPath = ROPath
    elif os.path.exists(ROSPath):
        foundIt = True
    # Try counting up to 9.
    else:
        for s in range(int(Sup) + 1, 9 + 1):
            ROSPath = "%s-%s" % (ROPath, s)
            if os.path.exists(ROSPath):
                foundIt = True
                break

    macFilePath = getattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath)
    uniHFilePath = getattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath)
    uvsFilePath = getattr(makeOTFParams, kFileOptPrefix + kUVSPath)
    if foundIt:
        for file_name in sorted(os.listdir(ROSPath)):
            if (not uniHFilePath and file_name.startswith("Uni") and
               file_name.endswith("UTF32-H")):
                uniHFilePath = os.path.join(ROSPath, file_name)
                setattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath,
                        uniHFilePath)
            elif not uvsFilePath and file_name.endswith("sequences.txt"):
                uvsName = "%s-%s_%s" % (Reg, Ord, "sequences.txt")
                if uvsName == file_name:
                    uvsFilePath = os.path.join(ROSPath, file_name)
                    setattr(makeOTFParams, kFileOptPrefix + kUVSPath,
                            uvsFilePath)
                else:
                    print("makeotf [Note] Found UVS file '%s' that doesn't "
                          "match expected name '%s'." % (file_name, uvsName))
            elif not macFilePath and file_name.endswith("-H"):
                if Reg == "Adobe":
                    if (Ord == "Japan1" and (file_name == "83pv-RKSJ-H")) or \
                       (Ord == "GB1" and (file_name == "GBpc-EUC-H")) or \
                       (Ord == "CNS1" and (file_name == "B5pc-H")) or \
                       (Ord == "Korea1" and (file_name == "KSCpc-EUC-H")):
                        macFilePath = os.path.join(ROSPath, file_name)
                        setattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath,
                                macFilePath)

    if not uniHFilePath:
        print("makeotf [Error] Could not find an Adobe CMAP Unicode mapping "
              "file. Please specify one with the '-ch' option.")
        error = True

    if not macFilePath:
        print("makeotf [Warning] Could not find an Adobe CMAP Mac encoding "
              "mapping file. If you want a Mac cmap subtable, please specify "
              "the Mac CMAP encoding file with the '-cm' option.")

    if not uvsFilePath:
        print(f"makeotf [Warning] Could not find a Unicode Variation Sequence "
              f"file in the expected '{cmapPath}' subdirectory.")

    return error


RE_FEATURE = re.compile(r"^(?!#)\s*feature\s+vert\s*{", re.MULTILINE)
RE_INCLUDE = re.compile(r"^(?!#)\s*include\s*\(\s*([^ )]+)\s*\)", re.MULTILINE)


def checkIfVertInFeature(featurePath):
    # report that vert CMAP is needed to synthesize
    # the vert feature, if the feature file exists
    # and does not contain a definition of the vert feature.
    foundVert = False

    if not (featurePath and os.path.exists(featurePath)):
        return foundVert

    with open(featurePath, "r", encoding='utf-8') as fp:
        data = fp.read()

    match = re.search(RE_FEATURE, data)
    if match:
        return True

    includeList = re.findall(RE_INCLUDE, data)
    for _file in includeList:
        # First, look for include files relative to parent feature file
        fdir = os.path.dirname(featurePath)
        apath = os.path.join(fdir, _file)
        if not os.path.exists(apath):
            # Second, look for include files relative to working directory.
            apath = os.path.abspath(_file)
            if not os.path.exists(apath):
                print("makeotf [Error] Could not find the include file '%s', "
                      "referenced in '%s'." % (_file, featurePath))
                return False
        foundVert = checkIfVertInFeature(apath)
        if foundVert:
            break

    return foundVert


def psname_in_fmndb(ps_name, fmndb_path):
    """
    Check the FontMenuNameDB file for the presence of a specific PostScript
    name. Returns a boolean.
    """
    found_psname = False
    with io.open(fmndb_path, encoding='utf-8') as fp:
        fmndb_lst = fp.readlines()
    for ln in fmndb_lst:
        ln = ln.strip()
        if not ln.startswith('#') and (f'[{ps_name}]' in ln):
            found_psname = True
            break
    return found_psname


def setMissingParams(makeOTFParams):
    error = False
    # The path to the original src font file
    inputFilePath = getattr(makeOTFParams, kFileOptPrefix + kInputFont)
    srcFontPath = inputFilePath

    # check for input font file.
    # path to font file was not specified.
    if not inputFilePath:
        for fileName in kDefaultFontPathList:
            if os.path.exists(fileName):
                srcFontPath = inputFilePath = fileName
                break
        if not inputFilePath:
            print("makeotf [Error] Could not find any of the default input "
                  "font files %s." % kDefaultFontPathList)
            # stop here already, otherwise getROS() will generate an IOError.
            raise MakeOTFOptionsError

        setattr(makeOTFParams, kFileOptPrefix + kInputFont, inputFilePath)
        checkInputFile(makeOTFParams)

    if makeOTFParams.tempFontPath:
        inputFontPath = makeOTFParams.tempFontPath
    else:
        inputFontPath = inputFilePath

    # features path.
    fea_path = getattr(makeOTFParams, kFileOptPrefix + kFeature)
    if not fea_path:
        # First look for FEA file in the font's home directory
        for fea_file_name in kDefaultFeaturesList:
            path1 = os.path.join(makeOTFParams.fontDirPath, fea_file_name)
            if os.path.exists(path1):
                fea_path = path1
                break
        # If FEA file hasn't been found yet, look for it inside UFO
        if not fea_path and makeOTFParams.srcIsUFO:
            path1 = os.path.join(srcFontPath, 'features.fea')
            if os.path.exists(path1):
                fea_path = path1

        if not fea_path:
            print("makeotf [Warning] Could not find default features file. "
                  "Font will be built without any layout features.")
            setattr(makeOTFParams, kFileOptPrefix + kFeature, None)
        else:
            print("makeotf [Note] Using features file at '%s'." % fea_path)
            setattr(makeOTFParams, kFileOptPrefix + kFeature, fea_path)

    # FontMenuNameDB path.
    fmndb_path = getattr(makeOTFParams, kFileOptPrefix + kFMB)
    ps_name = makeOTFParams.psName
    if fmndb_path == "None":
        setattr(makeOTFParams, kFileOptPrefix + kFMB, None)
    # FontMenuNameDB file was NOT specified
    elif fmndb_path is None:
        newpath = os.path.join(makeOTFParams.fontDirPath, kDefaultFMNDBPath)
        found_fmndb = False
        for new_fmndb_path in [newpath, lookUpDirTree(newpath)]:
            if new_fmndb_path and os.path.exists(new_fmndb_path):
                found_fmndb = True
                break
        if found_fmndb:
            if not psname_in_fmndb(ps_name, new_fmndb_path):
                print(f"makeotf [Warning] Could not find '[{ps_name}]' in "
                      "FontMenuNameDB file at "
                      f"'{os.path.abspath(new_fmndb_path)}'. Font will be "
                      "built with menu names derived from PostScript name.")
                setattr(makeOTFParams, kFileOptPrefix + kFMB, None)
            else:
                setattr(makeOTFParams, kFileOptPrefix + kFMB, new_fmndb_path)
        else:
            if makeOTFParams.srcIsUFO:
                ufo_fmndb_path = ufotools.makeUFOFMNDB(srcFontPath)
                if ufo_fmndb_path:
                    makeOTFParams.ufoFMNDBPath = ufo_fmndb_path
                    setattr(
                        makeOTFParams, kFileOptPrefix + kFMB, ufo_fmndb_path)
                else:
                    print("makeotf [Note] Could not find FontMenuNameDB file. "
                          "Font will be built with menu names derived from "
                          "PostScript name.")
                    setattr(makeOTFParams, kFileOptPrefix + kFMB, None)
            else:
                print("makeotf [Warning] Could not find FontMenuNameDB file. "
                      "Font will be built with menu names derived from "
                      "PostScript name.")
                setattr(makeOTFParams, kFileOptPrefix + kFMB, None)
    # FontMenuNameDB file was specified
    else:
        if not psname_in_fmndb(ps_name, fmndb_path):
            print(f"makeotf [Warning] Could not find '[{ps_name}]' in "
                  f"FontMenuNameDB file at '{os.path.abspath(fmndb_path)}'. "
                  "Font will be built with menu names derived from PostScript "
                  "name.")
            setattr(makeOTFParams, kFileOptPrefix + kFMB, None)

    # GOADB path.
    # Figure out if GOABD is required. It is required when (release mode
    # is set or aliasing is requested), and the font is not a CID font.
    if makeOTFParams.ROS is None:
        # Need to know if it has an ROS, e.g. is a CID font.
        # We don't use GOADB file with CID fonts; Unicode values
        # come from Uni-H CMAP file, and names can't be changed.
        makeOTFParams.ROS = getROS(inputFontPath)

    required = not makeOTFParams.ROS[0] and (
        ('true' == getattr(makeOTFParams, kFileOptPrefix + kDoAlias)) or
        ('true' == getattr(makeOTFParams, kFileOptPrefix + kRelease)))

    goadb_path = getattr(makeOTFParams, kFileOptPrefix + kGOADB)

    # GOADB path was not specified.
    if not goadb_path and required:
        newpath = os.path.join(makeOTFParams.fontDirPath, GOADB_NAME)
        for path in [newpath, lookUpDirTree(newpath)]:
            if path and os.path.exists(path):
                setattr(makeOTFParams, kFileOptPrefix + kGOADB, path)
                break

    # If font is CID, look in the SharedData folder.
    if os.path.exists(inputFontPath):
        Reg, Ord, Sup = makeOTFParams.ROS
        if Reg:
            featPath = getattr(makeOTFParams, kFileOptPrefix + kFeature)
            foundVert = checkIfVertInFeature(featPath)
            if featPath and os.path.exists(featPath) and not foundVert:
                print("makeotf [Warning] the feature file does not contain a "
                      "'vert' feature %s." % featPath)

            if not (getattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath) and
               getattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath) and
               getattr(makeOTFParams, kFileOptPrefix + kUVSPath)):
                error = setCIDCMAPPaths(makeOTFParams, Reg, Ord, Sup)

    # output file path.
    path = getattr(makeOTFParams, kFileOptPrefix + kOutputFont)
    output_dir = None
    if path and os.path.isdir(path):
        # support '-o' option to be a folder only
        # https://github.com/adobe-type-tools/afdko/issues/281
        output_dir = path
        path = None
    if not path:
        if makeOTFParams.srcIsTTF or inputFontPath.lower().endswith(".ttf"):
            font_filename = f"{makeOTFParams.psName}.ttf"
        else:
            font_filename = f"{makeOTFParams.psName}.otf"

        if output_dir:
            font_path = os.path.join(output_dir, font_filename)
        else:
            font_path = os.path.join(makeOTFParams.fontDirPath, font_filename)

        output_path = os.path.abspath(os.path.realpath(font_path))
        setattr(makeOTFParams, kFileOptPrefix + kOutputFont, output_path)

    if error:
        raise MakeOTFOptionsError


def convertFontIfNeeded(makeOTFParams):
    # we've already done this.
    if makeOTFParams.tempFontPath is not None:
        return

    # If the font doesn't look like a Unix ASCII
    # Type 1 font file, convert it with 'tx'
    filePath = getattr(makeOTFParams, kFileOptPrefix + kInputFont)

    needsConversion = False
    needsSEACRemoval = False
    isTextPS = False

    input_font_format = fdkutils.get_font_format(filePath)

    if not input_font_format:
        print("makeotf [Error] Unknown input font format '%s'" % filePath)
        raise MakeOTFRunError

    if input_font_format == 'UFO':
        needsConversion = True
        makeOTFParams.srcIsUFO = 1

        allMatch, msgList = ufotools.checkHashMaps(filePath, False)
        if not allMatch:
            for msg in msgList:
                print(msg)
            raise MakeOTFShellError

    elif input_font_format == 'OTF':
        needsConversion = True

    elif input_font_format == 'TTF':
        needsConversion = True
        makeOTFParams.srcIsTTF = 1

    # it's a PostScript font
    else:
        try:
            with open(filePath, "rb") as fp:
                data = fp.read(1024)
            if (b"%" not in data[:4]) and (b'/FontName' not in data):
                needsConversion = True
            else:
                if b"/Private" in data:
                    isTextPS = True
                    needsConversion = True
        except (OSError):
            print("makeotf [Error] Could not read font file at '%s'." %
                  filePath)
            raise MakeOTFShellError

    # Warn if there are any seac operators in the input file.
    if not (makeOTFParams.srcIsTTF or isTextPS or makeOTFParams.srcIsUFO):
        success, output = fdkutils.get_shell_command_output([
            'tx', '-dump', '-5', '-n', filePath])
        if not success:
            raise MakeOTFShellError

        glyphList = re.findall(r"glyph[^{]+?{([^,]+),[^[\]]+\sseac\s",
                               output)
        if glyphList:
            glyphList = ", ".join(glyphList)
            print("makeotf [Warning] Font at '%s' contains deprecated "
                  "SEAC operator. These composite glyphs will be "
                  "decomposed by makeOTF:\n%s" % (filePath, glyphList))
            needsConversion = True
            needsSEACRemoval = True

    if needsConversion:
        fontPath = fdkutils.get_temp_file_path()

        if isTextPS:
            tempTxtPath = fdkutils.get_temp_file_path()

            # convert PS decrypted to PS encrypted with 'type1'
            if not fdkutils.run_shell_command([
                    'type1', filePath, tempTxtPath]):
                raise MakeOTFShellError
            filePath = tempTxtPath

        if needsSEACRemoval:
            tempSeacPath = fdkutils.get_temp_file_path()

            # convert to CFF using 'tx'
            if not fdkutils.run_shell_command([
                    'tx', '-cff', '-Z', '+b', filePath, tempSeacPath]):
                raise MakeOTFShellError
            filePath = tempSeacPath

        psName = get_font_psname(filePath, makeOTFParams.srcIsUFO)

        # Now convert to Type 1
        # (it's the only font format that makeotfexe can consume)
        if not fdkutils.run_shell_command(['tx', '-t1', filePath, fontPath]):
            raise MakeOTFShellError

        makeOTFParams.tempFontPath = fontPath

    else:  # convertion is not needed
        psName = get_font_psname(filePath)

    makeOTFParams.psName = psName


def get_font_psname(font_path, is_ufo=False):
    # Figure out PS name in order to derive default output path.
    success, output = fdkutils.get_shell_command_output([
        'tx', '-dump', '-0', font_path], std_error=True)
    if not success:
        raise MakeOTFShellError

    match = re.search(r"(?:CID)?FontName\s+\"(\S+)\"", output)
    if not match:
        if is_ufo:
            print(f"makeotf [Error] Could not find 'postscriptFontName' "
                  f"in file '{font_path}'")
        else:
            print(f"makeotf [Error] Could not find FontName (a.k.a. "
                  f"PostScript name) in FontDict of file "
                  f"'{font_path}'")
        raise MakeOTFShellError

    return match.group(1)


def updateFontRevision(featuresPath, fontRevision):
    """
    Set or increment FontRevision value in fea file.

    supplied 'fontRevision' kind specifies behavior:
    - float/decimal means "set to" supplied value, e.g.:
      .fea FontRevision = '2.102', fontRevision == '1.100', result = '1.100'

    - integer value means "increment minor by this value (thousandths)":
      .fea FontRevision = '1.230', fontRevision == '5', result = '1.235'
      .fea FontRevision = '1.1', fontRevision == '1', result = '1.101'
      .fea FontRevision = '1.02', fontRevision == '31', result = '1.051'

    Constraint: resulting FontRevision must be in range 0.000 - 32767.000.
    This means:
       - for float case, 0.000 < float(fontRevision) <= 32767.000
       - for int case, fractional part + increment <= .999
       - if supplied values would overflow, a message is printed and
         the feature file is not updated.
    """

    try:
        # get FontRevision from feature file
        featuresPath = os.path.abspath(featuresPath)
        with open(featuresPath, "r", encoding='utf-8') as fp:
            data = fp.read()
    except (OSError):
        print("makeotf [Error] When trying to update the head table "
              "fontRevision field, failed to open '%s'." % featuresPath)
        return

    fea_match = re.search(r"FontRevision\s+(\d+)\.(\d+);", data)

    if not fea_match:
        print("makeotf [Error] When trying to update the head table "
              "fontRevision field, failed to find the fontRevision keyword in "
              f"'{featuresPath}'")
        return

    # fontRevision comes from -rev option
    usr_match = re.search(r'([0-9]{1,5})(\.[0-9]{1,3})?', fontRevision)

    if not usr_match:
        print("makeotf [Error] When trying to update the head table "
              "fontRevision field, supplied update value was not valid "
              "(must be integer in range 1-999 or decimal in range "
              "0.000 - 32767.000")
        return

    if usr_match.group(2) is not None:
        # "decimal" case; replace .fea value with normalized version
        if float(fontRevision) > 32767.000:
            print("makeotf [Error] When trying to update the head table "
                  f"fontRevision field, supplied value {fontRevision} "
                  "is out of range (0.000 - 32767.000)")
            return
        major = int(usr_match.group(1))
        minor = int(usr_match.group(2)[1:].ljust(3, '0'))
    else:
        # integer case, increment (if it doesn't cause overflow)
        major = int(fea_match.group(1))
        minor_str = fea_match.group(2).ljust(3, '0')
        minor = int(minor_str)
        increment = int(usr_match.group(1))
        minor += increment
        if minor > 999:
            print("makeotf [Error] When trying to update the head table "
                  f"fontRevision field, supplied value {increment} would "
                  f"cause {major}.{minor_str} to overflow.")
            return

    newData = re.sub(fea_match.group(0),
                     f"FontRevision {major}.{minor:03};",
                     data)

    try:
        with open(featuresPath, "w", encoding='utf-8') as fp:
            fp.write(newData)
    except (OSError):
        print("makeotf [Error] When trying to update the head table "
              "fontRevision field, failed to write the new data to '%s'." %
              featuresPath)


def checkFSTypeValue(FSType, outputPath):
    success, output = fdkutils.get_shell_command_output([
        'spot', '-t', 'OS/2', outputPath])
    if not success:
        raise MakeOTFShellError

    match = re.search(r"type\s+=(\S+)", output)
    if not match:
        print("makeotf [Error] Could not find 'type' in spot dump of OS/2 "
              "table of file at '%s'." % outputPath)
        return
    fsTypeTxt = match.group(1)
    fsType = fsTypeTxt.lstrip("0")
    if (len(fsType) == 0) and (len(fsTypeTxt) > 0):
        fsType = "0"
    fsType = str(fsType)
    if fsType != FSType:
        print("makeotf [Error] FSType value '%s' from (cid)fontinfo file does "
              "not match value '%s' of OS/2 fsType in file at '%s'." %
              (FSType, fsType, outputPath))


def getSourceGOADBData(inputFilePath):
    # First, get the Unicode mapping from the TTF cmap table.
    success, output = fdkutils.get_shell_command_output([
        'spot', '-t', 'cmap=7', inputFilePath])
    if not success:
        raise MakeOTFShellError

    spotGlyphList = re.findall(r"[\n\t]\[(....+)\]=<([^>]+)>", output)

    # Because this dumps all the Unicode map tables, there are a number
    # of duplicates; weed them out, and strip out gid part of spot name
    gDict = {}
    for entry in sorted(set(spotGlyphList)):
        uni = entry[0]
        gname, gid_str = entry[1].split('@')
        gid = int(gid_str)
        if gid in gDict:
            print("makeotf [Warning] Source TTF font contains multiple "
                  "Unicode values for glyph '%s'. Only the first ('%s') "
                  "will be used. Additional Unicode value: %s" %
                  (gname, gDict[gid], uni))
        else:
            gDict[gid] = uni

    # Now get the font glyph name list, so as to get the glyphs with
    # no unicode mapping. We'll also use this to set the glyph order.
    # I use tx so as to get the same names as tx for the TTF glyphs;
    # this can differ from spot. I don't use tx for Unicode values.
    # as tx doesn't check 32 bit UV's, and doesn't report double-encodings.
    success, output = fdkutils.get_shell_command_output([
        'tx', '-mtx', inputFilePath])
    if not success:
        raise MakeOTFShellError

    txGlyphList = re.findall(r"[\n\r]glyph\[(\d+)\]\s+{([^,]+)", output)

    gnameDict = {}
    for gid_str, gname in txGlyphList:
        gid = int(gid_str)
        gnameDict[gid] = gname
        if gid not in gDict:
            gDict[gid] = None

    # Now flatten this to a GOADB list.
    goadbList = []
    for gid, uni_val in sorted(gDict.items()):
        gname = gnameDict[gid]
        if uni_val is None:
            uniValue = ''
        else:
            uniValue = 'uni%s' % uni_val
        goadbList.append([gname, gname, uniValue])

    return goadbList


def getGOADBData(goadbPath):
    goadbList = []
    with open(goadbPath, "r", encoding='utf-8') as fp:
        for line in fp.readlines():
            line = line.strip()
            if not line:  # blank lines
                continue
            if line[0] == '#':  # comments
                continue

            line_items = line.split()

            uniName = None
            if len(line_items) > 2:
                uniName = line_items[2]

            goadbList.append([line_items[0], line_items[1], uniName])

    return goadbList


def compareSrcNames(srcGOADBList, realGOADBList):
    srcNameList = [entry[1] for entry in srcGOADBList]
    realNameList = [entry[1] for entry in realGOADBList]
    if srcNameList != realNameList:
        # order or names don't match, report any missing names
        onlyInSrc = [n for n in srcNameList if n not in realNameList]
        onlyInReal = [n for n in realNameList if n not in srcNameList]
        return 1, onlyInSrc, onlyInReal
    return 0, [], []  # both names and order match


def writeTempGOADB(srcGOADBList):
    fpath = fdkutils.get_temp_file_path()
    with open(fpath, "w") as fp:
        for entry in srcGOADBList:
            fp.write("%s\t%s\t%s\n" % (entry[0], entry[1], entry[2]))
    return fpath


def copyTTFGlyphTables(inputFilePath, tempOutputPath, outputPath):
    # tempOutputPath exists and is an OTF/CFF font.
    # outputPath does not yet exist, or is the same as inputFilePath.

    # Get the final glyph name list.
    success, output = fdkutils.get_shell_command_output([
        'tx', '-mtx', tempOutputPath])
    if not success:
        raise MakeOTFShellError

    glyphList = re.findall(r"[\n\r]glyph\[\d+\]\s+{([^,]+)", output)

    font = TTFont(inputFilePath)
    temp_font = TTFont(tempOutputPath)

    print("Fixing output font 'post' table...")
    fixPost(glyphList, font)

    print("Fixing output font 'head' table...")
    fixHead(temp_font, font)

    print("Fixing output font 'hhea' table...")
    fixHhea(temp_font, font)

    print("Copying makeotf-generated tables from temp OTF file to output "
          "font...")
    tags = ["GDEF", "GSUB", "GPOS", "cmap", "name", "OS/2", "BASE"]
    copy_tables(temp_font, font, tags)

    print("Succeeded in merging makeotf tables with TrueType source font to "
          "final TrueType output font at '%s'." % outputPath)

    font.save(outputPath)
    font.close()
    temp_font.close()


def fixPost(glyphList, font):
    """
    Replace 'post' table
    """
    glyphOrderList = []
    extraNamesList = []
    for gname in glyphList:
        glyphOrderList.append(gname)
        if gname not in kStdNames:
            extraNamesList.append(gname)

    post_table = font['post']
    post_table.mapping = {}
    post_table.formatType = 2
    post_table.glyphOrder = glyphOrderList
    post_table.extraNames = extraNamesList


def update_table_items(table_tag, items_list, font1, font2):
    """
    font1 will get new values from font2
    """
    font1_table = font1[table_tag]
    font2_table = font2[table_tag]

    for item_name in items_list:
        setattr(font1_table, item_name, getattr(font2_table, item_name))


def fixHead(temp_font, font):
    """
    temp_font is an OTF/CFF font.
    font is a TTF font.
    Need to update the head values. Can't just copy the entire table
    from the OTF temp font, as as some of the head table values control
    interpretation of the glyf data.
    """
    head_items = ("fontRevision", "created", "modified", "macStyle",
                  "xMin", "xMax", "yMin", "yMax")
    update_table_items('head', head_items, font, temp_font)


def fixHhea(temp_font, font):
    hhea_items = ("ascent", "descent", "lineGap")
    update_table_items('hhea', hhea_items, font, temp_font)


def copy_tables(temp_font, font, tags):
    for tag in tags:
        if tag not in temp_font:
            continue
        table = DefaultTable(tag)
        table.data = temp_font.getTableData(tag)
        font[tag] = table
        print('    copied "%s"' % tag)


def adjustPaths(makeOTFParams):
    """
    Change file paths to be relative to fontDir,
    if possible, else to absolute paths.
    (With the exception of fmndbFilePath and tempFontPath which are
    always absolute paths.)
    """
    inputFilePath = getattr(makeOTFParams, kFileOptPrefix + kInputFont)
    fontDir = os.path.dirname(os.path.abspath(inputFilePath))

    setattr(makeOTFParams, kFileOptPrefix + kInputFont, inputFilePath)

    outputFilePath = getattr(makeOTFParams, kFileOptPrefix + kOutputFont)
    if outputFilePath:
        outputFilePath = os.path.abspath(outputFilePath)
        setattr(makeOTFParams, kFileOptPrefix + kOutputFont, outputFilePath)

    featuresFilePath = getattr(makeOTFParams, kFileOptPrefix + kFeature)
    if featuresFilePath:
        featuresFilePath = os.path.abspath(featuresFilePath)
        setattr(makeOTFParams, kFileOptPrefix + kFeature, featuresFilePath)

    fmndbFilePath = getattr(makeOTFParams, kFileOptPrefix + kFMB)
    if fmndbFilePath:
        fmndbFilePath = os.path.abspath(fmndbFilePath)
        setattr(makeOTFParams, kFileOptPrefix + kFMB, fmndbFilePath)

    goadbFilePath = getattr(makeOTFParams, kFileOptPrefix + kGOADB)
    if goadbFilePath:
        goadbFilePath = os.path.abspath(goadbFilePath)
        setattr(makeOTFParams, kFileOptPrefix + kGOADB, goadbFilePath)

    maccmapFilePath = getattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath)
    if maccmapFilePath:
        maccmapFilePath = os.path.abspath(maccmapFilePath)
        setattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath, maccmapFilePath)

    uniHFilePath = getattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath)
    if uniHFilePath:
        uniHFilePath = os.path.abspath(uniHFilePath)
        setattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath, uniHFilePath)

    uvsFilePath = getattr(makeOTFParams, kFileOptPrefix + kUVSPath)
    if uvsFilePath:
        uvsFilePath = os.path.abspath(uvsFilePath)
        setattr(makeOTFParams, kFileOptPrefix + kUVSPath, uvsFilePath)

    if makeOTFParams.tempFontPath:
        makeOTFParams.tempFontPath = os.path.abspath(
            makeOTFParams.tempFontPath)

    return fontDir


def _byEntryOrder(key1, key2):
    a = kMOTFOptions[key1][0]
    b = kMOTFOptions[key2][0]
    return (a > b) - (a < b)


byEntryOrder = functools.cmp_to_key(_byEntryOrder)


def runMakeOTF(makeOTFParams):
    # Change to current directory to be the same as the features
    # dir so that relative paths in feature file will work
    curdir = os.getcwd()

    # Change file paths to be relative to fontDir,
    # if possible, else to absolute paths.
    fontDir = adjustPaths(makeOTFParams)
    inputFilePath = getattr(makeOTFParams, kFileOptPrefix + kInputFont, None)

    # This MUST follow makeRelativePaths.
    os.chdir(fontDir)

    inputFontPath = makeOTFParams.tempFontPath
    if not inputFontPath:
        inputFontPath = inputFilePath

    # If the output file already exists, delete it;
    # we want to know if making the new output file fails.
    outputPath = getattr(makeOTFParams, kFileOptPrefix + kOutputFont)
    if os.path.abspath(outputPath) == os.path.abspath(inputFilePath):
        print("makeotf [Error] Source and output files cannot be the "
              "same. %s." % outputPath)
        raise MakeOTFRunError

    tempOutPath = outputPath

    # The following is because fontPath may not be the same
    # as makeOTFParams.kFileOptPrefix, kInputFont anymore.
    optionKeys = list(kMOTFOptions.keys())
    optionKeys.remove(kInputFont)
    optionKeys.remove(kOutputFont)

    # If the source font is a TTF font, we fix up the output font by deleting
    # the CFF table from the built font, and copying over from the source all
    # the TTF-specific tables. This works only if glyph order is preserved
    # between the source and final font. makeotf will force glyph order to
    # match CFF Std Encoding, unless you override it with a GAODB file. We need
    # to now make sure that a GAODB files exists that will enforce the source
    # font glyph order, and that it is used.
    if makeOTFParams.srcIsTTF:
        tempOutPath = fdkutils.get_temp_file_path()
        # Build GOADB data from the source.
        # Maps src glyph names to the same, plus adding any Unicode values
        srcGOADBList = getSourceGOADBData(inputFilePath)

        # if the user has asked to use an existing GOADB file, we need
        # to make sure that it will preserve the src glyph order.
        use_alias = getattr(makeOTFParams, kFileOptPrefix + kDoAlias) \
            or getattr(makeOTFParams, kFileOptPrefix + kRelease)
        if use_alias:
            goadbPath = getattr(makeOTFParams, kFileOptPrefix + kGOADB)
            if not goadbPath:
                print("makeotf [Error] GlyphOrderAndAliasDB file not found.")
                raise MakeOTFRunError
            realGOADBList = getGOADBData(goadbPath)
            result, onlyInSrc, onlyInReal = compareSrcNames(srcGOADBList,
                                                            realGOADBList)
            if result == 1:
                print("makeotf [Error] For TTF fonts, the "
                      "GlyphOrderAndAliasDB file must preserve the original "
                      "font glyph order, and use the same names as they are "
                      "derived by the 'tx' tool. %s." % goadbPath)
                if onlyInSrc:
                    print("Glyphs in TTF font missing from "
                          "GlyphOrderAndAliasDB: %s" % " ".join(onlyInSrc))
                if onlyInReal:
                    print("Glyphs in GlyphOrderAndAliasDB missing from TTF "
                          "font: %s" % " ".join(onlyInReal))
                raise MakeOTFRunError
        else:
            # If the user has NOT asked to use an existing GOADB file, then
            # we need to make and use one in order to preserve glyph order.
            tempGOADBPath = writeTempGOADB(srcGOADBList)
            setattr(makeOTFParams, kFileOptPrefix + kGOADB, tempGOADBPath)
            setattr(makeOTFParams, kFileOptPrefix + kDoAlias, 'true')

        # if the source is TTF, then the name table must
        # be built without the -useOldNameID4 option.
        useOldNameID4 = getattr(makeOTFParams,
                                kFileOptPrefix + kUseOldNameID4,
                                False)

        if useOldNameID4:
            print("Because font is TTF, forcing use of new name table id 4 "
                  "format")
            setattr(makeOTFParams, kFileOptPrefix + kUseOldNameID4, None)

        # We also need to suppress the warnings about
        # no hints: all glyphs will be un-hinted.
        setattr(makeOTFParams, kFileOptPrefix + kSuppressHintWarnings, 'true')

    # Suppress hint warnings if not in release mode, but
    # only if the option was NOT used on the command line
    if (getattr(makeOTFParams, kFileOptPrefix + kRelease) != 'true' and
            kMOTFOptions[kSuppressHintWarnings][0] == kOptionNotSeen):
        setattr(makeOTFParams, kFileOptPrefix + kSuppressHintWarnings, 'true')

    # check if the OS/2 version table needs to be set to 4
    # because the new fsSelection bits are being used.
    bitsOff = getattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOff)
    bitsOn = getattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOn)

    if bitsOff and bitsOn:  # neither can be None
        # remove any OFF values that were also (mistakenly?) requested to be ON
        for bit_val in bitsOff:
            try:
                bitsOn.remove(bit_val)
            except ValueError:
                continue

    hasOS2V4Bit = False
    if bitsOn:
        if int(sorted(bitsOn)[0]) > 6:
            hasOS2V4Bit = True

    os2Version = getattr(makeOTFParams, kFileOptPrefix + kSetOS2Version)
    if hasOS2V4Bit and (os2Version is None):
        # If the OS/2 table version has not been explicitly specified,
        # and the kSetfsSelectionBits is being set to greater than 6,
        # then bump the OS/2 table version to 4.
        setattr(makeOTFParams, kFileOptPrefix + kSetOS2Version, '4')

    if hasOS2V4Bit:
        osv4Err = False
        if makeOTFParams.seenOS2v4Bits[0] == 0:
            osv4Err = True
            print("makeotf [Error] No value was provided for "
                  "USE_TYPO_METRICS OS/2 fsSelection bit 7.")
        if makeOTFParams.seenOS2v4Bits[1] == 0:
            osv4Err = True
            print("makeotf [Error] No value was provided for "
                  "WEIGHT_WIDTH_SLOPE_ONLY OS/2 fsSelection bit 8.")
        if makeOTFParams.seenOS2v4Bits[2] == 0:
            osv4Err = True
            print("makeotf [Error] No value was provided for OBLIQUE OS/2 "
                  "fsSelection bit 9.")
        if osv4Err:
            print("makeotf [Error] When setting any of the OS/2 table "
                  "version 4 bits, you must specify an explicit on/off "
                  "value for all three of them.")
            return

    # renumber the fontRevision
    fontRevision = getattr(makeOTFParams, kFileOptPrefix + kRenumber, None)
    featuresPath = getattr(makeOTFParams, kFileOptPrefix + kFeature, None)
    if fontRevision is not None:
        # If this is not defined, then there is no 'features' file. Make one.
        if not featuresPath:
            print("makeotf [Warning] Creating 'features' file to hold head "
                  "table FontRevision value.")
            featuresDir = os.path.dirname(inputFontPath)
            featuresPath = os.path.join(featuresDir, "features")
            setattr(makeOTFParams, kFileOptPrefix + kFeature, featuresPath)

            with open(featuresPath, "w") as fp:
                fp.write("\ntable head {\nFontRevision 1.000;\n} head;\n")

        updateFontRevision(featuresPath, fontRevision)

    # if converting to CID, save the setting, but
    # remove the option from makeOTFParams so we don't
    # pass the -cn arg/-ncn arg on to makeotfexe.
    doConvertToCID = getattr(makeOTFParams, kFileOptPrefix + kConvertToCID)
    if doConvertToCID is not None:
        if makeOTFParams.srcIsTTF:
            doConvertToCID = None

    params = [
        'makeotfexe',
        f'{kMOTFOptions[kInputFont][1]}',
        inputFontPath,
        f'{kMOTFOptions[kOutputFont][1]}',
        tempOutPath,
    ]

    # Add the rest of the parameters
    optionKeys.sort(key=byEntryOrder)
    for optionKey in optionKeys:
        # Skip the options that should not be passed to makeotfexe.
        if optionKey in kSkipOptions:
            continue

        val = getattr(makeOTFParams, kFileOptPrefix + optionKey, None)

        if val is None:
            continue

        optionEntry = kMOTFOptions[optionKey]
        # it is a true/false option.
        if optionEntry[2]:
            if val == 'true':
                params.append(optionEntry[1])
            elif val == 'false':
                params.append(optionEntry[2])
            # True/False option where the option may have
            # an integer value following, e.g -adds.
            else:
                params.append(optionEntry[1])
                params.append(val)
        # an option without a following value
        elif val == 'true':
            params.append(optionEntry[1])
        # an option without a following value
        elif val == 'false':
            print("makeotf [Error] Program error: option %s is set false but "
                  "is not T/F" % optionEntry[1])
        elif isinstance(val, list):
            for item in val:
                params.append(optionEntry[1])
                params.append(item)
        else:
            params.append(optionEntry[1])
            params.append(val)

    if makeOTFParams.verbose:
        print("makeotf [Note] Running %s with commands:" %
              os.path.basename('makeotfexe'))
        print(f'   cd "{fontDir}"')
        print(f"   {params}")

    success = fdkutils.run_shell_command_logging(params)

    _check_remove_bad_output(tempOutPath)

    if not success:
        raise MakeOTFShellError

    if makeOTFParams.srcIsTTF:
        copyTTFGlyphTables(inputFilePath, tempOutPath, outputPath)

    _check_remove_bad_output(outputPath)

    # The following check is here because of the internal Adobe
    # production process for CID fonts, where a Type1 CID font
    # is made with the FSType from the cidfontinfo file, and can
    # be a product independent of the OpenType font. Need to make
    # sure that CID font FSType is the same as the table fsType.
    if hasattr(makeOTFParams, 'FSType'):
        checkFSTypeValue(makeOTFParams.FSType, outputPath)

    # If we need to convert this to a CID keyed font,
    # we do this as a post processing step on the OTF.
    # NOTE: See comment about font.pfa below.
    if doConvertToCID == "true":
        print("Converting CFF table to CID-keyed CFF...")
        tempPath = fdkutils.get_temp_file_path()
        try:
            doSubr = 'true' == getattr(makeOTFParams, kFileOptPrefix + kDoSubr)

            if kMOTFOptions[kDoSubr][0] == kOptionNotSeen:
                doSubr = 'true' == getattr(makeOTFParams,
                                           kFileOptPrefix + kRelease)
            # Send the font.pfa file to convertfonttocid.py
            # rather than the OTF because the GlyphSet
            # definitions in the 'fontinfo' files use production
            # glyph names not final glyph names.
            convertfonttocid.convertFontToCID(
                outputPath, tempPath, makeOTFParams.fontinfoPath)
            convertfonttocid.mergeFontToCFF(tempPath, outputPath, doSubr)

        except (convertfonttocid.FontInfoParseError,
                convertfonttocid.FontParseError):
            raise

        if not os.path.exists(outputPath):
            print("makeotf [Error] Failed to convert font '%s' to CID." %
                  outputPath)
            raise MakeOTFRunError

    if getattr(makeOTFParams, kFileOptPrefix + kRelease):
        try:
            with TTFont(outputPath) as font:
                if 'head' not in font:
                    print("makeotf [Error] Could not extract version number "
                          "from file at '%s'." % outputPath)
                    raise MakeOTFShellError
                font_version = Decimal(
                    font['head'].fontRevision).quantize(Decimal('1.000'))

                vendor = None
                if 'OS/2' in font:
                    vendor = font['OS/2'].achVendID
                if int(font_version) == 0 and vendor == 'ADBE':
                    print("makeotf [Warning] Major version number not in "
                          "range 1 .. 255")

                print("Built release mode font '%s' Revision %s" % (
                    outputPath, font_version))

        except (IOError, TTLibError):
            # makeotfexe failed to generate a font
            # or the font that was generated is invalid
            raise MakeOTFShellError

    else:
        print("Built development mode font '%s'." % outputPath)

    os.chdir(curdir)


def CheckEnvironment():
    missingTools = []
    for name in ['tx', 'makeotfexe', 'spot']:
        if not fdkutils.get_shell_command_output([name, '-h'])[0]:
            missingTools.append(name)

    if missingTools:
        print("Please check your PATH, and if necessary re-install the AFDKO. "
              "Unable to find these tools: %s." % missingTools)
        raise FDKEnvironmentError


def main(args=None):
    if not args:
        args = sys.argv[1:]

    try:
        CheckEnvironment()
    except FDKEnvironmentError:
        return 1

    makeOTFParams = MakeOTFParams()

    try:
        getOptions(makeOTFParams, args)
        setMissingParams(makeOTFParams)
        setOptionsFromFontInfo(makeOTFParams)
        if makeOTFParams.saveOptions == 'true':
            # this always saves options to kDefaultOptionsFile;
            # may also save to user-specified option file.
            saveOptionsFile(makeOTFParams)
        runMakeOTF(makeOTFParams)
    except (MakeOTFOptionsError, MakeOTFShellError, MakeOTFRunError):
        return 2
    except Exception:
        raise


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