2017-08-22 10:05:20 +03:00
|
|
|
#!/usr/bin/env python
|
2017-10-24 07:59:39 +03:00
|
|
|
from __future__ import print_function
|
2017-08-22 10:05:20 +03:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import argparse
|
|
|
|
import logging
|
|
|
|
import subprocess
|
|
|
|
|
2017-08-25 04:27:00 +03:00
|
|
|
from shutil import copyfile
|
2017-08-22 10:05:20 +03:00
|
|
|
from robofab.objects.objectsRF import RPoint
|
|
|
|
from robofab.world import OpenFont
|
|
|
|
from fontbuild.Build import FontProject
|
|
|
|
from fontbuild.mix import Master
|
|
|
|
from fontbuild.mix import Mix
|
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
|
2017-09-19 01:58:34 +03:00
|
|
|
FAMILYNAME = "Inter UI"
|
2017-08-22 10:05:20 +03:00
|
|
|
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
|
|
|
|
|
|
def extractSpecializedGlyphs(masterFont):
|
|
|
|
glyphSpecializations = {}
|
|
|
|
specializationSuffix = '.specz.'
|
|
|
|
|
|
|
|
specializedGlyphNames = []
|
|
|
|
font = masterFont.font
|
|
|
|
for g in font:
|
|
|
|
p = g.name.find(specializationSuffix)
|
|
|
|
if p == -1:
|
|
|
|
continue
|
|
|
|
name = g.name[:p]
|
|
|
|
category = g.name[p + len(specializationSuffix):]
|
|
|
|
g2 = g.copy()
|
|
|
|
g2.name = name
|
|
|
|
if name in font:
|
|
|
|
# copy unicodes
|
|
|
|
masterGlyph = font[name]
|
|
|
|
g2.unicodes = masterGlyph.unicodes
|
|
|
|
if not category in glyphSpecializations:
|
|
|
|
glyphSpecializations[category] = { name: g2 }
|
|
|
|
else:
|
|
|
|
glyphSpecializations[category][name] = g2
|
|
|
|
specializedGlyphNames.append(g.name)
|
|
|
|
|
|
|
|
ffont = masterFont.ffont
|
|
|
|
for name in specializedGlyphNames:
|
|
|
|
del ffont.glyphs[name]
|
|
|
|
font.removeGlyph(name)
|
|
|
|
|
|
|
|
return glyphSpecializations
|
|
|
|
|
|
|
|
|
|
|
|
def readVersionControlTag(dir):
|
|
|
|
try:
|
|
|
|
return subprocess.check_output(
|
|
|
|
['git', '-C', dir, 'rev-parse', '--short', 'HEAD'],
|
|
|
|
shell=False).strip()
|
|
|
|
except:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
def main():
|
2018-01-10 07:38:30 +03:00
|
|
|
# silence warnings from fontTools.misc.fixedTools that is harmless and caused
|
|
|
|
# by the ufo2ft module.
|
2017-09-04 06:03:17 +03:00
|
|
|
logging.getLogger('fontTools.misc.fixedTools').setLevel(logging.ERROR)
|
|
|
|
|
|
|
|
default_out_dir = os.path.join(BASEDIR,'build','tmp')
|
|
|
|
srcDir = os.path.join(BASEDIR, 'src')
|
|
|
|
|
2018-01-10 07:38:30 +03:00
|
|
|
argparser = argparse.ArgumentParser(
|
|
|
|
description='Build TTF and OTF font files from UFO sources.')
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-10 07:38:30 +03:00
|
|
|
argparser.add_argument(
|
|
|
|
'styles', metavar='<style>', type=str, nargs='*',
|
|
|
|
help='Build specific styles. Omit to build all.')
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-10 07:38:30 +03:00
|
|
|
argparser.add_argument(
|
|
|
|
'--otf', dest='otf', action='store_const',
|
|
|
|
const=True, default=False, help='Build OTF files')
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-10 07:38:30 +03:00
|
|
|
argparser.add_argument(
|
|
|
|
'--no-ttf', dest='no_ttf', action='store_const',
|
|
|
|
const=True, default=False, help='Do not build TTF files')
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-10 07:38:30 +03:00
|
|
|
argparser.add_argument(
|
|
|
|
'--out', dest='out', metavar='<dir>', type=str, default=default_out_dir,
|
|
|
|
help='Write output to <dir> instead of the default (%r)' % default_out_dir)
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
args = argparser.parse_args()
|
|
|
|
styles = [s.lower() for s in args.styles]
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles = []
|
2017-09-04 06:03:17 +03:00
|
|
|
ALL = len(styles) == 0
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
# version control tag, if any
|
|
|
|
buildTag = readVersionControlTag(BASEDIR)
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-10 07:38:30 +03:00
|
|
|
# Since we reference a shared feature file, copy it to build dir so includes
|
|
|
|
# works
|
2017-09-19 01:58:34 +03:00
|
|
|
ufoTmpDir = os.path.join(args.out, 'InterUIUFO')
|
2017-09-05 18:06:04 +03:00
|
|
|
try:
|
|
|
|
os.makedirs(ufoTmpDir)
|
|
|
|
except:
|
|
|
|
pass
|
2017-09-04 06:03:17 +03:00
|
|
|
copyfile(
|
|
|
|
os.path.join(srcDir, 'features.fea'),
|
|
|
|
os.path.join(ufoTmpDir, 'features.fea'))
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
# Load masters
|
|
|
|
print('loading master: Regular')
|
2017-09-19 01:58:34 +03:00
|
|
|
rg = Master("%s/src/Inter-UI-Regular.ufo" % BASEDIR)
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-05 18:06:04 +03:00
|
|
|
bl = None
|
|
|
|
if ALL \
|
|
|
|
or 'black' in styles or 'blackitalic' in styles \
|
|
|
|
or 'bold' in styles or 'bolditalic' in styles \
|
|
|
|
or 'medium' in styles or 'mediumitalic' in styles:
|
|
|
|
print('loading master: Black')
|
2017-09-19 01:58:34 +03:00
|
|
|
bl = Master("%s/src/Inter-UI-Black.ufo" % BASEDIR)
|
2017-08-25 04:27:00 +03:00
|
|
|
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
glyphSpecializations = extractSpecializedGlyphs(rg)
|
2017-08-22 10:05:20 +03:00
|
|
|
|
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
class Mix2(Mix):
|
|
|
|
def __init__(self, masters, v, glyphSpecializations=None):
|
|
|
|
Mix.__init__(self, masters, v)
|
|
|
|
self.glyphSpecializations = glyphSpecializations
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
def mixGlyphs(self, gname):
|
|
|
|
if self.glyphSpecializations is not None:
|
|
|
|
specializedGlyph = self.glyphSpecializations.get(gname)
|
|
|
|
if specializedGlyph is not None:
|
2017-10-24 07:59:39 +03:00
|
|
|
print('mixglyph using specialized', gname)
|
2017-09-04 06:03:17 +03:00
|
|
|
return specializedGlyph
|
|
|
|
return Mix.mixGlyphs(self, gname)
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-10 07:38:30 +03:00
|
|
|
proj = FontProject(
|
|
|
|
rg.font, BASEDIR, os.path.join(srcDir,'fontbuild.cfg'), buildTag=buildTag)
|
2017-09-04 06:03:17 +03:00
|
|
|
proj.builddir = args.out
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-08 21:26:09 +03:00
|
|
|
# panose for entire family
|
|
|
|
panose = {
|
|
|
|
'bFamilyType': 2, # Latin Text
|
|
|
|
'bSerifStyle': 11, # Normal Sans
|
|
|
|
'bProportion': 2, # Old Style
|
2018-01-10 07:38:30 +03:00
|
|
|
'bContrast': 3, # Very Low (thickest vs thinnest stem of "O")
|
|
|
|
'bXHeight': 4, # Constant/Large
|
2018-01-08 21:26:09 +03:00
|
|
|
# bWeight: see http://monotype.de/services/pan2#_Toc380547249
|
|
|
|
}
|
|
|
|
|
|
|
|
def mkpanose(weight):
|
|
|
|
return dict(panose.items() + {
|
|
|
|
'bWeight': weight
|
|
|
|
}.items())
|
|
|
|
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
if args.otf:
|
|
|
|
proj.buildOTF = True
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2018-01-08 21:26:09 +03:00
|
|
|
# name syntax: family/styleName/styleCode/subfamilyAbbrev
|
|
|
|
#
|
|
|
|
# styleCode should be one of:
|
|
|
|
# Regular, Italic, Bold, Bold Italic
|
|
|
|
#
|
2017-10-06 18:40:17 +03:00
|
|
|
# italicNarrowAmount controls scale on the x axis. 1.0 means no scaling.
|
|
|
|
# italicMeanYCenter controls how far on the x axis the glyph should slide
|
|
|
|
# to compensate for the slant.
|
2017-08-25 04:27:00 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
if ALL or 'regular' in styles:
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles.append('regular')
|
2018-01-08 21:26:09 +03:00
|
|
|
proj.generateFont(rg.font, "%s/Regular/Regular/Rg" % FAMILYNAME,
|
|
|
|
panose=mkpanose(5))
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-10-24 07:59:39 +03:00
|
|
|
if ALL or 'italic' in styles:
|
|
|
|
handledStyles.append('italic')
|
2018-01-08 21:26:09 +03:00
|
|
|
proj.generateFont(
|
|
|
|
rg.font, "%s/Italic/Italic/Rg" % FAMILYNAME,
|
|
|
|
italic=True, stemWidth=232, italicMeanYCenter=-825, italicNarrowAmount=1,
|
|
|
|
panose=mkpanose(5))
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-05 18:06:04 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
if ALL or 'medium' in styles:
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles.append('medium')
|
2017-09-04 06:03:17 +03:00
|
|
|
proj.generateFont(
|
2017-09-05 18:06:04 +03:00
|
|
|
Mix2([rg, bl], 0.35, glyphSpecializations.get('medium', {})),
|
2018-01-08 21:26:09 +03:00
|
|
|
"%s/Medium/Regular/Me" % FAMILYNAME,
|
|
|
|
panose=mkpanose(6))
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
if ALL or 'mediumitalic' in styles:
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles.append('mediumitalic')
|
2017-09-04 06:03:17 +03:00
|
|
|
proj.generateFont(
|
2017-09-05 18:06:04 +03:00
|
|
|
Mix2([rg, bl], 0.35, glyphSpecializations.get('medium', {})),
|
|
|
|
"%s/Medium Italic/Italic/Me" % FAMILYNAME,
|
2018-01-08 21:26:09 +03:00
|
|
|
italic=True, stemWidth=300, italicMeanYCenter=-825, italicNarrowAmount=1,
|
|
|
|
panose=mkpanose(6))
|
2017-09-05 18:06:04 +03:00
|
|
|
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
if ALL or 'bold' in styles:
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles.append('bold')
|
2017-09-05 18:06:04 +03:00
|
|
|
proj.generateFont(
|
|
|
|
Mix2([rg, bl], 0.65, glyphSpecializations.get('bold', {})),
|
2018-01-08 21:26:09 +03:00
|
|
|
"%s/Bold/Bold/Rg" % FAMILYNAME,
|
|
|
|
panose=mkpanose(8))
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
if ALL or 'bolditalic' in styles:
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles.append('bolditalic')
|
2017-09-05 18:06:04 +03:00
|
|
|
proj.generateFont(
|
|
|
|
Mix2([rg, bl], 0.65, glyphSpecializations.get('bold', {})),
|
2018-01-08 21:26:09 +03:00
|
|
|
"%s/Bold Italic/Bold Italic/Rg" % FAMILYNAME,
|
|
|
|
italic=True, stemWidth=350, italicMeanYCenter=-825, italicNarrowAmount=1,
|
|
|
|
panose=mkpanose(8))
|
2017-09-05 18:06:04 +03:00
|
|
|
|
|
|
|
|
|
|
|
if ALL or 'black' in styles:
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles.append('black')
|
2018-01-08 21:26:09 +03:00
|
|
|
proj.generateFont(bl.font, "%s/Black/Regular/Bl" % FAMILYNAME,
|
|
|
|
panose=mkpanose(9))
|
2017-09-05 18:06:04 +03:00
|
|
|
|
|
|
|
if ALL or 'blackitalic' in styles:
|
2017-10-24 07:59:39 +03:00
|
|
|
handledStyles.append('blackitalic')
|
2018-01-08 21:26:09 +03:00
|
|
|
proj.generateFont(
|
|
|
|
bl.font, "%s/Black Italic/Italic/Bl" % FAMILYNAME,
|
|
|
|
italic=True, stemWidth=400, italicMeanYCenter=-825, italicNarrowAmount=1,
|
|
|
|
panose=mkpanose(9))
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
# generate TTFs
|
|
|
|
if args.no_ttf == False:
|
|
|
|
proj.generateTTFs()
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-10-24 07:59:39 +03:00
|
|
|
if not ALL:
|
|
|
|
diff = set(styles).difference(set(handledStyles))
|
|
|
|
if len(diff) != 0:
|
|
|
|
print('Unknown styles %r' % diff, file=sys.stderr)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
main()
|
2017-08-22 10:05:20 +03:00
|
|
|
|
2017-09-04 06:03:17 +03:00
|
|
|
# import hotshot, hotshot.stats, test.pystone
|
|
|
|
# prof = hotshot.Profile("ufocompile.prof")
|
|
|
|
# benchtime = prof.runcall(main)
|
|
|
|
# prof.close()
|
|
|
|
# stats = hotshot.stats.load("ufocompile.prof")
|
|
|
|
# # stats.strip_dirs()
|
|
|
|
# stats.sort_stats('time', 'calls')
|
|
|
|
# stats.print_stats(40)
|