#!/usr/bin/fontforge -lang=py # vim: ft=python:ts=4:sw=4:et:ai:cin # Build LeanConkyConfig font - requiring FontForge to run import argparse import logging import sys import os.path as osp import tempfile from glob import glob from datetime import date import fontforge as ff import psMat tmp_dir = tempfile.gettempdir() # Font Awesome settings FA_URL = ( "https://github.com/FortAwesome/Font-Awesome/releases/download/" + "6.1.1/fontawesome-free-6.1.1-desktop.zip" ) FA_DIR = osp.join(tmp_dir, "lcc_fontawesome") FA_SUBSET = """ f007 #user f013 #cog f015 #home f017 #clock f019 #download f01c #inbox f073 #calendar f085 #cogs f093 #upload f0a0 #hdd f0ac #globe f0e8 #sitemap f108 #desktop f1c0 #database f1eb #wifi f233 #server f293 #bluetooth f2db #microchip f2f5 #sign-out-alt f2f6 #sign-in-alt f381 #cloud-download-alt f382 #cloud-upload-alt f3c5 #map-marker-alt f538 #memory f56e #file-export f56f #file-import f6ff #network-wired f7c2 #sd-card """ # Bootstrap font settings FB_URL = "https://github.com/twbs/icons/raw/v1.8.2/font/fonts/bootstrap-icons.woff" FB_FILE = osp.join(tmp_dir, "bootstrap-icons.woff") FB_SUBSET = """ f6e2 #gpu-card """ # parse command line parser = argparse.ArgumentParser(description="Build LeanConkyConfig font file.") parser.add_argument( "-verbose", help="output debug information", action="count", default=0 ) parser.add_argument("-lcd-font", help="lcd font file", default="TRS-Million-mod.otf") parser.add_argument("-output", help="output file", default="lean-conky-config.otf") args = parser.parse_args() # logger logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, style="{", format="{asctime} {message}", stream=sys.stdout, ) logging.info("Start build custom font") def parse_subset(subset): parsed = [] for l in subset.split("\n"): x = l.split("#", maxsplit=1)[0].strip() if x: parsed.append(int(x, base=16)) return parsed def copy_attrs(src, dst, attrs): for a in attrs: v = getattr(src, a) logging.debug("%s: %s => %s", a, getattr(dst, a), v) setattr(dst, a, v) def merge_font(dst_font, src_font, selection=None): if selection: src_font.selection.select(*selection) dst_font.selection.select(*selection) else: src_font.selection.all() dst_font.selection.all() src_font.copy() dst_font.paste() def copy_glyphnames(dst_font, src_font, unicodes): logging.debug( "Copy glyphnames: {} -> {}".format(src_font.fontname, dst_font.fontname) ) logging.debug("---------------------") common_codes = [k for k in unicodes if k in src_font and k in dst_font] for k in common_codes: src_name = src_font[k].glyphname logging.debug("{} -> {}".format(src_name, dst_font[k].glyphname)) dst_font[k].glyphname = src_name logging.debug("---------------------") # fetch FA if needed if osp.isdir(FA_DIR): logging.debug("FA font cache found: {}".format(FA_DIR)) else: fa_download = FA_DIR + ".zip" if osp.isfile(fa_download): logging.debug("FA font already downloaded: {}".format(fa_download)) else: import urllib.request urllib.request.urlretrieve(FA_URL, fa_download) logging.info("Downloaded FA font: {}".format(fa_download)) import zipfile with zipfile.ZipFile(fa_download, "r") as z: z.extractall(FA_DIR, [f for f in z.namelist() if f.endswith(".otf")]) logging.debug("Extracted .otf files from FA") # subset and merge fonts # FA solid fa = glob(osp.join(FA_DIR, "**/*Solid*"), recursive=True) if fa: fa = ff.open(fa[0]) else: raise FileNotFoundError("FA-Solid font not found") nf = ff.font() nf.fontname = nf.familyname = nf.fullname = "LeanConkyConfig" nf.version = date.today().strftime("%Y%m%d") with open("./LICENSE", "r") as f: nf.copyright = f.read().strip() logging.info("Copy essential FA font attributes") copy_attrs(fa, nf, ["encoding", "em", "ascent", "descent"]) fa_subset = parse_subset(FA_SUBSET) sel = [("unicode", "singletons")] + fa_subset logging.info("Subset and merge FA-Solid font") merge_font(nf, fa, sel) copy_glyphnames(nf, fa, fa_subset) # FA brands fa = glob(osp.join(FA_DIR, "**/*Brands*"), recursive=True) if fa: logging.info("Subset and merge FA-Brands font") fa = ff.open(fa[0]) merge_font(nf, fa, sel) copy_glyphnames(nf, fa, fa_subset) else: raise FileNotFoundError("FA-Brands font not found") # Bootstrap font if osp.isfile(FB_FILE): logging.debug("Booststrap font already downloaded: {}".format(FB_FILE)) else: import urllib.request urllib.request.urlretrieve(FB_URL, FB_FILE) logging.info("Downloaded Bootstrap font: {}".format(FB_FILE)) fb = ff.open(FB_FILE) fb.selection.all() fb.transform(psMat.compose(psMat.scale(1.18 * nf.em / fb.em), psMat.translate(0, -100))) fb_subset = parse_subset(FB_SUBSET) sel = [("unicode", "singletons")] + fb_subset logging.info("Subset and merge Bootstrap font") merge_font(nf, fb, sel) copy_glyphnames(nf, fb, fb_subset) # LCD font (modified TRS Million by default) logging.info("Subset and merge LCD font file: {}".format(args.lcd_font)) cf = ff.open(args.lcd_font) cf.selection.all() cf.transform(psMat.scale(float(nf.em) / cf.em)) merge_font(nf, cf, [" "]) merge_font(nf, cf, [("more", "ranges"), ".", ":"]) # 0-9 included merge_font(nf, cf, [("more", "ranges"), "A", "Z"]) merge_font(nf, cf, [("more", "ranges"), "a", "z"]) # adjust specific icon nf.selection.select(0xF2DB) # cpu (microchip) nf.transform(psMat.compose(psMat.scale(1.15), psMat.translate(-20, 0))) # save the final work nf.generate(args.output) logging.info("Generated font file: {}".format(args.output))