Exemple #1
0
 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}"
     )
Exemple #2
0
    def set_variations(self, axes):
        """Instantiate a ttfont VF with axes vals"""
        logger.debug("Setting variations to {}".format(axes))
        if self.is_variable:
            font = instantiateVariableFont(self._src_ttfont,
                                           axes,
                                           inplace=False)
            self.ttfont = copy(font)
            self.axis_order = [
                a.axisTag for a in self._src_ttfont['fvar'].axes
            ]
            self.instance_coordinates = {
                a.axisTag: a.defaultValue
                for a in self._src_ttfont['fvar'].axes
            }
            for axis in axes:
                if axis in self.instance_coordinates:
                    self.instance_coordinates[axis] = axes[axis]
                else:
                    logger.info("font has no axis called {}".format(axis))
            self.recalc_tables()

            coords = []
            for name in self.axis_order:
                coord = FT_Fixed(int(self.instance_coordinates[name]) << 16)
                coords.append(coord)
            ft_coords = (FT_Fixed * len(coords))(*coords)
            FT_Set_Var_Design_Coordinates(self.ftfont._FT_Face, len(ft_coords),
                                          ft_coords)
            self.hbface = hb.Face.create(self._fontdata)
            self.hbfont = hb.Font.create(self.hbface)
            self.hbfont.set_variations(self.instance_coordinates)
            self.hbfont.scale = (self.size, self.size)
        else:
            logger.info("Not vf")
Exemple #3
0
def extractFontFromOpenType(
    pathOrFile,
    destination,
    doGlyphOrder=True,
    doGlyphs=True,
    doInfo=True,
    doKerning=True,
    customFunctions=[],
    doInstructions=True,
):
    source = TTFont(pathOrFile)
    if doInfo:
        extractOpenTypeInfo(source, destination)
    if doGlyphs:
        extractOpenTypeGlyphs(source, destination)
        if isVarFont:
            '''
            Add Support for extracting Variable Instances as RLayer objects
            Currently this is pulling the VFs instances but I think using the Sources is a better solution?
            '''
            defLoc = getDefaultLocation(source)
            locations = [instance.coordinates for instance in source["fvar"].instances]
            for varLoc in locations:
                if varLoc != defLoc:
                    instanceFont = instantiateVariableFont(source,varLoc)
                    layerName = locationToName(varLoc)
                    extractOpenTypeGlyphs(instanceFont, destination, layerName)

        extractUnicodeVariationSequences(source, destination)
    if doGlyphOrder:
        extractGlyphOrder(source, destination)
    if doKerning:
        kerning, groups = extractOpenTypeKerning(source, destination)
        destination.groups.update(groups)
        destination.kerning.clear()
        destination.kerning.update(kerning)
    for function in customFunctions:
        function(source, destination)
    if doInstructions:
        if not isVarFont:
            '''
            seems to run into issue with extracting VF component indentifier
            '''
            extractInstructions(source, destination)
    source.close()
