Created
March 6, 2022 14:02
-
-
Save SamusAranX/182cc80ecb99cbe787f1e17df67f0efd to your computer and use it in GitHub Desktop.
A script that scans directories containing fonts and generates a CSS file containing @font-face blocks for each of them, complete with somewhat correct naming and font-style/-stretch/-weight values. To make this work with .woff2 files, run a search-and-replace on the resulting file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
import argparse | |
from glob import glob | |
from os.path import join, relpath | |
from pathlib import Path | |
from fontTools.ttLib import TTFont | |
FONT_SPECIFIER_NAME_ID = 4 | |
FONT_SPECIFIER_FAMILY_ID = 1 | |
FONT_WIDTHS = { | |
1: "ultra-condensed", | |
2: "extra-condensed", | |
3: "condensed", | |
4: "semi-condensed", | |
5: "normal", | |
6: "semi-expanded", | |
7: "expanded", | |
8: "extra-expanded", | |
9: "ultra-expanded", | |
} | |
class FontInfo: | |
@staticmethod | |
def font_short_name(font: TTFont) -> (str, str): | |
def get_record_string(record) -> str: | |
try: | |
if b"\x00" in record.string: | |
return record.string.decode("utf-16-be") | |
else: | |
return record.string.decode("utf-8") | |
except UnicodeError: | |
return record.string.decode("latin-1") | |
name = "" | |
family = "" | |
for record in font["name"].names: | |
if record.nameID == FONT_SPECIFIER_NAME_ID and not name: | |
name = get_record_string(record) | |
elif record.nameID == FONT_SPECIFIER_FAMILY_ID and not family: | |
family = get_record_string(record) | |
if name and family: | |
break | |
return name, family | |
@staticmethod | |
def font_modifiers(font) -> (int, bool, int): | |
return ( | |
# weight as an integer | |
font["OS/2"].usWeightClass, | |
# italic | |
(font["OS/2"].fsSelection & 1 or font["head"].macStyle & 2) == 1, | |
# width as an integer | |
font["OS/2"].usWidthClass, | |
) | |
def __init__(self, font: TTFont): | |
self.name, self.family = self.font_short_name(font) | |
self.weight, self.italic, self.width = self.font_modifiers(font) | |
def __str__(self): | |
return f"{self.family}/{self.name} ({self.weight}, {self.italic}, {self.width})" | |
def main(args): | |
indir = args.indir | |
outfile = args.outfile | |
patterns = ["*.ttf", "*.otf"] | |
input_fonts = [] | |
if args.recursive: | |
print(f"Recursively scanning directory {indir}") | |
for p in patterns: | |
for m in Path(indir).rglob(p): | |
input_fonts.append(str(m.resolve())) | |
input_fonts = list(set(input_fonts)) | |
else: | |
print(f"Scanning directory {indir}") | |
for p in patterns: | |
input_fonts += glob(join(indir, p)) | |
input_fonts = list(set(input_fonts)) | |
if not input_fonts: | |
print("No fonts found") | |
return | |
font_infos = [] | |
for f in input_fonts: | |
ttfont = TTFont(f) | |
font_infos.append((f, FontInfo(ttfont),)) | |
font_infos.sort(key=lambda ft: (ft[1].family.split()[0], ft[1].width, ft[1].weight, ft[1].italic)) | |
with open(outfile, "w", encoding="utf8") as css: | |
for font_path, font_info in font_infos: | |
print(font_info) | |
css.write("@font-face {\n") | |
css.write(f"\tfont-family: \"{font_info.family}\";\n") | |
if font_info.italic: | |
css.write(f"\tfont-style: italic;\n") | |
if font_info.width != 5: | |
font_width = FONT_WIDTHS[font_info.width] | |
css.write(f"\tfont-stretch: {font_width};\n") | |
if font_info.weight != 400: | |
css.write(f"\tfont-weight: {font_info.weight};\n") | |
font_path_rel = relpath(font_path, indir) | |
css.write(f"\tsrc: url(\"{font_path_rel}\") format(\"truetype\");\n") | |
css.write("}\n") | |
css.write("\n") | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="@font-face generator") | |
parser.add_argument("--indir", "-i", type=str, required=True, help="Input directory") | |
parser.add_argument("--outfile", "-o", type=str, required=True, help="Output CSS file") | |
parser.add_argument("-r", "--recursive", action="store_true", help="Recursively scan subdirectories") | |
main(parser.parse_args()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment