Пример #1
0
def flattenGlyphs(input, fontNumber, output):
    font = TTFont(input, fontNumber=fontNumber)
    font.recalcBBoxes = False
    if "glyf" in font:
        for glyphName in font.getGlyphOrder():
            glyph = font["glyf"][glyphName]
            coordinates, endPtsOfContours, flags = glyph.getCoordinates(
                font["glyf"])
            glyph.numberOfContours = len(endPtsOfContours)
            glyph.coordinates = coordinates
            glyph.endPtsOfContours = endPtsOfContours
            glyph.flags = flags
            glyph.program = ttProgram.Program()
            font["glyf"][glyphName] = glyph
    elif "CFF " in font:
        cff = font["CFF "]
        fontName = cff.cff.fontNames[0]
        topDict = cff.cff[fontName]
        for glyphID in range(len(font.getGlyphOrder())):
            charString = topDict.CharStrings.charStringsIndex[glyphID]
            charString.decompile()
            localSubrs = getattr(charString.private, "Subrs", [])
            globalSubrs = charString.globalSubrs
            inlinedProgram = inlineProgram(localSubrs, globalSubrs,
                                           charString.program)
            charString.program = inlinedProgram
        if "Private" in topDict.rawDict and "Subrs" in topDict.Private.rawDict:
            topDict.Private.Subrs
            del topDict.Private.rawDict["Subrs"]
            del topDict.Private.Subrs
        topDict.GlobalSubrs.items = []
    else:
        raise FlattenError("Could not flatten glyphs.")

    font.save(output)
Пример #2
0
def reorderFont(input, fontNumber, desiredGlyphOrder, output):
    font = TTFont(input, fontNumber=fontNumber, lazy=False)
    font = fullyLoadFont(font)

    if "CFF " in font:
        cff = font["CFF "]
        fontName = cff.cff.fontNames[0]
        topDict = cff.cff[fontName]
        topDict.compilerClass = ReorderedTopDictCompiler

    glyphOrder = font.getGlyphOrder()
    reorderedGlyphs = reorderGlyphs(glyphOrder, desiredGlyphOrder)
    if reorderedGlyphs is None:
        return False

    font.setGlyphOrder(reorderedGlyphs)
    # Glyph order is cached in a few places, clear those out.
    if "glyf" in font:
        del font["glyf"].glyphOrder
    if hasattr(font, "_reverseGlyphOrderDict"):
        del font._reverseGlyphOrderDict

    # CFF stores glyph order internally, set it:
    if "CFF " in font:
        cff = font["CFF "]
        fontName = cff.cff.fontNames[0]
        topDict = cff.cff[fontName]
        topDict.charset = reorderedGlyphs

    fixLayoutCoverage(font)

    tmp = BytesIO()
    font.save(tmp)

    tableOrder = font.reader.keys()
    success = computeTableOrder(tableOrder)
    if not success:
        tmp.close()
        return False
    outputStream = open(output, "wb")
    reorderFontTables(tmp, outputStream, tableOrder)
    tmp.close()
    outputStream.close()

    return True
