# Copyright 2014 Adobe. All rights reserved.

from __future__ import print_function, absolute_import
import functools
import os
import re
import sys

from fontTools.ttLib import TTFont
from fontTools.misc.py23 import open, tounicode, tobytes

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.5.0 Aug 28 2018
"""

__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 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 white-space removed from both end. 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.
-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'.
-fs                     If there are no glyph substitution rules in
                the features file, make a stub GSUB table.
-mf <FontMenuNameDB>    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                     sets 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 subroutinzation, if it has
                previously been turned on.
-maxs <number>          Limits the number of subroutines generated by
                glyph subroutinization to the number. Used for
                testing with rasterizers that don't process
                subroutinization correctly.
-ga                     Applies 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/useMacNames   Write only Windows platform menu names in name
table,
                apart from the names specified in the feature file.
                -useMacNames writes Mac 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 time you run makeotf, it saves the options used to build the
font in a project file named "current.fpr". You can build the font
again 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"]
kDefaultFeaturesPath = "features"
kDefaultUFOFeaturesPath = "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 MakeOTFTXError(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.
        # exec("self.%s%s = [7,8]" % (kFileOptPrefix, kSetfsSelectionBitsOn))

    def __repr__(self):
        text = '{}:\n'.format(self.__class__.__name__)
        for key, val in sorted(vars(self).items()):
            text += '  {}: {}\n'.format(key, val)
        return text


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 (IOError, 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 = eval(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, "wb") as fp:
                fp.write(tobytes(data, encoding='utf-8'))
        except (IOError, 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, IOError):
            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):
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kBold))
            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):
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kBold))
            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):
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kItalic))
            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):
            exec("makeOTFParams.%s%s = 'true'" % (
                kFileOptPrefix, kConvertToCID))
            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):
            exec("makeOTFParams.%s%s = 'true'" % (
                kFileOptPrefix, kUseOldNameID4))
            print("makeotf [Note] using old name ID 4 logic.")

    if kMOTFOptions[kOmitMacNames][0] == kOptionNotSeen:
        if re.search(r"OmitMacNames+([Tt]rue|1)", data):
            exec("makeOTFParams.%s%s = 'true'" % (
                kFileOptPrefix, kOmitMacNames))
            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):
            exec("makeOTFParams.%s%s = 'true'" % (
                kFileOptPrefix, kOverrideMenuNames))
            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):
            exec("makeOTFParams.%s%s = 'true'" % (
                kFileOptPrefix, kSuppressKernOptimization))
            print("makeotf [Note] Suppressing Kern Optimization.")

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

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

    if kMOTFOptions[kLicenseCode][0] == kOptionNotSeen:
        m = re.search(r"LicenseCode\s+([^\r\n]+)", data)
        if m:
            exec("makeOTFParams.%s%s = \"%s\"" % (
                kFileOptPrefix, kLicenseCode, 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):
            exec("makeOTFParams.%s%s = 'true'" % (
                kFileOptPrefix, kSuppressWidthOptimization))

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

    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 = eval(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
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kBold))

        # 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
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix, kBold))

        elif arg == kMOTFOptions[kItalic][1]:
            kMOTFOptions[kItalic][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kItalic))

        # 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
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix, kItalic))

        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
            exec("makeOTFParams.%s%s = file_path" % (kFileOptPrefix, kFeature))

        # 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
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kGSUBStub))

        # 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
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix, kGSUBStub))

        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":
                exec("makeOTFParams.%s%s = file_path" % (kFileOptPrefix, kFMB))
            else:
                if not os.path.exists(file_path):
                    error = 1
                    print("makeotf [Error] Could not find the FontMenuNameDB "
                          "file at '%s'." % file_path)
                    continue
                exec("makeOTFParams.%s%s = file_path" % (kFileOptPrefix, kFMB))

        # -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.
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix, kDoSubr))
            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
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix, kDoSubr))
            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
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kDoSubr))

        elif arg == kMOTFOptions[kDoSubr][2]:
            kMOTFOptions[kDoSubr][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'false'" % (kFileOptPrefix, kDoSubr))

        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)

        elif arg == kMOTFOptions[kDoSubset][1]:
            kMOTFOptions[kDoSubset][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kDoSubset))

        elif arg == kMOTFOptions[kDoSubset][2]:
            exec("makeOTFParams.%s%s = 'false'" % (kFileOptPrefix, kDoSubset))

        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 = 0
            try:
                fontRevision = eval(fontRevisionString)
                if isinstance(fontRevision, int):
                    formatOK = 1
                elif isinstance(fontRevision, float):
                    _, minor = fontRevisionString.split(".")
                    if len(minor) == 3:
                        formatOK = 1
            except ValueError:
                pass

            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
            exec("makeOTFParams.%s%s = fontRevisionString" % (kFileOptPrefix,
                                                              kRenumber))

        elif arg == kMOTFOptions[kNotdef][1]:
            kMOTFOptions[kNotdef][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kNotdef))

        # 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
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix, kNotdef))

        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,
                        [val])
            else:
                setattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOn,
                        bitsOn + [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,
                        [val])
            else:
                setattr(makeOTFParams, kFileOptPrefix + kSetfsSelectionBitsOff,
                        bitsOff + [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, 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, 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, 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, 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
            exec("makeOTFParams.%s%s = file_path" % (kFileOptPrefix,
                                                     kMacCMAPPath))

        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
            exec("makeOTFParams.%s%s = file_path" % (kFileOptPrefix,
                                                     kHUniCMAPPath))

        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
            exec("makeOTFParams.%s%s = file_path" % (kFileOptPrefix, kUVSPath))

        elif arg == kMOTFOptions[kWriteWrongBacktrack][1]:
            kMOTFOptions[kWriteWrongBacktrack][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kWriteWrongBacktrack))

        elif arg == kMOTFOptions[kDoubleMapGlyphs][1]:
            kMOTFOptions[kDoubleMapGlyphs][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kDoubleMapGlyphs))

        elif arg == kMOTFOptions[kDefaultCharAsSpace][1]:
            kMOTFOptions[kDefaultCharAsSpace][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kDefaultCharAsSpace))

        elif arg == kMOTFOptions[kUseOldNameID4][1]:
            kMOTFOptions[kUseOldNameID4][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kUseOldNameID4))

        elif arg == kMOTFOptions[kUseOldNameID4][2]:
            kMOTFOptions[kUseOldNameID4][0] = i + optionIndex
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix,
                                                kUseOldNameID4))

        elif arg == kMOTFOptions[kOmitMacNames][1]:
            kMOTFOptions[kOmitMacNames][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kOmitMacNames))

        elif arg == kMOTFOptions[kOmitMacNames][2]:
            kMOTFOptions[kOmitMacNames][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'None'" % (kFileOptPrefix,
                                                  kOmitMacNames))

        elif arg == kMOTFOptions[kOverrideMenuNames][1]:
            kMOTFOptions[kOverrideMenuNames][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kOverrideMenuNames))

        elif arg == kMOTFOptions[kOverrideMenuNames][2]:
            kMOTFOptions[kOverrideMenuNames][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'None'" % (kFileOptPrefix,
                                                  kOverrideMenuNames))

        elif arg == kMOTFOptions[kSuppressKernOptimization][1]:
            kMOTFOptions[kSuppressKernOptimization][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kSuppressKernOptimization))

        elif arg == kMOTFOptions[kSuppressKernOptimization][2]:
            kMOTFOptions[kSuppressKernOptimization][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'None'" % (kFileOptPrefix,
                                                  kSuppressKernOptimization))

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

        elif arg == kMOTFOptions[kConvertToCID][1]:
            kMOTFOptions[kConvertToCID][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kConvertToCID))

        # 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
            exec("makeOTFParams.%s%s = 'false'" % (kFileOptPrefix,
                                                   kConvertToCID))
        # -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
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kSuppressWidthOptimization))
        elif arg == kMOTFOptions[kSuppressWidthOptimization][2]:
            kMOTFOptions[kSuppressWidthOptimization][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'None'" % (kFileOptPrefix,
                                                  kSuppressWidthOptimization))

        elif arg == kMOTFOptions[kStubCmap4][1]:
            kMOTFOptions[kStubCmap4][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix, kStubCmap4))

        elif arg == kMOTFOptions[kAddStubDSIG][1]:
            kMOTFOptions[kAddStubDSIG][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kAddStubDSIG))

        elif arg == kMOTFOptions[kAddStubDSIG][2]:
            kMOTFOptions[kAddStubDSIG][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'false'" % (kFileOptPrefix,
                                                   kAddStubDSIG))

        elif arg == kMOTFOptions[kShowFinalNames][1]:
            kMOTFOptions[kShowFinalNames][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kShowFinalNames))

        elif arg == kMOTFOptions[kVerboseWarnings][1]:
            kMOTFOptions[kVerboseWarnings][0] = i + optionIndex
            exec("makeOTFParams.%s%s = 'true'" % (kFileOptPrefix,
                                                  kVerboseWarnings))
            makeOTFParams.verbose = True
        elif arg == kMOTFOptions[kVerboseWarnings][2]:
            kMOTFOptions[kVerboseWarnings][0] = None
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix,
                                                kVerboseWarnings))
            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 None

    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)
    return fontPath


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 = tounicode(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("makeotf [Warning] Could not find a Unicode Variation Sequence "
              "file in the expected '{}' subdirectory.".format(cmapPath))

    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 = 0

    if not (featurePath and os.path.exists(featurePath)):
        print("No feature path at", featurePath)
        return 0

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

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

    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("Could not find the include file '%s', referenced in "
                      "'%s'." % (_file, featurePath))
                return 0
        foundVert = checkIfVertInFeature(apath)
        if foundVert:
            break

    return foundVert


def setMissingParams(makeOTFParams):
    error = False
    # The path to the original src font file
    inputFilePath = eval("makeOTFParams.%s%s" % (kFileOptPrefix, kInputFont))
    srcFontPath = inputFilePath

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

        checkInputFile(makeOTFParams)

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

    # features path.
    path = eval("makeOTFParams.%s%s" % (kFileOptPrefix, kFeature))
    if not path:
        # look in font's home dir.
        path1 = os.path.join(makeOTFParams.fontDirPath, kDefaultFeaturesPath)
        if os.path.exists(path1):
            path = path1
        elif makeOTFParams.srcIsUFO:
            path1 = os.path.join(srcFontPath, kDefaultUFOFeaturesPath)
            if os.path.exists(path1):
                path = path1
        if not path:
            print("makeotf [Warning] Could not find default features file at "
                  "'%s'. Font will be built without any layout features." %
                  path)
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix, kFeature))
        else:
            exec("makeOTFParams.%s%s = path" % (kFileOptPrefix, kFeature))

    # FontMenuNameDB path.
    fmndb_path = getattr(makeOTFParams, kFileOptPrefix + kFMB)
    if fmndb_path == "None":
        setattr(makeOTFParams, kFileOptPrefix + kFMB, None)
    # it was not specified
    elif not fmndb_path:
        newpath = os.path.join(makeOTFParams.fontDirPath, kDefaultFMNDBPath)
        found_fmndb = False
        for path in [newpath, lookUpDirTree(newpath)]:
            if path and os.path.exists(path):
                found_fmndb = True
                break
        if found_fmndb:
            setattr(makeOTFParams, kFileOptPrefix + kFMB, 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)

    # 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:
        # need to figure out PS name in order to derive default output path.
        command = "%s -dump -0 \"%s\" 2>&1" % (makeOTFParams.txPath,
                                               inputFontPath)
        report = fdkutils.runShellCmd(command)

        match = re.search(r"(?:CID)?FontName\s+\"(\S+)\"", report)
        if not match:
            print("makeotf [Error] Could not find FontName (a.k.a. "
                  "PostScript name) in FontDict of file "
                  "'{}'".format(inputFilePath))
            raise MakeOTFTXError

        psName = match.group(1)

        if psName == 'PSNameNotSpecified':
            # 'tx' fills-in the PostScript font name if the UFO doesn't have it
            # https://github.com/adobe-type-tools/afdko/issues/437
            # This condition makes the UFO pipeline behave the same as Type 1
            print("makeotf [Error] Could not find 'postscriptFontName' "
                  "in file '{}'".format(inputFilePath))
            raise MakeOTFTXError

        if makeOTFParams.srcIsTTF or inputFontPath.lower().endswith(".ttf"):
            font_filename = "{}.ttf".format(psName)
        else:
            font_filename = "{}.otf".format(psName)

        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
    # Empty, but not None. Means we have already checked.
    makeOTFParams.tempFontPath = ""

    # Check if input is UFO font or TTF or OTF font
    if os.path.isdir(filePath):
        if os.path.exists(os.path.join(filePath, "fontinfo.plist")):
            needsConversion = True
            doSync = False
            allMatch, msgList = ufotools.checkHashMaps(filePath, doSync)
            if not allMatch:
                for msg in msgList:
                    print(msg)
                raise MakeOTFTXError
        elif os.path.exists(os.path.join(filePath, "glyphs",
                                         "contents.plist")):
            print("The source file looks like a UFO font, but makeotf "
                  "requires a fontinfo.plist, which this font does not have. "
                  "%s." % filePath)
            print("makeotf [Error] Failed to convert input font '%s' to temp "
                  "file." % filePath)
            raise MakeOTFTXError
        else:
            print("The source file is a directory, and is not a valid UFO "
                  "font - missing glyphs/contents.plist. %s." % filePath)
            print("makeotf [Error] Failed to convert input font '%s' to temp "
                  "file." % filePath)
            raise MakeOTFTXError
        makeOTFParams.srcIsUFO = 1

    else:
        commandString = "spot \"%s\" 2>&1" % filePath
        report = fdkutils.runShellCmd(commandString)
        if "sfnt" in report:
            needsConversion = True
            if "glyf" in report:
                makeOTFParams.srcIsTTF = 1
        else:
            # If not OTF or TTF, better be some legacy Type 1 font.
            try:
                with open(filePath, "rb") as fp:
                    data = tounicode(fp.read(1024))
                if ("%" not in data[:4]) and ('/FontName' not in data):
                    needsConversion = True
                else:
                    if "/Private" in data:
                        isTextPS = True
                        needsConversion = True
            except (IOError, OSError):
                print("makeotf [Error] Could not read font file at '%s'." %
                      filePath)
                raise MakeOTFTXError

        # Warn if there are any seac operators in the input file.
        if not (makeOTFParams.srcIsTTF or isTextPS):
            command = "tx -dump -5 -n \"%s\" 2>&1" % filePath
            report = fdkutils.runShellCmd(command)
            glyphList = re.findall(r"glyph[^{]+?{([^,]+),[^[\]]+\sseac\s",
                                   report)
            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()
            commandString = "type1 \"%s\" \"%s\"  2>&1" % (
                filePath, tempTxtPath)
            report1 = fdkutils.runShellCmd(commandString)
            if report1:
                report1 = "type1 output: <%s>" % report1
            commandString = "%s -t1 \"%s\" \"%s\"  2>&1" % (
                makeOTFParams.txPath, tempTxtPath, fontPath)
            report2 = fdkutils.runShellCmd(commandString)
            if report2:
                report2 = "tx output: <%s>" % report2
            report = "%s %s" % (report1, report2)

        else:
            if needsSEACRemoval:
                seacPath = fdkutils.get_temp_file_path()
                commandString = "%s -cff -Z +b \"%s\" \"%s\"  2>&1" % (
                    makeOTFParams.txPath, filePath, seacPath)
                report1 = fdkutils.runShellCmd(commandString)
                if report1:
                    report1 = "converting to CFF with seac removal: <%s>" % (
                        report1)

                commandString = "%s -t1 \"%s\" \"%s\"  2>&1" % (
                    makeOTFParams.txPath, seacPath, fontPath)
                report2 = fdkutils.runShellCmd(commandString)
                if report2:
                    report2 = "tx converting to T1 font: <%s>" % report2
                report = report1 + report2
            else:
                commandString = "%s -t1 \"%s\" \"%s\"  2>&1" % (
                    makeOTFParams.txPath, filePath, fontPath)
                report = fdkutils.runShellCmd(commandString)
                if report:
                    report = "tx converting to T1 font: <%s>" % report

        if ("fatal" in report) or ("error" in report):
            print("makeotf [Error] Failed to convert input font '%s' to temp "
                  "file '%s' with tx." % (filePath, fontPath))
            print(report)
            raise MakeOTFTXError

        makeOTFParams.tempFontPath = fontPath


def updateFontRevision(featuresPath, fontRevision):
    try:
        with open(featuresPath, "r", encoding='utf-8') as fp:
            data = fp.read()
    except (IOError, OSError):
        print("makeotf [Error] When trying to update the head table "
              "fontRevision field, failed to open '%s'." % featuresPath)
        return

    match = re.search(r"FontRevision\s+(\d+)\.(\d+);", data)
    if not match:
        print("makeotf [Error] When trying to update the head table "
              "fontRevision field, failed to find the fontRevision keyword in "
              "'%s'." % featuresPath)
        return
    fontRevision = eval(fontRevision)
    if isinstance(fontRevision, float):
        # Font Revision is a decimal value; replace the current font revision
        major = int(fontRevision)
        minor = int(0.5 + (fontRevision - major) * 1000)
        minor = str(minor).zfill(3)
    else:
        # fontRevision is an integer; increment the minor version.
        major = match.group(1)
        while major[0] == "0":
            major = major[1:]
        major = eval(major)

        minor = match.group(2)
        while minor[0] == "0":
            minor = minor[1:]
        minor = eval(minor)
        minor += fontRevision
        minor = str(minor).zfill(3)

    newData = re.sub(match.group(0), "FontRevision %s.%s;" % (major, minor),
                     data)
    try:
        with open(featuresPath, "wb") as fp:
            fp.write(tobytes(newData, encoding='utf-8'))
    except (IOError, 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):
    command = "spot -t OS/2 \"%s\" 2>&1" % outputPath
    report = fdkutils.runShellCmd(command)
    match = re.search(r"type\s+=(\S+)", report)
    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 = eval(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.
    command = "spot -t cmap=7 \"%s\" 2>&1" % inputFilePath
    report = fdkutils.runShellCmd(command)
    spotGlyphList = re.findall("[\n\t]\[(....+)\]=<([^>]+)>", report)

    # 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.
    command = "tx -mtx \"%s\" 2>&1" % inputFilePath
    report = fdkutils.runShellCmd(command)
    txGlyphList = re.findall("[\n\r]glyph\[(\d+)\]\s+{([^,]+)", report)

    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.

    command = "spot \"%s\"" % tempOutputPath
    tableDump = fdkutils.runShellCmd(command)

    # Get the final glyph name list.
    tempTablePath = fdkutils.get_temp_file_path()
    command = "tx -mtx \"%s\" 2>&1" % tempOutputPath
    report = fdkutils.runShellCmd(command)
    glyphList = re.findall("[\n\r]glyph\[\d+\]\s+{([^,]+)", report)

    if os.path.exists(outputPath):
        os.remove(outputPath)

    print("Fixing output font 'post' table...")
    fixPost(glyphList, inputFilePath, outputPath)
    print("Fixing output font 'head' table...")
    fixHead(tempOutputPath, outputPath)
    print("Fixing output font 'hhea' table...")
    fixHhea(tempOutputPath, outputPath)

    print("Copying makeotf-generated tables from temp OTF file to output "
          "font...")
    for tableTag in ["GDEF", "GSUB", "GPOS", "cmap", "name", "OS/2", "BASE"]:
        if tableTag not in tableDump:
            continue
        command = "sfntedit -x \"%s\"=\"%s\" \"%s\" 2>&1" % (
            tableTag, tempTablePath, tempOutputPath)
        log = fdkutils.runShellCmd(command)
        if not ("Done." in log):
            print(log)
            print("Error removing table '%s' from OTF font reference." %
                  tableTag)
            return

        command = "sfntedit -a \"%s\"=\"%s\" \"%s\" 2>&1" % (
            tableTag, tempTablePath, outputPath)
        log = fdkutils.runShellCmd(command)
        if not ("Done." in log):
            print(log)
            print("Error adding makeotf-made table '%s' to TrueType font." %
                  tableTag)
            return

        print("\tcopied \"%s\"." % tableTag)

    command = "sfntedit -f \"%s\" 2>&1" % outputPath
    fdkutils.runShellCmd(command)

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


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

    font = TTFont(inputFilePath)
    post_table = font['post']
    post_table.formatType = 2
    post_table.glyphOrder = glyphOrderList
    post_table.extraNames = extraNamesList
    font.save(outputPath)


def update_table_items(table_tag, items_list, font1_path, font2_path):
    """
    font1 will get new values from font2
    """
    font1 = TTFont(font1_path)
    font2 = TTFont(font2_path)

    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))

    font1.save(font1_path)
    font1.close()
    font2.close()


def fixHead(tempOTFFilePath, outputPath):
    """
    tempOTFFilePath is an OTF/CFF font.
    outputPath 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, outputPath, tempOTFFilePath)


