Ejemplo n.º 1
0
def _update_fvar_nametable_records(ttFont, stat_table):
    """Add postscript names to fvar instances and add nameID 25 to a
    font's nametable"""
    nametable = ttFont["name"]
    fvar = ttFont["fvar"]
    family_name = font_familyname(ttFont)
    axes_with_one_axis_value = [
        a["values"][0] for a in stat_table if len(a["values"]) == 1
    ]
    tokens = [v["name"] for v in axes_with_one_axis_value]
    tokens = [t for t in tokens if t not in family_name.split()]
    ps_tokens = "".join(t for t in tokens)

    # Variations PostScript Name Prefix
    ps_prefix = f"{family_name}{ps_tokens}".replace(" ", "")
    for rec in [(25, 1, 0, 0), (25, 3, 1, 0x409)]:
        nametable.setName(ps_prefix, *rec)

    # Add or update fvar instance postscript names
    for instance in fvar.instances:
        subfamily_id = instance.subfamilyNameID
        subfamily_name = nametable.getName(subfamily_id, 3, 1, 0x409).toUnicode()
        for token in tokens:
            subfamily_name = subfamily_name.replace(token, "")
            if subfamily_name == "":
                subfamily_name = "Regular"
        ps_name = f"{ps_prefix}-{subfamily_name}".replace(" ", "")
        # Remove ps name records if they already exist
        if instance.postscriptNameID != 65535:
            nametable.removeNames(nameID=instance.postscriptNameID)
        instance.postscriptNameID = nametable.addName(ps_name)
Ejemplo n.º 2
0
def css_font_classes_from_vf(ttFont, position=None):
    instances = ttFont["fvar"].instances
    nametable = ttFont["name"]
    family_name = font_familyname(ttFont)
    style_name = font_stylename(ttFont)

    results = []
    for instance in instances:
        nameid = instance.subfamilyNameID
        inst_style = nametable.getName(nameid, 3, 1, 0x409).toUnicode()

        class_name = _class_name(family_name, inst_style, position)
        font_family = _class_name(family_name, style_name, position)
        font_weight = (css_font_weight(ttFont)
                       if not "wght" in instance.coordinates else int(
                           instance.coordinates["wght"]))
        font_style = "italic" if "Italic" in inst_style else "normal"
        font_stretch = ("100%" if not "wdth" in instance.coordinates else
                        f"{int(instance.coordinates['wdth'])}%")
        font_class = CSSElement(
            class_name,
            _full_name=f"{family_name} {inst_style}",
            _style=inst_style,
            _font_path=ttFont.reader.file.name,
            font_family=font_family,
            font_weight=font_weight,
            font_style=font_style,
            font_stretch=font_stretch,
        )
        results.append(font_class)
    return results
Ejemplo n.º 3
0
def _append_non_fvar_axes_to_stat(
    ttFont, stat_table, axes_in_family_name_records, axis_reg=axis_registry
):
    stylename = font_stylename(ttFont)
    familyname = font_familyname(ttFont)
    style = f"{familyname} {stylename}"
    # {"wght": "Regular", "ital": "Roman", ...}
    font_axes_in_namerecords = {style_token_to_axis(t): t for t in style.split()}

    # Add axes to ttFont which exist across the family but are not in the
    # ttFont's fvar
    axes_missing = axes_in_family_name_records - set(stat_table)
    for axis in axes_missing:
        axis_record = {
            "tag": axis,
            "name": axis_reg[axis].display_name,
            "values": [],
        }
        # Add Axis Value for axis which isn't in the fvar or ttFont style
        # name/family name
        if axis not in font_axes_in_namerecords:
            axis_record["values"].append(_default_axis_value(axis, axis_reg))
        # Add Axis Value for axis which isn't in the fvar but does exist in
        # the ttFont style name/family name
        else:
            style_name = font_axes_in_namerecords[axis]
            value = next(
                (i.value for i in axis_reg[axis].fallback if i.name == style_name),
                None,
            )
            axis_value = _add_axis_value(style_name, value)
            axis_record["values"].append(axis_value)
        stat_table[axis] = axis_record
    return stat_table