Пример #3
0
class VTT_updater:
    def __init__(self,
                 path_old: Union[Path, str],
                 path_new: Union[Path, str],
                 log: bool = True) -> None:

        self.font_old = TTFont(str(path_old))
        self.font_old.cmap = {
            v: k
            for k, v in self.path_old.getBestCmap().items()
        }
        self.font_old.go = self.path_old.getGlyphOrder()
        self.font_old.path = path_old

        self.font_new = TTFont(str(path_new))
        self.font_new.cmap = self.path_new.getBestCmap()
        self.font_new.go = self.path_new.getGlyphOrder()
        self.font_new.path = path_new

        self.log = True

        self.name_map = {
            k: self.font_new.cmap.get(v)
            for k, v in self.font_old.cmap.items()
        }
        self.name_map.update({
            i: i
            for i in filter(lambda x: x in self.font_new.go, self.font_old.go)
        })
        self.incompatible_glyphs = self.get_incompatible_glyphs()

    def get_glyph_map(self) -> dict:
        glyph_map = {}
        for i, g_new in enumerate(self.font_new.go):
            if g_new in self.font_old.go:
                glyph_map[self.font_old.index(g_new)] = i
            return glyph_map

    def get_incompatible_glyphs(self) -> list:
        incompatible_glyphs = list(
            filter(lambda x: not self.name_map[x] in self.font_new.go,
                   self.font_old.go))
        for g_name in self.font_old.go:
            if g_name not in incompatible_glyphs:
                g_old = self.font_old["glyf"][g_name]
                g_new = self.font_new["glyf"][self.name_map[g_name]]

                pen_old = RecordingPen()
                g_old.draw(pen_old, self.font_old["glyf"])

                pen_new = RecordingPen()
                g_new.draw(pen_new, self.font_new["glyf"])

                for (pt_old, *_), (pt_new,
                                   *_) in zip_longest(pen_old.value,
                                                      pen_new.value,
                                                      fillvalue=[None, None]):
                    if pt_old != pt_new:
                        if self.log:
                            print(
                                f"{g_name}/{self.name_map[g_name]} has an incompatible contour"
                            )
                        break
                else:
                    if hasattr(g_old, "components") and hasattr(
                            g_new, "components"):
                        for comp_old, comp_new in zip_longest(
                                g_old.components, g_new.components):
                            if comp_old.glyphName != comp_new.glyphName:
                                if self.log:
                                    print(
                                        f"{g_name}/{self.name_map[g_name]} has incompatible components"
                                    )
                                break
                    continue
                incompatible_glyphs.append(g_name)
        return incompatible_glyphs

    def update_assembly(self) -> None:
        for g_name in self.font_old.go:
            if g_name not in self.incompatible_glyphs:
                g_old = self.font_old["glyf"][g_name]
                if hasattr(g_old, "program"):
                    g_new = self.font_new["glyf"][self.name_map[g_name]]
                    g_new.program = g_old.program
        return None

    def update_glyph_programs(self, font) -> None:
        pattern = r"(.*OFFSET\[[r,R]\]\ ?),.*"
        for key in font["TSI1"].glyphPrograms:
            glyph_program = font["TSI1"].glyphPrograms[key].replace("\r", "\n")
            matches = list(re.finditer(pattern, glyph_program))
            if matches:
                components = self.font_new["glyf"][
                    self.name_map[key]].components
                for match, component in zip(matches[::-1], components[::-1]):
                    left, right = match.span(0)
                    command = match.group(1)
                    g_name, (*transformations, x,
                             y) = component.getComponentInfo()
                    assembly = [x, y]
                    if transformations != [1, 0, 0, 1]:
                        assembly.extend(transformations)
                    assembly = list(map(str, assembly))
                    gid = self.font_new.go.index(g_name)
                    new_command = f"{command}, {gid}, {', '.join(assembly)}"
                    glyph_program = (glyph_program[:left] + new_command +
                                     glyph_program[right:])
                font["TSI1"].glyphPrograms[key] = glyph_program.replace(
                    "\n", "\r")
        return None

    def _filter_glyphs(self, dict_data) -> dict:
        new_dict_data = {}
        keys = [i for i in dict_data.keys()]
        for key in keys:
            if key not in self.incompatible_glyphs:
                new_dict_data[self.name_map[key]] = dict_data[key]
        return new_dict_data

    def update_TSI_tables(self) -> None:

        self.font_new["TSI0"] = self.font_old["TSI0"]  # empty...
        self.font_new["TSI1"] = self.font_old["TSI1"]  # assembly
        self.font_new["TSI2"] = self.font_old["TSI2"]  # empty...
        self.font_new["TSI3"] = self.font_old["TSI3"]  # VTT talks
        self.font_new["TSI5"] = self.font_old["TSI5"]  # glyph groups
        self.font_new["cvt "] = self.font_old["cvt "]  # cvts
        self.font_new["prep"] = self.font_old["prep"]  # prep
        self.font_new["fpgm"] = self.font_old["fpgm"]  # fpgm

        self.font_new["TSI1"].glyphPrograms = self._filter_glyphs(
            self.font_new["TSI1"].glyphPrograms)
        self.font_new["TSI3"].glyphPrograms = self._filter_glyphs(
            self.font_new["TSI3"].glyphPrograms)
        return None

    def update_table_entries(self) -> None:
        update = dict(
            maxp=[
                "maxSizeOfInstructions",
                "maxFunctionDefs",
                "maxStorage",
                "maxStackElements",
                "maxZones",
                "maxTwilightPoints",
            ],
            head=["checkSumAdjustment"],
        )

        for table, attributes in update.items():
            for attribute in attributes:
                setattr(
                    self.font_new[table],
                    attribute,
                    getattr(self.font_old[table], attribute),
                )

        self.font_new["head"].flags |= 1 << 3

        return None

    def update(self) -> None:
        self.update_assembly()
        self.update_glyph_programs(self.font_old)
        self.update_TSI_tables()
        self.update_table_entries()
        return None

    def write(self, save_as: Union[Path, str, bool] = None) -> None:
        if save_as:
            self.font_new.save(str(save_as))
        else:
            self.font_new.save(str(self.font_new.path))
        return None
Пример #4
0
class InstanceWorker(QRunnable):
    def __init__(
        self,
        outpath=None,
        font_model=None,
        axis_model=None,
        name_model=None,
        bit_model=None,
    ):
        super().__init__()
        self.signals = InstanceWorkerSignals()
        self.outpath = outpath
        self.font_model = font_model
        self.axis_model = axis_model
        self.name_model = name_model
        self.bit_model = bit_model
        self.ttfont = None

    @pyqtSlot()
    def run(self):
        try:
            # Debugging in stdout
            print(f"\n\n{datetime.datetime.now()}")
            self.ttfont = TTFont(self.font_model.fontpath)
            # instantiate
            self.instantiate_variable_font()
            # edit name table records
            self.edit_name_table()
            # edit bit flags
            self.edit_bit_flags()
            # write to disk
            self.ttfont.save(self.outpath)
        except Exception as e:
            self.signals.error.emit(f"{e}")
            sys.stderr.write(f"{traceback.format_exc()}\n")
        else:
            # returns the file out file path on success
            self.signals.result.emit(self.outpath)
            self.signals.finished.emit()

    def instantiate_variable_font(self):
        axis_instance_data = self.axis_model.get_instance_data()
        instantiateVariableFont(self.ttfont, axis_instance_data, inplace=True)
        print("\nAXIS INSTANCE VALUES")
        print(
            f"Instantiated variable font with axis definitions:\n{axis_instance_data}"
        )

    def edit_name_table(self):
        # string, nameID, platformID, platEncID, langID
        name_record_plat_enc_lang = (3, 1, 1033)
        name_instance_data = self.name_model.get_instance_data()
        name_table = self.ttfont["name"]
        # set 3, 1, 1033 name records (only!)
        # mandatory writes
        name_table.setName(name_instance_data["nameID1"], 1, *name_record_plat_enc_lang)
        name_table.setName(name_instance_data["nameID2"], 2, *name_record_plat_enc_lang)
        name_table.setName(name_instance_data["nameID3"], 3, *name_record_plat_enc_lang)
        name_table.setName(name_instance_data["nameID4"], 4, *name_record_plat_enc_lang)
        name_table.setName(name_instance_data["nameID6"], 6, *name_record_plat_enc_lang)

        # optional writes
        # Approach:
        # (1) if user text data exists, write it
        # (2) if user text data does not exist but record does, delete it
        # (3) otherwise do nothing
        if name_instance_data["nameID16"] != "":
            name_table.setName(
                name_instance_data["nameID16"], 16, *name_record_plat_enc_lang
            )
        elif name_table.getName(16, *name_record_plat_enc_lang):
            name_table.removeNames(16, *name_record_plat_enc_lang)

        if name_instance_data["nameID17"] != "":
            name_table.setName(
                name_instance_data["nameID17"], 17, *name_record_plat_enc_lang
            )
        elif name_table.getName(17, *name_record_plat_enc_lang):
            name_table.removeNames(17, *name_record_plat_enc_lang)

        if name_instance_data["nameID21"] != "":
            name_table.setName(
                name_instance_data["nameID21"], 21, *name_record_plat_enc_lang
            )
        elif name_table.getName(21, *name_record_plat_enc_lang):
            name_table.removeNames(21, *name_record_plat_enc_lang)

        if name_instance_data["nameID22"] != "":
            name_table.setName(
                name_instance_data["nameID22"], 22, *name_record_plat_enc_lang
            )
        elif name_table.getName(22, *name_record_plat_enc_lang):
            name_table.removeNames(22, *name_record_plat_enc_lang)

        # update name table data
        self.ttfont["name"] = name_table

        # print name table report
        print("\nNAME TABLE EDITS")
        print("Name records at write time:\n")
        print(f"nameID1: {self.ttfont['name'].getName(1, *name_record_plat_enc_lang)}")
        print(f"nameID2: {self.ttfont['name'].getName(2, *name_record_plat_enc_lang)}")
        print(f"nameID3: {self.ttfont['name'].getName(3, *name_record_plat_enc_lang)}")
        print(f"nameID4: {self.ttfont['name'].getName(4, *name_record_plat_enc_lang)}")
        print(f"nameID6: {self.ttfont['name'].getName(6, *name_record_plat_enc_lang)}")
        print(
            f"nameID16: {self.ttfont['name'].getName(16, *name_record_plat_enc_lang)}"
        )
        print(
            f"nameID17: {self.ttfont['name'].getName(17, *name_record_plat_enc_lang)}"
        )
        print(
            f"nameID21: {self.ttfont['name'].getName(21, *name_record_plat_enc_lang)}"
        )
        print(
            f"nameID22: {self.ttfont['name'].getName(22, *name_record_plat_enc_lang)}"
        )

    def edit_bit_flags(self):
        # edit the OS/2.fsSelection bit flag
        pre_os2_fsselection_int = self.ttfont["OS/2"].fsSelection
        edited_os2_fsselection_int = self.bit_model.edit_os2_fsselection_bits(
            pre_os2_fsselection_int
        )
        # edit OS/2.fsSelection in the TTFont attribute
        self.ttfont["OS/2"].fsSelection = edited_os2_fsselection_int

        # edit head.macstyle bit flag
        pre_head_macstyle_int = self.ttfont["head"].macStyle
        edited_head_macstyle_int = self.bit_model.edit_head_macstyle_bits(
            pre_head_macstyle_int
        )
        self.ttfont["head"].macStyle = edited_head_macstyle_int

        # bit flag debugging stdout report
        print("\nBIT FLAGS")
        print(
            f"\nOS/2.fsSelection updated with the following data:\n"
            f"{self.bit_model.get_os2_instance_data()}"
        )
        print(f"Pre OS/2.fsSelection:  {num2binary(pre_os2_fsselection_int, bits=16)}")
        print(
            f"Post OS/2.fsSelection: {num2binary(self.ttfont['OS/2'].fsSelection, bits=16)}"
        )
        print(
            f"\nhead.macStyle bit flag updated with the following data:\n"
            f"{self.bit_model.get_head_instance_data()}"
        )
        print(f"Pre head.macStyle:  {num2binary(pre_head_macstyle_int, bits=16)}")
        print(
            f"Post head.macStyle: {num2binary(self.ttfont['head'].macStyle, bits=16)}"
        )