From 1df9b852839754b71f0fe58f765a07147ce32b0f Mon Sep 17 00:00:00 2001 From: Marc Foley Date: Wed, 23 Feb 2022 11:50:05 +0000 Subject: [PATCH 1/3] remove redundant scripts --- bin/gftools-build-font2ttf.py | 119 ------- bin/gftools-build-vf.py | 566 --------------------------------- bin/gftools-check-gf-github.py | 196 ------------ bin/gftools-check-vf-avar.py | 218 ------------- bin/gftools-dump-names.py | 44 --- bin/gftools-fix-dsig.py | 87 ----- 6 files changed, 1230 deletions(-) delete mode 100755 bin/gftools-build-font2ttf.py delete mode 100755 bin/gftools-build-vf.py delete mode 100755 bin/gftools-check-gf-github.py delete mode 100755 bin/gftools-check-vf-avar.py delete mode 100755 bin/gftools-dump-names.py delete mode 100755 bin/gftools-fix-dsig.py diff --git a/bin/gftools-build-font2ttf.py b/bin/gftools-build-font2ttf.py deleted file mode 100755 index 69b469e04..000000000 --- a/bin/gftools-build-font2ttf.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2013 The Font Bakery Authors. -# Copyright 2017 The Google Font Tools Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# See AUTHORS.txt for the list of Authors and LICENSE.txt for the License. -# -# Convert a source font into OpenType-TTF and optionally also OpenType-CFF -# -# $ gftools build-font2ttf font.sfd font.ttf font.otf -# $ gftools build-font2ttf font.sfdir font.ttf font.otf -# $ gftools build-font2ttf font.ufo font.ttf font.otf -# $ gftools build-font2ttf font.otf font.ttf -from __future__ import print_function - -import sys -import argparse -import logging -import os - -try: - import fontforge -except: - sys.exit("To run this tool you'll need to install FontForge.") - -log_format = '%(levelname)-8s %(message)s' -logger = logging.getLogger() -handler = logging.StreamHandler() -formatter = logging.Formatter(log_format) -handler.setFormatter(formatter) -logger.addHandler(handler) - - -def convert(sourceFont, ttf, otf=None): - try: - font = fontforge.open(sourceFont) - except: - logger.error("Error: Could not open font (%s)" % sourceFont) - return - - font.selection.all() - - # Remove overlap - try: - font.removeOverlap() - except: - logger.error("Error: Could not remove overlaps") - - if otf: - try: - font.generate(otf) - logger.info("OK: Generated OpenType-CFF (%s)" % otf) - except: - logger.error("Error: Could not generate OpenType-CFF (%s)" % otf) - - # Convert curves to quadratic (TrueType) - try: - font.layers["Fore"].is_quadratic = True - except: - logger.error("Error: Could not convert to quadratic TrueType curves") - return - - # Simplify - try: - font.simplify(1, ('setstarttoextremum', - 'removesingletonpoints', - 'mergelines')) - except: - logger.error("Error: Could not simplify") - - # Correct Directions - try: - font.correctDirection() - except: - logger.error("Error: Could not correct directions") - - # Generate with DSIG and OpenType tables - try: - flags = ('dummy-dsig', 'opentype') - font.generate(ttf, flags=flags) - logger.info("Success: Generated OpenType-TTF (%s)" % ttf) - except: - logger.error("Error: Could not generate OpenType-TTF (%s)" % ttf) - return - - -parser = argparse.ArgumentParser() -parser.add_argument('--with-otf', action="store_true", - help='Generate otf file') -parser.add_argument('source', nargs='+', type=str) - -def main(): - args = parser.parse_args() - for src in args.source: - if not os.path.exists(src): - print('\nError: {} does not exists\n'.format(src), file=sys.stderr) - continue - - basename, _ = os.path.splitext(src) - otffile = None - if args.with_otf: - otffile = '{}.otf'.format(basename) - - convert(src, '{}.ttf'.format(basename), otffile) - -if __name__ == '__main__': - main() - diff --git a/bin/gftools-build-vf.py b/bin/gftools-build-vf.py deleted file mode 100755 index 24424f30c..000000000 --- a/bin/gftools-build-vf.py +++ /dev/null @@ -1,566 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2018 The Google Font Tools Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# See AUTHORS.txt for the list of Authors and LICENSE.txt for the License. -""" -Automated build process for variable font onboarding to: -https://github.com/google/fonts - - -BASIC USE: - -This script is used to build variable fonts from the command line of a -UNIX system (MacOS, GNU+Linux, Windows Subsystem for Linux (WSL)). - -To start the build process, navigate to the root directory of a font repo -and run the following: - -python3 $SOURCE/BUILD.py - -For additional build features, the script can be run with flags, like so: - -python3 $SOURCE/BUILD.py --googlefonts ~/Google/fonts/ofl/$FONTNAME --static - - -FLAGS: - ---googlefonts ~/Google/fonts/ofl/foo Sets upstream repo location - ---drawbot Render the specimen with DrawBot - ---ttfautohint "-args" Autohints fonts given args - ---fontbakery Run QA tests on fonts in upstream - ---static Output static fonts from VF source - ---fixnonhinting Run if --ttfautohint is not used - ---addfont Update font metadata -""" -import argparse -import glob -import os -import subprocess -import time - - -# Initialize flag parser -parser = argparse.ArgumentParser() -parser.add_argument( - "--drawbot", help="Render a specimen with DrawBot", action="store_true" -) -parser.add_argument( - "--fontbakery", help="Test fonts with fontbakery", action="store_true" -) -parser.add_argument( - "--googlefonts", help="Store GoogleFonts directory name" -) -parser.add_argument( - "--ttfautohint", help="Store ttfautohint flags" -) -parser.add_argument( - "--static", help="Build static fonts", action="store_true" -) -parser.add_argument( - "--fixnonhinting", help="Fix nonhinting with gs tools", action="store_true" -) -parser.add_argument( - "--addfont", help="Update metadata", action="store_true" -) -parser.add_argument( - "--ufosrc", help="Build from ufo source and not glyphs", action="store_true" -) -args = parser.parse_args() - - -# Initialize empty lists -sources = [] -sources_styles = [] - - -def printR(prt): - """ - Print in red - """ - print("\033[91m {}\033[00m".format(prt)) - - -def printG(prt): - """ - Print in green - """ - print("\033[92m {}\033[00m".format(prt)) - - -def printY(prt): - """ - Print in yellow - """ - print("\033[93m {}\033[00m".format(prt)) - - -def intro(): - """ - Gives basic script info. - """ - printG("# # ##### ##### ################") - printG("# # # # # # ## #") - printG(" # # #### # # # # # #######") - printG(" # # # <----> # ## # # #") - printG(" ## # # # # ####") - printG(" ## # ########## #####") - print("\n**** Starting variable font build script:") - print(" [+]", time.ctime()) - printG(" [!] Done") - - -def display_args(): - """ - Prints info about argparse flag use. - """ - print("\n**** Settings:") - - print(" [+] --drawbot\t\t", end="") - if args.drawbot == True: - printG(args.drawbot) - else: - printR(args.drawbot) - - print(" [+] --googlefonts\t\t", end="") - if args.googlefonts is not None: - printG(args.googlefonts) - else: - printR(args.googlefonts) - - print(" [+] --ttfautohint\t\t", end="") - if args.ttfautohint is not None: - printG(args.ttfautohint) - else: - printR(args.ttfautohint) - - print(" [+] --fontbakery\t\t", end="") - if args.fontbakery == True: - printG(args.fontbakery) - else: - printR(args.fontbakery) - - print(" [+] --static\t\t", end="") - if args.static == True: - printG(args.static) - else: - printR(args.static) - - print(" [+] --fixnonhinting\t", end="") - if args.fixnonhinting == True: - printG(args.fixnonhinting) - else: - printR(args.fixnonhinting) - - print(" [+] --addfont\t\t", end="") - if args.addfont == True: - printG(args.addfont) - else: - printR(args.addfont) - - print(" [+] --ufosrc\t\t", end="") - if args.ufosrc == True: - printG(args.ufosrc) - else: - printR(args.ufosrc) - - printG(" [!] Done") - time.sleep(8) - - -def check_root_dir(): - """ - Checks to make sure script is run from a git repo root directory. - """ - print("\n**** Looking for the font repo root directory:") - REPO_ROOT = [".gitignore", ".git"] - repo_test = os.listdir(path=".") - repo_test_result = all(elem in repo_test for elem in REPO_ROOT) - if repo_test_result: - print(" [+] OK: Looks good") - printG(" [!] Done") - else: - printR(" [!] ERROR: Run script from the root directory") - time.sleep(2) - - -def get_source_list(): - """ - Gets a list of source files. - """ - print("\n**** Making a list of Glyphsapp source files:") - os.chdir("source") - for name in glob.glob("*.glyphs"): - sources.append(os.path.splitext(name)[0]) - os.chdir("..") - print(" [+] SOURCES: List of sources =", sources) - time.sleep(1) - printG(" [!] Done") - - -def get_style_list(): - """ - Gets a list of styles from the source list. - """ - print("\n**** Starting build process:") - for source in sources: - time.sleep(0.5) - print(" [+] SOURCES: Preparing to build", source) - print(" [+] SOURCES: Style =", source.rpartition("-")[2]) - sources_style = str(source.rpartition("-")[2]) - sources_styles.append(sources_style) - print(" [+] SOURCES: Styles =", sources_styles) - time.sleep(1) - printG(" [!] Done") - - -def run_fontmake_variable(): - """ - Builds ttf variable font files with FontMake. - """ - for source in sources: - print("\n**** Building %s variable font files with FontMake:" % source) - print(" [+] Run: fontmake ") - if args.ufosrc == True: - subprocess.call( - "fontmake \ - -g source/master_ufo/%s.designspace \ - -o variable \ - --output-path fonts/%s-VF.ttf" - % (source, source), - shell=True, - ) - else: - subprocess.call( - "fontmake \ - -g source/%s.glyphs \ - -o variable \ - --output-path fonts/%s-VF.ttf" - % (source, source), - shell=True, - ) - print(" [!] Done") - printG(" [!] Done") - - -def run_fontmake_static(): - """ - Builds ttf static font files with FontMake. - """ - for source in sources: - print("\n**** Building %s static font files with FontMake:" % source) - print(" [+] Run: fontmake ") - subprocess.call( - "fontmake \ - -g source/%s.glyphs \ - -o ttf \ - --keep-overlaps -i" - % (source), - shell=True, - ) - print(" [!] Done") - printG(" [!] Done") - - -def prep_static_fonts(): - """ - Move static fonts to the fonts/static directory. - Run ttfautohint on all fonts and fix missing dsig - """ - print("\n**** Moving static fonts:") - for path in glob.glob("instance_ttf/*.ttf"): - print(path) - subprocess.call("cp %s fonts/static-fonts/" % path, shell=True) - subprocess.call("rm -rf instance_ttf", shell=True) - - for static_font in glob.glob("fonts/static-fonts/*.ttf"): - print(static_font) - subprocess.call( - "gftools fix-dsig %s --autofix" - % static_font, shell=True - ) - if args.fixnonhinting == True: - print("FIXING NONHINTING") - subprocess.call( - "gftools fix-nonhinting %s %s.fix" - % (static_font, static_font), shell=True - ) - subprocess.call( - "mv %s.fix %s" - % (static_font, static_font), shell=True - ) - subprocess.call( - "rm -rf %s.fix" - % static_font, shell=True - ) - subprocess.call( - "rm -rf fonts/static-fonts/*gasp.ttf", shell=True - ) - print(" [+] Done:", static_font) - - if args.ttfautohint == True: - subprocess.call( - "ttfautohint %s %s temp.ttf" - % (args.ttfautohint, static_font), - shell=True, - ) - subprocess.call("cp temp.ttf %s" % static_font, shell=True) - subprocess.call("rm -rf temp.ttf", shell=True) - time.sleep(1) - printG(" [!] Done") - - -def rm_build_dirs(): - """ - Cleanup build dirs - """ - print("\n**** removing build directories") - print(" [+] run: rm -rf variable_ttf master_ufo instance_ufo") - subprocess.call( - "rm -rf variable_ttf master_ufo instance_ufo", shell=True - ) - printG(" [!] Done") - time.sleep(1) - - -def fix_dsig(): - """ - Fixes DSIG table - """ - print("\n**** Run: gftools: fix DSIG") - for source in sources: - subprocess.call( - "gftools fix-dsig fonts/%s-VF.ttf --autofix" - % source, - shell=True, - ) - print(" [+] Done:", source) - printG(" [!] Done") - time.sleep(1) - - -def fix_nonhinting(): - """ - Fixes non-hinting - """ - print("\n**** Run: gftools: fix nonhinting") - for path in glob.glob("fonts/*.ttf"): - print(path) - subprocess.call( - "gftools fix-nonhinting %s %s.fix" - % (path, path), shell=True - ) - subprocess.call( - "mv %s.fix %s" - % (path, path), shell=True - ) - subprocess.call( - "rm -rf %s.fix" - % path, shell=True - ) - subprocess.call( - "rm -rf fonts/*gasp.ttf", shell=True - ) - print(" [+] Done:", path) - printG(" [!] Done") - time.sleep(1) - - -def ttfautohint(): - """ - Runs ttfautohint with flags set. For more info run: ttfautohint --help - """ - print("\n**** Run: ttfautohint") - os.chdir("fonts") - cwd = os.getcwd() - print(" [+] In Directory:", cwd) - for source in sources: - subprocess.call( - "ttfautohint %s %s-VF.ttf %s-VF-Fix.ttf" - % (args.ttfautohint, source, source), - shell=True, - ) - subprocess.call( - "cp %s-VF-Fix.ttf %s-VF.ttf" - % (source, source), shell=True - ) - subprocess.call( - "rm -rf %s-VF-Fix.ttf" - % source, shell=True - ) - print(" [+] Done:", source) - os.chdir("..") - cwd = os.getcwd() - print(" [+] In Directory:", cwd) - printG(" [!] Done") - time.sleep(1) - - -def ttfautohint_static(): - """ - Runs ttfautohint with flags set. For more info run: ttfautohint --help - """ - print("\n**** Run: ttfautohint") - os.chdir("fonts") - cwd = os.getcwd() - print(" [+] In Directory:", cwd) - for source in sources: - subprocess.call( - "ttfautohint %s %s-VF.ttf %s-VF-Fix.ttf" - % (args.ttfautohint, source, source), - shell=True, - ) - subprocess.call( - "cp %s-VF-Fix.ttf %s-VF.ttf" - % (source, source), shell=True - ) - subprocess.call( - "rm -rf %s-VF-Fix.ttf" - % source, shell=True - ) - print(" [+] Done:", source) - os.chdir("..") - cwd = os.getcwd() - print(" [+] In Directory:", cwd) - printG(" [!] Done") - time.sleep(1) - - -def google_fonts(): - """ - Copy font output to the GoogleFonts repo. - """ - print("\n**** Copying font output to the GoogleFonts repo.") - if args.googlefonts is not None: - for source in sources: - subprocess.call( - "cp fonts/%s-VF.ttf %s/" - % (source, args.googlefonts), shell=True - ) - print(" [+] Done:", source) - for path in glob.glob("fonts/static-fonts/*.ttf"): - print(path) - subprocess.call( - "cp %s %s/static/" - % (path, args.googlefonts), shell=True - ) - else: - pass - printG(" [!] Done") - time.sleep(1) - - -def add_font(): - """ - Build new metadata file for font if gf flag is used. - """ - print("\n**** Making new metadata file for font.") - if args.googlefonts is not None: - subprocess.call( - "gftools add-font %s" - % args.googlefonts, shell=True - ) - print(" [+] Done:") - else: - printR(" [!] Error: Use Google Fonts Flag (--googlefonts)") - printG(" [!] Done") - time.sleep(1) - - -def fontbakery(): - """ - Run FontBakery on the GoogleFonts repo. - """ - print("\n**** Run: FontBakery:") - for source in sources: - subprocess.call( - "fontbakery check-googlefonts %s/%s-VF.ttf \ - --ghmarkdown docs/FONTBAKERY-REPORT-%s.md" - % (args.googlefonts, source, source), shell=True, - ) - print(" [+] Done:", source) - printG(" [!] Done") - time.sleep(1) - - -def render_specimens(): - """ - Render specimens - """ - print("\n**** Run: DrawBot") - subprocess.call( - "python3 docs/drawbot-sources/basic-specimen.py", - shell=True, - ) - printG(" [!] Done") - time.sleep(1) - - -def main(): - """ - Executes font build sequence - """ - intro() - display_args() - check_root_dir() - get_source_list() - get_style_list() - run_fontmake_variable() - # fix non-hinting - if args.fixnonhinting == True: - fix_nonhinting() - else: - pass - # make static fonts - if args.static == True: - run_fontmake_static() - prep_static_fonts() - else: - pass - rm_build_dirs() - fix_dsig() - # ttfautohint - if args.ttfautohint is not None: - ttfautohint() - else: - pass - # GoogleFonts - if args.googlefonts is not None: - google_fonts() - else: - pass - # AddFont - if args.addfont == True: - add_font() - else: - pass - # FontBakery - if args.fontbakery == True: - fontbakery() - else: - pass - # DrawBot - if args.drawbot == True: - render_specimens() - else: - pass - -if __name__ == "__main__": - main() diff --git a/bin/gftools-check-gf-github.py b/bin/gftools-check-gf-github.py deleted file mode 100755 index 8a1278cb3..000000000 --- a/bin/gftools-check-gf-github.py +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2017 The Fontbakery Authors -# Copyright 2017 The Google Font Tools Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -Report how many github issues/prs were opened and closed for the google/fonts -repository between two specified dates. - -Example: -Issues between 2017-01-01 to 2017-06-01: -gftools check-gf-github 2017-01-01 2017-06-01 - -The title and url of each issues/pr can be displayed by using the --v, --verbose option. -""" -from __future__ import print_function -import requests -import re -from datetime import datetime -from argparse import (ArgumentParser, - RawTextHelpFormatter) - - -def get_pagination_urls(request): - pages = dict( - [(rel[6:-1], url[url.index('<')+1:-1]) for url, rel in - [link.split(';') for link in - request.headers['link'].split(',')]] - ) - last_page_url = pages['last'] - last_page_no = re.search(r'(?<=&page=)[0-9]{1,20}', last_page_url).group(0) - base_url = last_page_url.replace('page={}'.format(last_page_no), 'page={}') - return [base_url.format(u) for u in range(1, int(last_page_no) + 1)] - - -def get_issues_paginate(request_issues, start, end, headers): - """ - If there are too many issues for one page, iterate through the pages - to collect them all. - """ - issues = {} - print('Getting paginated results, be patient...') - pages_url = get_pagination_urls(request_issues) - - for page_url in pages_url: - request = requests.get(page_url, headers=headers) - page_issues = get_issues(request, start, end) - - for issue_type in page_issues: - if issue_type not in issues: - issues[issue_type] = [] - issues[issue_type] = issues[issue_type] + page_issues[issue_type] - return issues - - -def get_issues(request_issues, start, end): - """ - Return a dictionary containing 4 categories of issues - """ - issues = [i for i in request_issues.json()] - return { - "closed_issues": [ - i for i in issues - if i['closed_at'] and 'pull_request' not in i - and iso8601_to_date(i['closed_at']) >= start - and iso8601_to_date(i['closed_at']) <= end - ], - - "opened_issues": [ - i for i in issues - if 'pull_request' not in i - and iso8601_to_date(i['created_at']) >= start - and iso8601_to_date(i['created_at']) <= end - ], - - "closed_prs": [ - i for i in issues - if i['closed_at'] and 'pull_request' in i - and iso8601_to_date(i['closed_at']) >= start - and iso8601_to_date(i['closed_at']) <= end - ], - - "opened_prs": [ - i for i in issues - if 'pull_request' in i - and iso8601_to_date(i['created_at']) >= start - and iso8601_to_date(i['created_at']) <= end - ], - } - - -def output_issues(issues, key): - for issue in issues[key]: - title = issue['title'][:50] + '...' - url = issue['url'].replace('api.github.com/repos/', 'github.com/') - print('%s\t%s\t%s' % ( - key, - title.ljust(50, ' ').encode('utf-8'), - url.encode('utf-8'), - )) - - -def iso8601_to_date(date_string): - """Note, this function will strip out the time and tz""" - date_string = date_string.split('T')[0] - return datetime.strptime(date_string, "%Y-%m-%d") - - -def main(): - parser = ArgumentParser(description=__doc__, - formatter_class=RawTextHelpFormatter) - parser.add_argument('github_api_token', - help=("User's Github API token. Generate one using the " - "following link: https://github.com/settings/tokens")) - parser.add_argument('start', - help="Start date in ISO 8601 format YYYY-MM-DD") - parser.add_argument('end', - help="End date in ISO 8601 format YYYY-MM-DD") - parser.add_argument('-v', '--verbose', action='store_true', - help="Output all title and urls for prs and issues") - parser.add_argument('-ci', '--closed-issues',action='store_true', - help="Output all closed issues") - parser.add_argument('-oi', '--opened-issues',action='store_true', - help="Output all opened issues") - parser.add_argument('-cp', '--closed-pulls',action='store_true', - help="Output all closed/merged pull requests") - parser.add_argument('-op', '--opened-pulls',action='store_true', - help="Output all opened pull requests") - - args = parser.parse_args() - - start = iso8601_to_date(args.start) - end = iso8601_to_date(args.end) - - if start > end: - raise ValueError('start time is greater than end time') - - repo_url = "https://api.github.com/repos/google/fonts/issues" - - request_params = { - 'state': 'all', - 'direction': 'asc', - 'since': args.start, - 'per_page': 100 - } - headers = {'Authorization': 'token %s' % args.github_api_token} - - request_issues = requests.get( - repo_url, - params=request_params, - headers=headers, - ) - print(request_issues.text) - - # Check if issues span more than one page - if 'link' in request_issues.headers: - issues = get_issues_paginate(request_issues, start, end, headers) - else: - issues = get_issues(request_issues, start, end) - - if args.verbose: - output_issues(issues, 'closed_issues') - output_issues(issues, 'opened_issues') - output_issues(issues, 'closed_prs') - output_issues(issues, 'opened_prs') - else: - if args.closed_issues: - output_issues(issues, 'closed_issues') - if args.opened_issues: - output_issues(issues, 'opened_issues') - if args.closed_pulls: - output_issues(issues, 'closed_prs') - if args.opened_pulls: - output_issues(issues, 'opened_prs') - - print('Issues closed\t%s' % len(issues['closed_issues'])) - print('Issues opened\t%s' % len(issues['opened_issues'])) - print('Pull requests closed/merged\t%s' % len(issues['closed_prs'])) - print('Pull requests opened\t%s' % len(issues['opened_prs'])) - - -if __name__ == '__main__': - main() - diff --git a/bin/gftools-check-vf-avar.py b/bin/gftools-check-vf-avar.py deleted file mode 100755 index e0c748979..000000000 --- a/bin/gftools-check-vf-avar.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python3 -""" -Generate a html overlay doc which compares a family of static fonts against -a family of vf font instances. - -If the variable font instances do not match the static fonts perfectly, -it usually means the avar table needs adjusting -https://docs.microsoft.com/en-us/typography/opentype/spec/avar - -Please note: The generated html doc will only work on browsers which support -variable fonts. - -TODO (M Foley) this script is a quickie. The functionality of this script -should be built into GF regression. -""" -from __future__ import print_function -import argparse -import os - -WEIGHT_MAP = { - 'Thin': 100, - 'ThinItalic': 100, - 'ExtraLight': 200, - 'ExtraLightItalic': 200, - 'Light': 300, - 'LightItalic': 300, - 'Regular': 400, - 'Italic': 400, - 'Medium': 500, - 'MediumItalic': 500, - 'SemiBold': 600, - 'SemiBoldItalic': 600, - 'Bold': 700, - 'BoldItalic': 700, - 'ExtraBold': 800, - 'ExtraBoldItalic': 800, - 'Black': 900, - 'BlackItalic': 900 -} - -HTML_TEMPLATE = """ - - - - - -

