# Source code for fontTools.cffLib.CFFToCFF2

    """CFF to CFF2 converter."""

    from fontTools.ttLib import TTFont, newTable
    from fontTools.misc.cliTools import makeOutputFileName
    from fontTools.misc.psCharStrings import T2WidthExtractor
    from fontTools.cffLib import (
        TopDictIndex,
        FDArrayIndex,
        FontDict,
        buildOrder,
        topDictOperators,
        privateDictOperators,
        topDictOperators2,
        privateDictOperators2,
    )
    from io import BytesIO
    import logging

    __all__ = ["convertCFFToCFF2", "main"]

    log = logging.getLogger("fontTools.cffLib")

    class _NominalWidthUsedError(Exception):
        def __add__(self, other):
            raise self

        def __radd__(self, other):
            raise self

    def _convertCFFToCFF2(cff, otFont):
        """Converts this object from CFF format to CFF2 format. This conversion
        is done 'in-place'. The conversion cannot be reversed.

        This assumes a decompiled CFF table. (i.e. that the object has been
        filled via :meth:`decompile` and e.g. not loaded from XML.)"""

        # Clean up T2CharStrings

        topDict = cff.topDictIndex[0]
        fdArray = topDict.FDArray if hasattr(topDict, "FDArray") else None
        charStrings = topDict.CharStrings
        globalSubrs = cff.GlobalSubrs
        localSubrs = (
            [getattr(fd.Private, "Subrs", []) for fd in fdArray]
            if fdArray
            else (
                [topDict.Private.Subrs]
                if hasattr(topDict, "Private") and hasattr(topDict.Private, "Subrs")
                else []
            )
        )

        for glyphName in charStrings.keys():
            cs, fdIndex = charStrings.getItemAndSelector(glyphName)
            cs.decompile()

        # Clean up subroutines first
        for subrs in [globalSubrs] + localSubrs:
            for subr in subrs:
                program = subr.program
                i = j = len(program)
                try:
                    i = program.index("return")
                except ValueError:
                    pass
                try:
                    j = program.index("endchar")
                except ValueError:
                    pass
                program[min(i, j) :] = []

        # Clean up glyph charstrings
        removeUnusedSubrs = False
        nominalWidthXError = _NominalWidthUsedError()
        for glyphName in charStrings.keys():
            cs, fdIndex = charStrings.getItemAndSelector(glyphName)
            program = cs.program

            thisLocalSubrs = (
                localSubrs[fdIndex]
                if fdIndex is not None
                else (
                    getattr(topDict.Private, "Subrs", [])
                    if hasattr(topDict, "Private")
                    else []
                )
            )

            # Intentionally use custom type for nominalWidthX, such that any
            # CharString that has an explicit width encoded will throw back to us.
            extractor = T2WidthExtractor(
                thisLocalSubrs,
                globalSubrs,
                nominalWidthXError,
                0,
            )
            try:
                extractor.execute(cs)
            except _NominalWidthUsedError:
                # Program has explicit width. We want to drop it, but can't
                # just pop the first number since it may be a subroutine call.
                # Instead, when seeing that, we embed the subroutine and recurse.
                # If this ever happened, we later prune unused subroutines.
                while len(program) >= 2 and program[1] in ["callsubr", "callgsubr"]:
                    removeUnusedSubrs = True
                    subrNumber = program.pop(0)
                    assert isinstance(subrNumber, int), subrNumber
                    op = program.pop(0)
                    bias = extractor.localBias if op == "callsubr" else extractor.globalBias
                    subrNumber += bias
                    subrSet = thisLocalSubrs if op == "callsubr" else globalSubrs
                    subrProgram = subrSet[subrNumber].program
                    program[:0] = subrProgram
                # Now pop the actual width
                assert len(program) >= 1, program
                program.pop(0)

            if program and program[-1] == "endchar":
                program.pop()

        if removeUnusedSubrs:
            cff.remove_unused_subroutines()

        # Upconvert TopDict

        cff.major = 2
        cff2GetGlyphOrder = cff.otFont.getGlyphOrder
        topDictData = TopDictIndex(None, cff2GetGlyphOrder)
        for item in cff.topDictIndex:
            # Iterate over, such that all are decompiled
            topDictData.append(item)
        cff.topDictIndex = topDictData
        topDict = topDictData[0]
        if hasattr(topDict, "Private"):
            privateDict = topDict.Private
        else:
            privateDict = None
        opOrder = buildOrder(topDictOperators2)
        topDict.order = opOrder
        topDict.cff2GetGlyphOrder = cff2GetGlyphOrder

        if not hasattr(topDict, "FDArray"):
            fdArray = topDict.FDArray = FDArrayIndex()
            fdArray.strings = None
            fdArray.GlobalSubrs = topDict.GlobalSubrs
            topDict.GlobalSubrs.fdArray = fdArray
            charStrings = topDict.CharStrings
            if charStrings.charStringsAreIndexed:
                charStrings.charStringsIndex.fdArray = fdArray
            else:
                charStrings.fdArray = fdArray
            fontDict = FontDict()
            fontDict.setCFF2(True)
            fdArray.append(fontDict)
            fontDict.Private = privateDict
            privateOpOrder = buildOrder(privateDictOperators2)
            if privateDict is not None:
                for entry in privateDictOperators:
                    key = entry[1]
                    if key not in privateOpOrder:
                        if key in privateDict.rawDict:
                            # print "Removing private dict", key
                            del privateDict.rawDict[key]
                        if hasattr(privateDict, key):
                            delattr(privateDict, key)
                            # print "Removing privateDict attr", key
        else:
            # clean up the PrivateDicts in the fdArray
            fdArray = topDict.FDArray
            privateOpOrder = buildOrder(privateDictOperators2)
            for fontDict in fdArray:
                fontDict.setCFF2(True)
                for key in list(fontDict.rawDict.keys()):
                    if key not in fontDict.order:
                        del fontDict.rawDict[key]
                        if hasattr(fontDict, key):
                            delattr(fontDict, key)

                privateDict = fontDict.Private
                for entry in privateDictOperators:
                    key = entry[1]
                    if key not in privateOpOrder:
                        if key in list(privateDict.rawDict.keys()):
                            # print "Removing private dict", key
                            del privateDict.rawDict[key]
                        if hasattr(privateDict, key):
                            delattr(privateDict, key)
                            # print "Removing privateDict attr", key

        # Now delete up the deprecated topDict operators from CFF 1.0
        for entry in topDictOperators:
            key = entry[1]
            # We seem to need to keep the charset operator for now,
            # or we fail to compile with some fonts, like AdditionFont.otf.
            # I don't know which kind of CFF font those are. But keeping
            # charset seems to work. It will be removed when we save and
            # read the font again.
            #
            # AdditionFont.otf has <Encoding name="StandardEncoding"/>.
            if key == "charset":
                continue
            if key not in opOrder:
                if key in topDict.rawDict:
                    del topDict.rawDict[key]
                if hasattr(topDict, key):
                    delattr(topDict, key)

        # TODO(behdad): What does the following comment even mean? Both CFF and CFF2
        # use the same T2Charstring class. I *think* what it means is that the CharStrings
        # were loaded for CFF1, and we need to reload them for CFF2 to set varstore, etc
        # on them. At least that's what I understand. It's probably safe to remove this
        # and just set vstore where needed.
        #
        # See comment above about charset as well.

        # At this point, the Subrs and Charstrings are all still T2Charstring class
        # easiest to fix this by compiling, then decompiling again
        file = BytesIO()
        cff.compile(file, otFont, isCFF2=True)
        file.seek(0)
        cff.decompile(file, otFont, isCFF2=True)

    def convertCFFToCFF2(font):
        cff = font["CFF "].cff
        del font["CFF "]
        _convertCFFToCFF2(cff, font)
        table = font["CFF2"] = newTable("CFF2")
        table.cff = cff

    def main(args=None):
        """Convert CFF OTF font to CFF2 OTF font"""
        if args is None:
            import sys

            args = sys.argv[1:]

        import argparse

        parser = argparse.ArgumentParser(
            "fonttools cffLib.CFFToCFF2",
            description="Upgrade a CFF font to CFF2.",
        )
        parser.add_argument(
            "input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
        )
        parser.add_argument(
            "-o",
            "--output",
            metavar="OUTPUT.ttf",
            default=None,
            help="Output instance OTF file (default: INPUT-CFF2.ttf).",
        )
        parser.add_argument(
            "--no-recalc-timestamp",
            dest="recalc_timestamp",
            action="store_false",
            help="Don't set the output font's timestamp to the current time.",
        )
        loggingGroup = parser.add_mutually_exclusive_group(required=False)
        loggingGroup.add_argument(
            "-v", "--verbose", action="store_true", help="Run more verbosely."
        )
        loggingGroup.add_argument(
            "-q", "--quiet", action="store_true", help="Turn verbosity off."
        )
        options = parser.parse_args(args)

        from fontTools import configLogger

        configLogger(
            level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
        )

        import os

        infile = options.input
        if not os.path.isfile(infile):
            parser.error("No such file '{}'".format(infile))

        outfile = (
            makeOutputFileName(infile, overWrite=True, suffix="-CFF2")
            if not options.output
            else options.output
        )

        font = TTFont(infile, recalcTimestamp=options.recalc_timestamp, recalcBBoxes=False)

        convertCFFToCFF2(font)

        log.info(
            "Saving %s",
            outfile,
        )
        font.save(outfile)

    if __name__ == "__main__":
        import sys

        sys.exit(main(sys.argv[1:]))