def fixHhea(tempOTFFilePath, outputPath):
    hhea_items = ("ascent", "descent", "lineGap")
    update_table_items('hhea', hhea_items, outputPath, tempOTFFilePath)


def makeRelativePath(curDir, targetPath):
    if targetPath is None:
        return

    targetPath = os.path.abspath(targetPath)
    curDir = os.path.abspath(curDir)
    targetPath = os.path.relpath(targetPath, curDir)
    return targetPath


def makeRelativePaths(makeOTFParams):
    inputFilePath = getattr(makeOTFParams, kFileOptPrefix + kInputFont)
    fontDir = os.path.dirname(os.path.abspath(inputFilePath))

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

    outputFilePath = makeRelativePath(
        fontDir, getattr(makeOTFParams, kFileOptPrefix + kOutputFont))
    if outputFilePath:
        setattr(makeOTFParams, kFileOptPrefix + kOutputFont, outputFilePath)

    featuresFilePath = makeRelativePath(
        fontDir, getattr(makeOTFParams, kFileOptPrefix + kFeature))
    if featuresFilePath:
        setattr(makeOTFParams, kFileOptPrefix + kFeature, featuresFilePath)

    fmndbFilePath = makeRelativePath(
        fontDir, getattr(makeOTFParams, kFileOptPrefix + kFMB))
    if fmndbFilePath:
        setattr(makeOTFParams, kFileOptPrefix + kFMB, fmndbFilePath)

    goadbFilePath = makeRelativePath(
        fontDir, getattr(makeOTFParams, kFileOptPrefix + kGOADB))
    if goadbFilePath:
        setattr(makeOTFParams, kFileOptPrefix + kGOADB, goadbFilePath)

    maccmapFilePath = makeRelativePath(
        fontDir, getattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath))
    if maccmapFilePath:
        setattr(makeOTFParams, kFileOptPrefix + kMacCMAPPath, maccmapFilePath)

    uniHFilePath = makeRelativePath(
        fontDir, getattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath))
    if uniHFilePath:
        setattr(makeOTFParams, kFileOptPrefix + kHUniCMAPPath, uniHFilePath)

    uvsFilePath = makeRelativePath(
        fontDir, getattr(makeOTFParams, kFileOptPrefix + kUVSPath))
    if uvsFilePath:
        setattr(makeOTFParams, kFileOptPrefix + kUVSPath, uvsFilePath)

    if makeOTFParams.tempFontPath:
        makeOTFParams.tempFontPath = makeRelativePath(
            fontDir, 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 = makeRelativePaths(makeOTFParams)
    inputFilePath = eval("makeOTFParams.%s%s" % (kFileOptPrefix, kInputFont))

    # 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 = eval("makeOTFParams.%s%s" % (kFileOptPrefix,
                                                     kUseOldNameID4))
        if useOldNameID4:
            print("Because font is TTF, forcing use of new name table id 4 "
                  "format")
            exec("makeOTFParams.%s%s = None" % (kFileOptPrefix,
                                                kUseOldNameID4))
        # 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 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 = eval("makeOTFParams.%s%s" % (kFileOptPrefix, kRenumber))
    featuresPath = eval("makeOTFParams.%s%s" % (kFileOptPrefix, kFeature))
    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")
            exec("makeOTFParams.%s%s = featuresPath" % (kFileOptPrefix,
                                                        kFeature))
            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 = [
        makeOTFParams.makeotfPath,
        "\"%s\" \"%s\"" % (eval("kMOTFOptions[\"%s\"]" % (
            kInputFont))[1], inputFontPath),  # add temp font as the input font
        "\"%s\" \"%s\"" % (eval("kMOTFOptions[\"%s\"]" % (
            kOutputFont))[1], tempOutPath)  # add temp font as the input font
    ]
    # 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 = eval("makeOTFParams.%s%s" % (kFileOptPrefix, optionKey))
        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('%s "%s"' % (optionEntry[1], 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('%s "%s"' % (optionEntry[1], item))
        else:
            params.append('%s "%s"' % (optionEntry[1], val))

    commandString = " ".join(params)
    if makeOTFParams.verbose:
        print("makeotf [Note] Running %s with commands:" %
              os.path.basename(makeOTFParams.makeotfPath))
        print("   cd \"%s\"" % fontDir)
        print("   %s" % commandString)

    fdkutils.runShellCmdLogging(commandString)

    if not os.path.exists(tempOutPath) or (os.path.getsize(tempOutPath) < 500):
        print("makeotf [Error] Failed to build output font file '%s'." %
              tempOutPath)
        raise MakeOTFRunError

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

    if not os.path.exists(outputPath) or (os.path.getsize(outputPath) < 500):
        print("makeotf [Error] Failed to build output font file '%s'." %
              outputPath)
        raise MakeOTFRunError

    # 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:
            command = "spot -t head \"%s\" 2>&1" % outputPath
            report = fdkutils.runShellCmd(command)
            match = re.search(r"fontRevision\s+=(\S+)", report)
            if not match:
                print("makeotf [Error] Could not find fontRevision in spot "
                      "dump of head table of file %s." % outputPath)
                raise MakeOTFTXError
            print("Built release mode font '%s' Revision %s" % (
                outputPath, match.group(1)))
        except ValueError:
            print("makeotf [Error] Could not extract version number from "
                  "file at '%s'." % outputPath)
    else:
        print("Built development mode font '%s'." % outputPath)

    os.chdir(curdir)


def CheckEnvironment():

    missingTools = []
    for name in ["tx", "makeotfexe"]:
        command = "%s -u 2>&1" % name
        report = fdkutils.runShellCmd(command)
        if ("options" not in report) and ("Option" not in report):
            missingTools.append(name)

    if missingTools:
        print("Please check your PATH, and if necessary re-install the FDK. "
              "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:
        makeOTFParams.txPath = "tx"
        makeOTFParams.makeotfPath = "makeotfexe"
        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, MakeOTFTXError, MakeOTFRunError):
        return 1
    except Exception:
        raise


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