Variable Font instances vs Static fonts

-

Static fonts, Variable Font Instances

-
- {{ elements }} -
- - -""" - - -def get_vf_font_info(variable_font_paths): - - faces = [] - for path in variable_font_paths: - filename = os.path.basename(path)[:-4] - family_name = filename.split('-')[0] + '-VF' - font_type = filename.split('-')[1] - if font_type not in ('Roman', 'Italic'): - raise Exception("Filename must contain either Roman or Italic") - style = 'normal' if 'Roman' in font_type else 'italic' - - faces.append((family_name, path, style)) - return sorted(faces, key=lambda k: k[2]) - - -def get_static_fonts_info(static_font_paths): - faces = [] - for path in static_font_paths: - filename = os.path.basename(path)[:-4] - family_name, style = filename.split('-') - weight = WEIGHT_MAP[style] - ttype = 'normal' if 'Italic' not in style else 'italic' - - faces.append((family_name, path, weight, ttype)) - return sorted(faces, key=lambda k: k[2]) - - -def populate_html_template(html_template, static_fonts, vf_fonts): - """Note: The vf css styles are populated using the weight - and style values from the static fonts.""" - static_font_template = """ - @font-face {font-family: '%s'; - src: url('%s') format('truetype'); - font-weight: %s; - font-style: %s}""" - - vf_font_template = """ - @font-face {font-family: '%s'; - src: url('%s') format('truetype'); - font-weight: 1 999; - font-style: %s}""" - - style_template = """ - .%s{ - position: absolute; - font-family: %s; - font-weight: %s; - font-style: %s; - font-size: 48px; - top: %spx; - }""" - - vf_style_template = """ - .%s{ - position: absolute; - font-family: %s; - font-weight: %s; - font-style: %s; - font-size: 48px; - top: %spx; - color: cyan; - }""" - element_template = """ -
hamburgevons
""" - - html = html_template - - # Gen @font-faces for static fonts - static_font_faces = [] - for family_name, path, weight, ttype in static_fonts: - static_font_face = static_font_template % ( - family_name, path, weight, ttype - ) - static_font_faces.append(static_font_face) - - # Gen @font-face for variable fonts - vf_font_faces = [] - for family_name, path, style in vf_fonts: - vf_font_face = vf_font_template % ( - family_name, path, style - ) - vf_font_faces.append(vf_font_face) - - # Gen css classes for both static fonts and variable fonts. Use the - # static font values to set the vf values so they're matching. - # Gen div elements for each style as well. - static_styles = [] - variable_styles = [] - elements = [] - line_gap = 150 - for family_name, path, weight, ttype in static_fonts: - # Gen static class styles - static_style = style_template % ( - family_name+str(weight)+ttype, - family_name, - weight, - ttype, - line_gap - ) - static_styles.append(static_style) - - # Gen variable class styles - variable_style = vf_style_template % ( - vf_fonts[0][0]+str(weight)+ttype, - vf_fonts[0][0], - weight, - ttype, - line_gap - ) - variable_styles.append(variable_style) - - # Gen Div elements - static_element = element_template % ( - family_name+str(weight)+ttype - ) - elements.append(static_element) - - variable_element = element_template % ( - vf_fonts[0][0]+str(weight)+ttype - ) - elements.append(variable_element) - line_gap += 72 - - html = html.replace('{{ static_fonts }}', '\n'.join(static_font_faces)) - html = html.replace('{{ variable_fonts }}', '\n'.join(vf_font_faces)) - html = html.replace('{{ static_styles }}', '\n'.join(static_styles)) - html = html.replace('{{ variable_styles }}', '\n'.join(variable_styles)) - html = html.replace('{{ elements }}', '\n'.join(elements)) - return html - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--variable-fonts', '-vf', nargs='+') - parser.add_argument('--static-fonts', '-sf', nargs='+') - parser.add_argument('--out', '-o', help='html output filepath', required=True) - args = parser.parse_args() - - vf_fonts = get_vf_font_info(args.variable_fonts) - static_fonts = get_static_fonts_info(args.static_fonts) - - html = populate_html_template( - HTML_TEMPLATE, - static_fonts, - vf_fonts - ) - with open(args.out, 'w') as html_doc: - html_doc.write(html) - print('html written to {}'.format(args.out)) - - -if __name__ == '__main__': - main() diff --git a/bin/gftools-dump-names.py b/bin/gftools-dump-names.py deleted file mode 100755 index 1950598d8..000000000 --- a/bin/gftools-dump-names.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2017 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -r"""Utility to print Unicode 'name' table values from font(s). - -""" -from __future__ import print_function -import contextlib -import os - -from fontTools import ttLib -from absl import app - - -def main(argv): - for font_file in argv[1:]: - filename = os.path.basename(font_file) - try: - with contextlib.closing(ttLib.TTFont(font_file)) as ttf: - if 'name' not in ttf: - continue - for name in ttf['name'].names: - print('%s %d %d %d %s %s' % (filename, name.platformID, - name.platEncID, name.langID, name.nameID, - name.toUnicode())) - except ttLib.TTLibError as e: - print('BAD_FILE', font_file, e) - - -if __name__ == '__main__': - app.run(main) diff --git a/bin/gftools-fix-dsig.py b/bin/gftools-fix-dsig.py deleted file mode 100755 index a230187db..000000000 --- a/bin/gftools-fix-dsig.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2013,2016 The Font Bakery Authors. -# Copyright 2017 The Google Font Tools Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# See AUTHORS.txt for the list of Authors and LICENSE.txt for the License. -# -from __future__ import print_function, unicode_literals -import argparse -import os -from fontTools import ttLib -from gftools.fix import add_dummy_dsig - - -description = 'Fixes TTF to have a dummy DSIG table' -parser = argparse.ArgumentParser(description=description) -parser.add_argument('ttf_font', - nargs='+', - help="One or more font files") -parser.add_argument('-a', '--autofix', - action='store_true', - help='Write empty DSIG table if not present in font.') - -parser.add_argument('-f', '--force', - action='store_true', - help='Write empty DSIG table in any case.') - -parser.add_argument('-d', '--delete', - action='store_true', - help='Delete DSIG table if present in font.') - - -def main(): - args = parser.parse_args() - for path in args.ttf_font: - if not os.path.exists(path): - continue - font = ttLib.TTFont(path) - has_DSIG = "DSIG" in font - write_DSIG = args.force or args.autofix and not has_DSIG - - if has_DSIG and args.delete: - del font["DSIG"] - font.save(path) - has_DSIG = False - print("DELETED: '{}': removed digital " - "signature (DSIG)".format(path)) - - if write_DSIG: - add_dummy_dsig(font) - font.save(path) - - if not args.force: - print("HOTFIX: '{}': Font lacked a digital" - " signature (DSIG), so we just added a dummy" - " placeholder that should be enough for the" - " applications that require its presence in" - " order to work properly.".format(path)) - else: - print("FORCED: '{}': Font has a new a dummy digital " - "signature (DSIG)".format(path)) - - elif not has_DSIG and not args.delete: - print(("ERROR: '{}': Font lacks a digital signature" - " (DSIG table). Some applications may required" - " one (even if only a dummy placeholder)" - " in order to work properly. Re-run this script" - " passing --autofix in order to hotfix the font" - " with a dummy signature.").format(path)) - elif has_DSIG: - print(("INFO: '{}': Font has a digital signature" - " (DSIG table).").format(path)) - - -if __name__ == '__main__': - main() From 2c9dc005b95613e44077d76afb0f4bc1a4da09dc Mon Sep 17 00:00:00 2001 From: Marc Foley Date: Fri, 25 Feb 2022 15:17:36 +0000 Subject: [PATCH 2/3] fix tests --- Lib/gftools/tests/test_usage.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Lib/gftools/tests/test_usage.py b/Lib/gftools/tests/test_usage.py index e5617c310..c381f2ad7 100644 --- a/Lib/gftools/tests/test_usage.py +++ b/Lib/gftools/tests/test_usage.py @@ -103,9 +103,6 @@ def test_check_vtt_compatibility(self): def test_compare_font(self): self.check_script(['python', self.get_path('compare-font'), self.example_font, self.example_font]) - def test_dump_names(self): - self.check_script(['python', self.get_path('dump-names'), self.example_font]) - def test_find_features(self): self.check_script(['python', self.get_path('find-features'), self.example_font]) @@ -115,9 +112,6 @@ def test_fix_ascii_fontmetadata(self): def test_fix_cmap(self): self.check_script(['python', self.get_path('fix-cmap'), self.example_font]) - def test_fix_dsig(self): - self.check_script(['python', self.get_path('fix-dsig'), self.example_font]) - def test_fix_familymetadata(self): self.check_script(['python', self.get_path('fix-familymetadata'), self.example_font]) From f0aea2ef62a1dcb210365c3fe6b66743e92da539 Mon Sep 17 00:00:00 2001 From: Marc Foley Date: Fri, 25 Feb 2022 15:20:24 +0000 Subject: [PATCH 3/3] rm fix-vf-meta --- bin/gftools-fix-vf-meta.py | 237 ------------------------------------- 1 file changed, 237 deletions(-) delete mode 100755 bin/gftools-fix-vf-meta.py diff --git a/bin/gftools-fix-vf-meta.py b/bin/gftools-fix-vf-meta.py deleted file mode 100755 index 2ecf0edc7..000000000 --- a/bin/gftools-fix-vf-meta.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python3 -""" -Add a STAT table to a weight only variable font. - -This script can also add STAT tables to a variable font family which -consists of two fonts, one for Roman, the other for Italic. -Both of these fonts must also only contain a weight axis. - -For variable fonts with multiple axes, write a python script which -uses fontTools.otlLib.builder.buildStatTable e.g -https://github.com/googlefonts/literata/blob/main/sources/gen_stat.py - -The generated STAT tables use format 2 Axis Values. These are needed in -order for Indesign to work. - -Special mention to Thomas Linard for reviewing the output of this script. - - -Usage: - -Single family: -gftools fix-vf-meta FontFamily[wght].ttf - -Roman + Italic family: -gftools fix-vf-meta FontFamily[wght].ttf FontFamily-Italic[wght].ttf -""" -from fontTools.otlLib.builder import buildStatTable -from fontTools.ttLib import TTFont -from gftools.utils import font_is_italic -import argparse - - -WGHT = { - 100: "Thin", - 200: "ExtraLight", - 300: "Light", - 400: "Regular", - 500: "Medium", - 600: "SemiBold", - 700: "Bold", - 800: "ExtraBold", - 900: "Black", - 1000: "ExtraBlack", -} - - -def font_has_mac_names(ttfont): - for record in ttfont['name'].names: - if record.platformID == 1: - return True - return False - - -def build_stat(roman_font, italic_font=None): - roman_wght_axis = dict( - tag="wght", - name="Weight", - values=build_axis_values(roman_font), - ) - roman_axes = [roman_wght_axis] - if italic_font: - # We need to create a new Italic axis in the Roman font - roman_axes.append( - dict( - tag="ital", - name="Italic", - values=[ - dict( - name="Roman", - flags=2, - value=0.0, - linkedValue=1.0, - ) - - ] - ) - ) - italic_wght_axis = dict( - tag="wght", - name="Weight", - values=build_axis_values(italic_font), - ) - italic_axes = [italic_wght_axis] - italic_axes.append( - dict( - tag="ital", - name="Italic", - values=[ - dict( - name="Italic", - value=1.0, - ) - ] - ) - ) - buildStatTable(italic_font, italic_axes) - buildStatTable(roman_font, roman_axes) - - -def build_axis_values(ttfont): - results = [] - nametable = ttfont['name'] - instances = ttfont['fvar'].instances - has_bold = any([True for i in instances if i.coordinates['wght'] == 700]) - for instance in instances: - wght_val = instance.coordinates["wght"] - desired_inst_info = WGHT[wght_val] - name = nametable.getName( - instance.subfamilyNameID, - 3, - 1, - 1033 - ).toUnicode() - name = name.replace("Italic", "").strip() - if name == "": - name = "Regular" - inst = { - "name": name, - "nominalValue": wght_val, - } - if inst["nominalValue"] == 400: - inst["flags"] = 0x2 - results.append(inst) - - # Dynamically generate rangeMinValues and rangeMaxValues - entries = [results[0]["nominalValue"]] + \ - [i["nominalValue"] for i in results] + \ - [results[-1]["nominalValue"]] - for i, entry in enumerate(results): - entry["rangeMinValue"] = (entries[i] + entries[i+1]) / 2 - entry["rangeMaxValue"] = (entries[i+1] + entries[i+2]) / 2 - - # Format 2 doesn't support linkedValues so we have to append another - # Axis Value (format 3) for Reg which does support linkedValues - if has_bold: - inst = { - "name": "Regular", - "value": 400, - "flags": 0x2, - "linkedValue": 700 - } - results.append(inst) - return results - - -def update_nametable(ttfont): - """ - - Add nameID 25 - - Update fvar instance names and add fvar instance postscript names - """ - is_italic = font_is_italic(ttfont) - has_mac_names = font_has_mac_names(ttfont) - - # Add nameID 25 - # https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids - vf_ps_name = _add_nameid_25(ttfont, is_italic, has_mac_names) - - # Update fvar instances - instances = ttfont["fvar"].instances - for inst in instances: - wght_val = inst.coordinates["wght"] - if wght_val not in WGHT: - raise ValueError(f"Unsupported wght coord '{wght_val}'. Coord " - "needs to be in {WGHT.keys()}") - - # Update instance subfamilyNameID - wght_name = WGHT[wght_val] - inst_name = wght_name - if is_italic: - inst_name = f"{inst_name} Italic" - inst_name = inst_name.replace("Regular Italic", "Italic") - ttfont['name'].setName(inst_name, inst.subfamilyNameID, 3, 1, 0x409) - if has_mac_names: - ttfont['name'].setName(inst_name, inst.subfamilyNameID, 1, 0, 0) - - # Add instance psName - ps_name = f"{vf_ps_name}-{wght_name}" - ps_name_id = ttfont['name'].addName(ps_name) - inst.postscriptNameID = ps_name_id - - -def _add_nameid_25(ttfont, is_italic, has_mac_names): - name = ttfont['name'].getName(16, 3, 1, 1033) or \ - ttfont['name'].getName(1, 3, 1, 1033) - name = name.toUnicode().replace(" ", "") - if is_italic: - name = f"{name}Italic" - else: - name = f"{name}Roman" - ttfont['name'].setName(name, 25, 3, 1, 1033) - if has_mac_names: - ttfont['name'].setName(name, 25, 1, 0, 0) - return name - - -def main(): - parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description=__doc__ - ) - parser.add_argument("fonts", nargs="+", help=( - "Paths to font files. Fonts must be part of the same family." - ) - ) - args = parser.parse_args() - fonts = args.fonts - - # This monstrosity exists so we don't break the v1 api. - italic_font = None - if len(fonts) > 2: - raise Exception( - "Can only add STAT tables to a max of two fonts. " - "Run gftools fix-vf-meta --help for usage instructions" - ) - elif len(fonts) == 2: - if "Italic" in fonts[0]: - italic_font = TTFont(fonts[0]) - roman_font = TTFont(fonts[1]) - elif "Italic" in fonts[1]: - italic_font = TTFont(fonts[1]) - roman_font = TTFont(fonts[0]) - else: - raise Exception("No Italic font found!") - else: - roman_font = TTFont(fonts[0]) - update_nametable(roman_font) - if italic_font: - update_nametable(italic_font) - build_stat(roman_font, italic_font) - roman_font.save(roman_font.reader.file.name + ".fix") - if italic_font: - italic_font.save(italic_font.reader.file.name + ".fix") - - -if __name__ == "__main__": - main() -