Ejemplo n.º 4
0
def inherit_vertical_metrics(ttFonts, family_name=None):
    """Inherit the vertical metrics from the same family which is
    hosted on Google Fonts.

    Args:
        ttFonts: a list of TTFont instances which belong to a family
        family_name: Optional string which allows users to specify a
            different family to inherit from e.g "Maven Pro".
    """
    family_name = font_familyname(
        ttFonts[0]) if not family_name else family_name

    gf_fonts = list(map(TTFont,
                        download_family_from_Google_Fonts(family_name)))
    gf_fonts = {font_stylename(f): f for f in gf_fonts}
    # TODO (Marc F) use Regular font instead. If VF use font which has Regular
    # instance
    gf_fallback = list(gf_fonts.values())[0]

    fonts = {font_stylename(f): f for f in ttFonts}
    for style, font in fonts.items():
        if style in gf_fonts:
            src_font = gf_fonts[style]
        else:
            src_font = gf_fallback
        copy_vertical_metrics(src_font, font)

        if typo_metrics_enabled(src_font):
            font["OS/2"].fsSelection |= 1 << 7
Ejemplo n.º 5
0
    def instantiate_static_fonts(self, directory, postprocessor):
        self.mkdir(directory, clean=True)
        for font in self.config["instances"]:
            varfont_path = os.path.join(self.config['vfDir'], font)
            varfont = TTFont(varfont_path)
            for font, instances in self.config["instances"].items():
                for inst in instances:
                    if 'familyName' in inst:
                        family_name = inst['familyName']
                    else:
                        family_name = self.config['familyName']
                    if "styleName" in inst:
                        style_name = inst['styleName']
                    else:
                        style_name = None

                    static_font = gen_static_font(
                        varfont,
                        axes=inst["coordinates"],
                        family_name=family_name,
                        style_name=style_name,
                    )
                    family_name = font_familyname(static_font)
                    style_name = font_stylename(static_font)
                    dst = os.path.join(
                        directory,
                        f"{family_name}-{style_name}.ttf".replace(" ", ""))
                    static_font.save(dst)
                    postprocessor(dst)
Ejemplo n.º 6
0
def _axes_in_family_name_records(ttFonts):
    results = set()
    for ttFont in ttFonts:
        familyname = font_familyname(ttFont)
        stylename = font_stylename(ttFont)
        results |= set(stylename_to_axes(familyname)) | set(
            stylename_to_axes(stylename))
    return results
Ejemplo n.º 7
0
def css_font_faces(ttFonts, server_dir=None, position=None):
    """Generate @font-face CSSElements for a collection of fonts

    Args:
      ttFonts: a list containing ttFont instances
      server_dir: optional. A path to the root directory of the server.
        @font-face src urls are relative to the server's root dir.
      position: optional. Adds a suffix to the font-family name

    Returns:
      A list of @font-face CSSElements
    """
    results = []
    for ttFont in ttFonts:
        family_name = font_familyname(ttFont)
        style_name = font_stylename(ttFont)
        font_path = ttFont.reader.file.name
        path = (font_path if not server_dir else os.path.relpath(
            font_path, start=server_dir))
        src = f"url({path})"
        font_family = _class_name(family_name, style_name, position)
        font_style = "italic" if font_is_italic(ttFont) else "normal"
        font_weight = css_font_weight(ttFont)
        font_stretch = WIDTH_CLASS_TO_CSS[ttFont["OS/2"].usWidthClass]

        if "fvar" in ttFont:
            fvar = ttFont["fvar"]
            axes = {a.axisTag: a for a in fvar.axes}
            if "wght" in axes:
                min_weight = int(axes["wght"].minValue)
                max_weight = int(axes["wght"].maxValue)
                font_weight = f"{min_weight} {max_weight}"
            if "wdth" in axes:
                min_width = int(axes["wdth"].minValue)
                max_width = int(axes["wdth"].maxValue)
                font_stretch = f"{min_width}% {max_width}%"
            if "ital" in axes:
                pass
            if "slnt" in axes:
                min_angle = int(axes["slnt"].minValue)
                max_angle = int(axes["slnt"].maxValue)
                font_style = f"oblique {min_angle}deg {max_angle}deg"

        font_face = CSSElement(
            "@font-face",
            src=src,
            font_family=font_family,
            font_weight=font_weight,
            font_stretch=font_stretch,
            font_style=font_style,
        )
        results.append(font_face)
    return results