Exemple #4
0
    def test_kerning_merging(self):
        """Test the correct merging of class-based pair kerning.

        Problem description at https://github.com/fonttools/fonttools/pull/1638.
        Test font and Designspace generated by 
        https://gist.github.com/madig/183d0440c9f7d05f04bd1280b9664bd1.
        """
        ds_path = self.get_test_input("KerningMerging.designspace")
        ttx_dir = self.get_test_input("master_kerning_merging")

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            ttx_dump = TTFont()
            ttx_dump.importXML(
                os.path.join(
                    ttx_dir, os.path.basename(source.filename).replace(".ttf", ".ttx")
                )
            )
            source.font = reload_font(ttx_dump)

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)

        class_kerning_tables = [
            t
            for l in varfont["GPOS"].table.LookupList.Lookup
            for t in l.SubTable
            if t.Format == 2
        ]
        assert len(class_kerning_tables) == 1
        class_kerning_table = class_kerning_tables[0]

        # Test that no class kerned against class zero (containing all glyphs not
        # classed) has a `XAdvDevice` table attached, which in the variable font
        # context is a "VariationIndex" table and points to kerning deltas in the GDEF
        # table. Variation deltas of any kerning class against class zero should
        # probably never exist.
        for class1_record in class_kerning_table.Class1Record:
            class2_zero = class1_record.Class2Record[0]
            assert getattr(class2_zero.Value1, "XAdvDevice", None) is None

        # Assert the variable font's kerning table (without deltas) is equal to the
        # default font's kerning table. The bug fixed in 
        # https://github.com/fonttools/fonttools/pull/1638 caused rogue kerning
        # values to be written to the variable font.
        assert _extract_flat_kerning(varfont, class_kerning_table) == {
            ("A", ".notdef"): 0,
            ("A", "A"): 0,
            ("A", "B"): -20,
            ("A", "C"): 0,
            ("A", "D"): -20,
            ("B", ".notdef"): 0,
            ("B", "A"): 0,
            ("B", "B"): 0,
            ("B", "C"): 0,
            ("B", "D"): 0,
        }

        instance_thin = instantiateVariableFont(varfont, {"wght": 100})
        instance_thin_kerning_table = (
            instance_thin["GPOS"].table.LookupList.Lookup[0].SubTable[0]
        )
        assert _extract_flat_kerning(instance_thin, instance_thin_kerning_table) == {
            ("A", ".notdef"): 0,
            ("A", "A"): 0,
            ("A", "B"): 0,
            ("A", "C"): 10,
            ("A", "D"): 0,
            ("B", ".notdef"): 0,
            ("B", "A"): 0,
            ("B", "B"): 0,
            ("B", "C"): 10,
            ("B", "D"): 0,
        }

        instance_black = instantiateVariableFont(varfont, {"wght": 900})
        instance_black_kerning_table = (
            instance_black["GPOS"].table.LookupList.Lookup[0].SubTable[0]
        )
        assert _extract_flat_kerning(instance_black, instance_black_kerning_table) == {
            ("A", ".notdef"): 0,
            ("A", "A"): 0,
            ("A", "B"): 0,
            ("A", "C"): 0,
            ("A", "D"): 40,
            ("B", ".notdef"): 0,
            ("B", "A"): 0,
            ("B", "B"): 0,
            ("B", "C"): 0,
            ("B", "D"): 40,
        }
def makeTTFInstancesFromVF(family):
    axesName = dict()
    path, folder = getFile(".designspace", family)
    designSpace = openDesignSpace(path)
    loca = dict()
    maps = dict()
    for a in designSpace.axes:
        if a.map:
            maps[a.name] = a.map
    varFontPath = folder + "/fonts/VAR/" + family + "-VF.ttf"
    if not os.path.exists(varFontPath):
        print("Make Variable first")
        makeVariableFonts(family)
    varFont = TTFont(varFontPath)
    revision = varFont['head'].fontRevision
    V_major = str(revision).split(".")[0]
    V_minor = str(revision).split(".")[1]
    #store axis name and axis tag in a dict for correspondance
    for a in designSpace.axes:
        axesName[a.name] = a.tag
    # read the location of instance and put in a new dict the tag
    # corresponding to the axis name as k and the location value as v
    for instance in designSpace.instances:
        location = dict(instance.location)
        for name in location:
            if name in maps:
                for i in maps[name]:
                    if int(location[name]) == int(i[1]):
                        locationValue = i[0]
                        loca[axesName[name]] = round(locationValue)
            else:
                loca[axesName[name]] = int(location[name])
        print(instance.name, loca)
        # ''.join(instance.familyName.split(' '))
        fontName = '-'.join([''.join(instance.familyName.split(' ')), \
                                instance.styleName.replace(" ", "")]) + '.ttf'
        fi = instantiateVariableFont(varFont, loca, inplace=False)
        #build name table entries
        styleName = instance.styleName
        familyName = instance.familyName
        for namerecord in fi['name'].names:
            if namerecord.nameID == 1:
                if styleName in ["Bold", "Regular", "Italic", "Bold Italic"]:
                    namerecord.string = familyName
                else:
                    namerecord.string = familyName + " " + styleName
                    n16 = makeName(familyName, 16, 3, 1, 0x409)
                    n17 = (makeName(styleName, 17, 3, 1, 0x409))
                    fi['name'].names.append(n16)
                    fi['name'].names.append(n17)
            if namerecord.nameID == 2:
                if styleName == "Bold":
                    namerecord.string = "Bold"
                elif styleName == "Italic":
                    namerecord.string = "Italic"
                elif styleName == "Bold Italic":
                    namerecord.string = "Bold Italic"
                elif "Italic" in styleName:
                    namerecord.string = "Italic"
                else:
                    namerecord.string = "Regular"
            if namerecord.nameID == 5:
                namerecord.string = 'Version %s.%s' % (V_major, V_minor)
            if namerecord.nameID == 3:
                PSName = ''.join(familyName.split(' ')) + '-' + ''.join(
                    styleName.split(' '))
                namerecord.string = '%s.%s' % (
                    V_major, V_minor) + ';' + '%s' % PSName + ';'
            if namerecord.nameID == 4:
                namerecord.string = familyName + " " + styleName
            if namerecord.nameID == 6:
                namerecord.string = ''.join(
                    familyName.split(' ')) + '-' + ''.join(
                        styleName.split(' '))
        destination = folder + "/fonts/instances/"
        if not os.path.exists(destination):
            os.makedirs(destination)
        fi.save(os.path.join(destination, fontName))