Ejemplo n.º 8
0
def fix_nametable(ttFont):
    """Fix a static font's name table so it conforms to the Google Fonts
    supported styles table:
    https://github.com/googlefonts/gf-docs/tree/main/Spec#supported-styles

    Args:
        ttFont: a TTFont instance
    """
    if "fvar" in ttFont:
        from fontTools.varLib.instancer.names import updateNameTable
        dflt_axes = {a.axisTag: a.defaultValue for a in ttFont['fvar'].axes}
        updateNameTable(ttFont, dflt_axes)
        return
    family_name = font_familyname(ttFont)
    style_name = font_stylename(ttFont)
    update_nametable(ttFont, family_name, style_name)
Ejemplo n.º 9
0
def fix_nametable(ttFont):
    """Fix a static font's name table so it conforms to the Google Fonts
    supported styles table:
    https://github.com/googlefonts/gf-docs/tree/master/Spec#supported-styles

    Args:
        ttFont: a TTFont instance
    """
    if "fvar" in ttFont:
        # TODO, regen the nametable so it reflects the default fvar axes
        # coordinates. Implement once https://github.com/fonttools/fonttools/pull/2078
        # is merged.
        return
    family_name = font_familyname(ttFont)
    style_name = font_stylename(ttFont)
    update_nametable(ttFont, family_name, style_name)
Ejemplo n.º 10
0
def gen_static_font(
    var_font, axes, family_name=None, style_name=None, keep_overlaps=False, dst=None
):
    """Generate a GF spec compliant static font from a variable font.

    Args:
        var_font: a variable TTFont instance
        family_name: font family name
        style_name: font style name
        axes: dictionary containing axis positions e.g {"wdth": 100, "wght": 400}
        keep_overlaps: If true, keep glyph overlaps
        dst: Optional. Path to output font

    Returns:
        A TTFont instance or a filepath if an out path has been provided
    """
    if "fvar" not in var_font:
        raise ValueError("Font is not a variable font!")
    if not keep_overlaps:
        keep_overlaps = OverlapMode.REMOVE

    # if the axes dict doesn't include all fvar axes, add default fvar vals to it
    fvar_dflts = {a.axisTag: a.defaultValue for a in var_font["fvar"].axes}
    for k, v in fvar_dflts.items():
        if k not in axes:
            axes[k] = v

    update_style_name = True if not style_name else False
    static_font = instantiateVariableFont(
        var_font, axes, overlap=keep_overlaps, updateFontNames=update_style_name
    )

    if not family_name:
        family_name = font_familyname(static_font)
    if not style_name:
        style_name = font_stylename(static_font)
    # We need to reupdate the name table using our own update function
    # since GF requires axis particles which are not wght or ital to
    # be appended to the family name. See func for more details.
    update_nametable(static_font, family_name, style_name)
    fix_fs_selection(static_font)
    fix_mac_style(static_font)
    static_font["OS/2"].usWidthClass = 5
    if dst:
        static_font.save(dst)
    return static_font
Ejemplo n.º 11
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("font")
    parser.add_argument("new_name")
    parser.add_argument("-o", "--out")
    args = parser.parse_args()

    font = TTFont(args.font)
    current_name = font_familyname(font)
    rename_font(font, args.new_name)

    if args.out:
        out = args.out
    else:
        out = args.font.replace(current_name.replace(" ", ""),
                                args.new_name.replace(" ", ""))
    print("Saving font: {}".format(out))
    font.save(out)
Ejemplo n.º 12
0
def css_font_class_from_static(ttFont, position=None):
    family_name = font_familyname(ttFont)
    style_name = font_stylename(ttFont)

    class_name = _class_name(family_name, style_name, position)
    font_family = class_name
    font_weight = css_font_weight(ttFont)
    font_style = "italic" if font_is_italic(ttFont) else "normal"
    font_stretch = WIDTH_CLASS_TO_CSS[ttFont["OS/2"].usWidthClass]
    return CSSElement(
        class_name,
        _full_name=f"{family_name} {style_name}",
        _style=style_name,
        _font_path=ttFont.reader.file.name,
        font_family=font_family,
        font_weight=font_weight,
        font_style=font_style,
        font_stretch=font_stretch,
    )