def makeOneInstanceFromVF(family, loca):
    family = "".join(family.split(" "))
    # print(family)
    path, folder = getFile(".designspace", family)
    ds = openDesignSpace(path)
    axesName = dict()
    ax = dict()
    maps = dict()
    for a in ds.axes:
        ax[a.tag] = a.name
        axesName[a.name] = a.tag
        if a.map:
            maps[a.name] = a.map
    locaStyleName, familyName = stockDSstylename(ds)
    location = str(loca)
    ##### READ THE MAP #####
    for tag in loca:
        print(">>> Now creating the", loca, "instance of " + family)
        name = ax[tag]
        if name in maps:
            for i in maps[name]:
                if int(loca[tag]) == int(i[1]):
                    locationValue = i[0]
                    loca[tag] = round(locationValue)
        else:
            pass
    for i in ax:
        location = location.replace(i, ax[i])
    styleName = locaStyleName[location]
    destination = folder + "/fonts/static/"
    if not os.path.exists(destination):
        os.makedirs(destination)
    varFontPath = folder + "/fonts/VAR/" + family + "-VF.ttf"
    if not os.path.exists(varFontPath):
        print("\nMake Variable first")
        makeVariableFonts(family)
    varFont = TTFont(varFontPath)
    revision = varFont['head'].fontRevision
    V_major = str(revision).split(".")[0]
    V_minor = str(revision).split(".")[1]
    fontName = '-'.join(
        [familyName.replace(" ", ""),
         styleName.replace(" ", "")]) + '.ttf'
    # familyName = instance.familyName
    static = instantiateVariableFont(varFont, loca, inplace=False)
    # print(static['name'].names)
    #<NameRecord NameID=14; PlatformID=3; LanguageID=1033>
    for namerecord in static['name'].names:
        if namerecord.nameID == 1:
            if styleName in ["Bold", "Regular", "Italic", "Bold Italic"]:
                namerecord.string = familyName
            else:
                namerecord.string = familyName + " " + styleName
                n16_p3 = makeName(familyName, 16, 3, 1, 0x409)
                n16_p1 = makeName(familyName, 16, 1, 0, 0x0)
                static['name'].names.append(n16_p3)
                static['name'].names.append(n16_p1)
                n17_p3 = (makeName(styleName, 17, 3, 1, 0x409))
                n17_p1 = (makeName(styleName, 17, 1, 0, 0x0))
                static['name'].names.append(n17_p3)
                static['name'].names.append(n17_p1)
        if namerecord.nameID == 2:
            if styleName in ["Bold", "Regular", "Italic", "Bold Italic"]:
                namerecord.string = styleName
            elif "Italic" in styleName:
                namerecord.string = "Italic"
            else:
                namerecord.string = "Regular"
        if namerecord.nameID == 5:
            namerecord.string = 'Version %s.%s' % (V_major, V_minor)
        if namerecord.nameID == 3:
            PSName = ''.join(familyName.split(' ')) + '-' + ''.join(
                styleName.split(' '))
            namerecord.string = '%s.%s' % (V_major,
                                           V_minor) + ';' + '%s' % PSName + ';'
        if namerecord.nameID == 4:
            namerecord.string = familyName + " " + styleName
        if namerecord.nameID == 6:
            namerecord.string = ''.join(familyName.split(' ')) + '-' + ''.join(
                styleName.split(' '))
        if namerecord.nameID == 16:
            namerecord.string = familyName
        if namerecord.nameID == 17:
            namerecord.string = styleName
    static.save(os.path.join(destination, fontName))
Exemple #7
0
ttFont = TTFont(path)

glyphSet = ttFont.getGlyphSet()

data = {}
for key in glyphSet.keys():
    data[key] = {}

# DEFAULT
for key in glyphSet.keys():
    g = glyphSet[key]
    data[key]["default"] = g.width

# MAX
location = {"XOPQ": xopq, "YOPQ": yopq, "XTRA": xtramax}
maxfont = instantiateVariableFont(ttFont, location)
maxGlyphSet = maxfont.getGlyphSet()

for key in maxGlyphSet.keys():
    g = maxGlyphSet[key]
    data[key]["max"] = g.width

# MIN
location = {"XOPQ": xopq, "YOPQ": yopq, "XTRA": xtramin}
minfont = instantiateVariableFont(ttFont, location)
minGlyphSet = minfont.getGlyphSet()

for key in minGlyphSet.keys():
    g = minGlyphSet[key]
    data[key]["min"] = g.width
Exemple #8
0
def main():
    parser = argparse.ArgumentParser(
        description="A variable font to static instance generator.")
    parser.add_argument(
        "-x",
        "--expression",
        default=DEFAULT_EXPRESSION,
        type=float,
        help="Expression axis value ({}-{})".format(EXPRESSION_MIN,
                                                    EXPRESSION_MAX),
    )  # xprn
    parser.add_argument(
        "-w",
        "--weight",
        default=DEFAULT_WEIGHT,
        type=int,
        help="Weight axis value ({}-{})".format(WEIGHT_MIN, WEIGHT_MAX),
    )  # wght
    parser.add_argument(
        "-s",
        "--slant",
        default=DEFAULT_SLANT,
        type=float,
        help="Slant axis value ({}-{})".format(SLANT_MIN, SLANT_MAX),
    )  # slnt
    parser.add_argument(
        "-i",
        "--italic",
        default=DEFAULT_SLANT,
        type=float,
        help="Italic axis value ({}-{})".format(ITALIC_MIN, ITALIC_MAX),
    )  # slnt
    parser.add_argument(
        "-v",
        "--version",
        action="version",
        version="vf2s {}".format(SCRIPT_VERSION),
        help="Display application version",
    )
    parser.add_argument(
        "--style",
        default="Regular",
        type=str,
        help=
        "Style linking style name: Italic, Bold, Bold Italic, etc (default is 'Regular')",
    )
    parser.add_argument("path", help="Variable font path")

    args = parser.parse_args()

    instance_location = {}
    # axis value validity testing and location definitions
    if args.weight is not None:
        if args.weight < WEIGHT_MIN or args.weight > WEIGHT_MAX:
            sys.stderr.write(
                "Weight axis value must be in the range {} - {}{}".format(
                    WEIGHT_MIN, WEIGHT_MAX, os.linesep))
            sys.exit(1)
        else:
            instance_location["wght"] = args.weight
    if args.expression is not None:
        if args.expression < EXPRESSION_MIN or args.expression > EXPRESSION_MAX:
            sys.stderr.write(
                "Expression axis value must be in the range {} - {}{}".format(
                    EXPRESSION_MIN, EXPRESSION_MAX, os.linesep))
            sys.exit(1)
        else:
            instance_location["XPRN"] = args.expression
    if args.slant is not None:
        # slant is negative, 0 to -15
        # SLANT_MIN = 0.0
        # SLANT_MAX = -15.0
        if args.slant > SLANT_MIN or args.slant < SLANT_MAX:
            sys.stderr.write(
                "Slant axis value must be in the range {} - {}{}".format(
                    SLANT_MIN, SLANT_MAX, os.linesep))
            sys.exit(1)
        else:
            instance_location["slnt"] = args.slant
        instance_location["slnt"] = args.slant
    if args.italic is not None:
        if args.italic < ITALIC_MIN or args.italic > ITALIC_MAX:
            sys.stderr.write(
                "Italic axis value must be in the range {} - {}{}".format(
                    ITALIC_MIN, ITALIC_MAX, os.linesep))
            sys.exit(1)
        else:
            instance_location["ital"] = args.italic

    # variable font path check
    if not os.path.exists(args.path):
        sys.stderr.write(
            "{} does not appear to be a valid path to a variable font{}".
            format(args.path, os.linesep))
        sys.exit(1)

    # instantiate the variable font with the requested values
    font = TTFont(args.path)
    instantiateVariableFont(font,
                            instance_location,
                            inplace=True,
                            overlap=False)
    print(f"Generating static instance at {instance_location}")

    # ---------------------------------------------------------------
    # rewrite name table records with new name values for A/B testing
    # ---------------------------------------------------------------

    namerecord_list = font["name"].names

    # create a name string from the axis location parameters
    axis_param_string = ""
    for axis_value in instance_location:
        axis_param_string += "{}{}".format(axis_value,
                                           instance_location[axis_value])

    # map axis name to an abbreviation in font path and name table record string values
    axis_param_string = axis_param_string.replace("XPRN", "xp")
    axis_param_string = axis_param_string.replace("wght", "wg")
    axis_param_string = axis_param_string.replace("slnt", "sl")
    axis_param_string = axis_param_string.replace("ital", "it")

    # name definitions (NEEDS TO BE MODIFIED TO SUPPORT STYLES OTHER THAN REGULAR)
    nameID1_name = "{}".format(FONTNAME)
    nameID2_name = "{}".format(args.style)
    nameID4_name = "{} {}".format(FONTNAME, args.style)
    nameID6_name = "{}-{}".format(FONTNAME, args.style)
    outfont_name = "{}-{}-{}.ttf".format(FONTNAME, axis_param_string,
                                         args.style)
    outfont_path = os.path.join(os.path.dirname(os.path.abspath(args.path)),
                                outfont_name.replace(" ", "_"))

    # TODO: update unique font identifier, name 3

    for record in namerecord_list:
        if record.nameID == 1:
            record.string = nameID1_name
        elif record.nameID == 2:
            record.string = nameID2_name
        elif record.nameID == 4:
            record.string = nameID4_name
        elif record.nameID == 6:
            record.string = nameID6_name.replace(" ", "")
        elif record.nameID == 16:
            record.string = nameID1_name
        elif record.nameID == 17:
            record.string = nameID2_name

    # Set the macOS overlap rendering bit
    # addresses bug in overlap path rendering on macOS web browsers
    font = set_mac_overlap_rendering_bit(font)

    # write the instance font to disk
    try:
        font.save(outfont_path)
        print("[New instance]: {}".format(outfont_path))
    except Exception as e:
        sys.stderr.write(
            "Failed to write font file {} with error: {}{}".format(
                outfont_name, str(e), os.linesep))
        sys.exit(1)