Ejemplo n.º 13
0
def fix_family(fonts, include_source_fixes=False):
    """Fix all fonts in a family"""
    validate_family(fonts)
    family_name = font_familyname(fonts[0])

    for font in fonts:
        fix_font(font, include_source_fixes=include_source_fixes)

    if include_source_fixes:
        try:
            if Google_Fonts_has_family(family_name):
                inherit_vertical_metrics(fonts)
            else:
                log.warning(f"{family_name} is not on Google Fonts. Skipping "
                            "regression fixes")
        except FileNotFoundError:
            log.warning(
                f"Google Fonts api key not found so we can't regression "
                "fix fonts. See Repo readme to add keys.")
        fix_vertical_metrics(fonts)
Ejemplo n.º 14
0
def update_nametable(ttFont, family_name=None, style_name=None):
    """Update a static font's name table. The updated name table will conform
    to the Google Fonts support styles table:
    https://github.com/googlefonts/gf-docs/tree/main/Spec#supported-styles

    If a style_name includes tokens other than wght and ital, these tokens
    will be appended to the family name e.g

    Input:
    family_name="MyFont"
    style_name="SemiCondensed SemiBold"

    Output:
    familyName (nameID 1) = "MyFont SemiCondensed SemiBold
    subFamilyName (nameID 2) = "Regular"
    typo familyName (nameID 16) = "MyFont SemiCondensed"
    typo subFamilyName (nameID 17) = "SemiBold"

    Google Fonts has used this model for several years e.g
    https://fonts.google.com/?query=cabin

    Args:
        ttFont:
        family_name: New family name
        style_name: New style name
    """
    if "fvar" in ttFont:
        raise ValueError("Cannot update the nametable for a variable font")
    nametable = ttFont["name"]

    # Remove nametable records which are not Win US English
    # TODO this is too greedy. We should preserve multilingual
    # names in the future. Please note, this has always been an issue.
    platforms = set()
    for rec in nametable.names:
        platforms.add((rec.platformID, rec.platEncID, rec.langID))
    platforms_to_remove = platforms ^ set([(3, 1, 0x409)])
    if platforms_to_remove:
        log.warning(
            f"Removing records which are not Win US English, {list(platforms_to_remove)}"
        )
        for platformID, platEncID, langID in platforms_to_remove:
            nametable.removeNames(platformID=platformID,
                                  platEncID=platEncID,
                                  langID=langID)

    # Remove any name records which contain linebreaks
    contains_linebreaks = []
    for r in nametable.names:
        for char in ("\n", "\r"):
            if char in r.toUnicode():
                contains_linebreaks.append(r.nameID)
    for nameID in contains_linebreaks:
        nametable.removeNames(nameID)

    if not family_name:
        family_name = font_familyname(ttFont)

    if not style_name:
        style_name = font_stylename(ttFont)

    ribbi = ("Regular", "Bold", "Italic", "Bold Italic")
    tokens = family_name.split() + style_name.split()

    nameids = {
        1:
        " ".join(t for t in tokens if t not in ribbi),
        2:
        " ".join(t for t in tokens if t in ribbi) or "Regular",
        16:
        " ".join(t for t in tokens
                 if t not in list(WEIGHT_NAMES) + ['Italic']),
        17:
        " ".join(t for t in tokens if t in list(WEIGHT_NAMES) + ['Italic'])
        or "Regular"
    }
    # Remove typo name if they match since they're redundant
    if nameids[16] == nameids[1]:
        del nameids[16]
    if nameids[17] == nameids[2]:
        del nameids[17]

    family_name = nameids.get(16) or nameids.get(1)
    style_name = nameids.get(17) or nameids.get(2)

    # create NameIDs 3, 4, 6
    nameids[4] = f"{family_name} {style_name}"
    nameids[
        6] = f"{family_name.replace(' ', '')}-{style_name.replace(' ', '')}"
    nameids[3] = unique_name(ttFont, nameids)

    # Pass through all records and replace occurences of the old family name
    # with the new family name
    current_family_name = font_familyname(ttFont)
    for record in nametable.names:
        string = record.toUnicode()
        if current_family_name in string:
            nametable.setName(
                string.replace(current_family_name, family_name),
                record.nameID,
                record.platformID,
                record.platEncID,
                record.langID,
            )

    # Remove previous typographic names
    for nameID in (16, 17):
        nametable.removeNames(nameID=nameID)

    # Update nametable with new names
    for nameID, string in nameids.items():
        nametable.setName(string, nameID, 3, 1, 0x409)