mirror of
https://github.com/rsms/inter.git
synced 2024-12-25 08:33:40 +03:00
New version of fontbuild which addresses several issues
Fixes for things that stopped working when we updated fontmake: - restore glyph decomposition for VF - restore glyph overlap removal for VF - restore version metadata writing for VF Improvements for VF - fix "full name" name table entry to say "Inter" instead of "Inter Regular" New and changed: - "rename" command for renaming metadata like family and style, optionally saving a separate file. Used to produce new "Inter V" family. - The "build" command no longer performs "style name compactation" for Google fonts. Instead, the new "rename" command is used. Closes #198 Closes #202
This commit is contained in:
parent
bc8b267b01
commit
0ba7c2b42f
232
misc/fontbuild
232
misc/fontbuild
@ -16,6 +16,8 @@ import re
|
|||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import ufo2ft
|
import ufo2ft
|
||||||
|
import font_names
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from fontmake.font_project import FontProject
|
from fontmake.font_project import FontProject
|
||||||
from defcon import Font
|
from defcon import Font
|
||||||
@ -110,28 +112,11 @@ def findGlyphDirectives(g): # -> set<string> | None
|
|||||||
return directives
|
return directives
|
||||||
|
|
||||||
|
|
||||||
class VarFontProject(FontProject):
|
|
||||||
def __init__(self, familyName=None, compact_style_names=False, *args, **kwargs):
|
|
||||||
super(VarFontProject, self).__init__(*args, **kwargs)
|
|
||||||
self.familyName = familyName
|
|
||||||
self.compact_style_names = compact_style_names
|
|
||||||
|
|
||||||
|
def deep_copy_contours(ufo, parent, component, transformation):
|
||||||
def decompose_glyphs(self, designspace, glyph_filter=lambda g: True):
|
|
||||||
"""Move components of UFOs' glyphs to their outlines."""
|
|
||||||
for ufo in designspace:
|
|
||||||
log.info('Decomposing glyphs for ' + self._font_name(ufo))
|
|
||||||
for glyph in ufo:
|
|
||||||
if not glyph.components or not glyph_filter(glyph):
|
|
||||||
continue
|
|
||||||
self._deep_copy_contours(ufo, glyph, glyph, Transform())
|
|
||||||
glyph.clearComponents()
|
|
||||||
|
|
||||||
|
|
||||||
def _deep_copy_contours(self, ufo, parent, component, transformation):
|
|
||||||
"""Copy contours from component to parent, including nested components."""
|
"""Copy contours from component to parent, including nested components."""
|
||||||
for nested in component.components:
|
for nested in component.components:
|
||||||
self._deep_copy_contours(
|
deep_copy_contours(
|
||||||
ufo, parent, ufo[nested.baseGlyph],
|
ufo, parent, ufo[nested.baseGlyph],
|
||||||
transformation.transform(nested.transformation))
|
transformation.transform(nested.transformation))
|
||||||
if component != parent:
|
if component != parent:
|
||||||
@ -144,90 +129,79 @@ class VarFontProject(FontProject):
|
|||||||
component.draw(pen)
|
component.draw(pen)
|
||||||
|
|
||||||
|
|
||||||
def _build_interpolatable_masters(
|
|
||||||
self,
|
|
||||||
designspace,
|
|
||||||
ttf,
|
|
||||||
use_production_names=None,
|
|
||||||
reverse_direction=True,
|
|
||||||
conversion_error=None,
|
|
||||||
feature_writers=None,
|
|
||||||
cff_round_tolerance=None,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
# We decompose any glyph with reflected components to make sure
|
|
||||||
# that fontTools varLib is able to produce properly-slanting interpolation.
|
|
||||||
|
|
||||||
designspace = self._load_designspace_sources(designspace)
|
def decompose_glyphs(ufos, glyphNamesToDecompose):
|
||||||
|
for ufo in ufos:
|
||||||
|
for glyphname in glyphNamesToDecompose:
|
||||||
|
glyph = ufo[glyphname]
|
||||||
|
deep_copy_contours(ufo, glyph, glyph, Transform())
|
||||||
|
glyph.clearComponents()
|
||||||
|
|
||||||
decomposeGlyphs = set()
|
|
||||||
removeOverlapsGlyphs = set()
|
|
||||||
masters = [s.font for s in designspace.sources]
|
# subclass of fontmake.FontProject that
|
||||||
|
# - patches version metadata
|
||||||
|
# - decomposes certain glyphs
|
||||||
|
# - removes overlaps of certain glyphs
|
||||||
|
#
|
||||||
|
class VarFontProject(FontProject):
|
||||||
|
def __init__(self, compact_style_names=False, *args, **kwargs):
|
||||||
|
super(VarFontProject, self).__init__(*args, **kwargs)
|
||||||
|
self.compact_style_names = compact_style_names
|
||||||
|
|
||||||
|
|
||||||
|
# override FontProject._load_designspace_sources
|
||||||
|
def _load_designspace_sources(self, designspace):
|
||||||
|
designspace = FontProject._load_designspace_sources(designspace)
|
||||||
|
masters = [s.font for s in designspace.sources] # list of UFO font objects
|
||||||
|
|
||||||
|
# Update the default source's full name to not include style name
|
||||||
|
defaultFont = designspace.default.font
|
||||||
|
defaultFont.info.openTypeNameCompatibleFullName = defaultFont.info.familyName
|
||||||
|
|
||||||
for ufo in masters:
|
for ufo in masters:
|
||||||
|
|
||||||
if self.familyName is not None:
|
|
||||||
ufo.info.familyName =\
|
|
||||||
ufo.info.familyName.replace('Inter', self.familyName)
|
|
||||||
ufo.info.styleMapFamilyName =\
|
|
||||||
ufo.info.styleMapFamilyName.replace('Inter', self.familyName)
|
|
||||||
ufo.info.postscriptFontName =\
|
|
||||||
ufo.info.postscriptFontName.replace('Inter', self.familyName.replace(' ', ''))
|
|
||||||
ufo.info.macintoshFONDName =\
|
|
||||||
ufo.info.macintoshFONDName.replace('Inter', self.familyName)
|
|
||||||
ufo.info.openTypeNamePreferredFamilyName =\
|
|
||||||
ufo.info.openTypeNamePreferredFamilyName.replace('Inter', self.familyName)
|
|
||||||
|
|
||||||
# patch style name if --compact-style-names is set
|
# patch style name if --compact-style-names is set
|
||||||
if args.compact_style_names:
|
if self.compact_style_names:
|
||||||
collapseFontStyleName(ufo)
|
collapseFontStyleName(ufo)
|
||||||
|
# update font version
|
||||||
|
updateFontVersion(ufo, isVF=True)
|
||||||
|
|
||||||
updateFontVersion(ufo)
|
# find glyphs subject to decomposition and/or overlap removal
|
||||||
ufoname = basename(ufo.path)
|
glyphNamesToDecompose = set() # glyph names
|
||||||
|
glyphsToRemoveOverlaps = set() # glyph names
|
||||||
|
for ufo in masters:
|
||||||
for g in ufo:
|
for g in ufo:
|
||||||
directives = findGlyphDirectives(g)
|
directives = findGlyphDirectives(g)
|
||||||
if g.components and composedGlyphIsNonTrivial(g):
|
if g.components and composedGlyphIsNonTrivial(g):
|
||||||
decomposeGlyphs.add(g.name)
|
glyphNamesToDecompose.add(g.name)
|
||||||
if 'removeoverlap' in directives:
|
if 'removeoverlap' in directives:
|
||||||
if g.components and len(g.components) > 0:
|
if g.components and len(g.components) > 0:
|
||||||
decomposeGlyphs.add(g.name)
|
glyphNamesToDecompose.add(g.name)
|
||||||
removeOverlapsGlyphs.add(g)
|
glyphsToRemoveOverlaps.add(g)
|
||||||
|
|
||||||
self.decompose_glyphs(masters, lambda g: g.name in decomposeGlyphs)
|
# decompose
|
||||||
|
if log.isEnabledFor(logging.INFO):
|
||||||
|
log.info('Decomposing glyphs:\n %s', "\n ".join(glyphNamesToDecompose))
|
||||||
|
decompose_glyphs(masters, glyphNamesToDecompose)
|
||||||
|
|
||||||
if len(removeOverlapsGlyphs) > 0:
|
# remove overlaps
|
||||||
|
if len(glyphsToRemoveOverlaps) > 0:
|
||||||
rmoverlapFilter = RemoveOverlapsFilter(backend='pathops')
|
rmoverlapFilter = RemoveOverlapsFilter(backend='pathops')
|
||||||
rmoverlapFilter.start()
|
rmoverlapFilter.start()
|
||||||
for g in removeOverlapsGlyphs:
|
if log.isEnabledFor(logging.INFO):
|
||||||
log.info(
|
log.info(
|
||||||
'Removing overlaps in glyph "%s" of %s',
|
'Removing overlaps in glyphs:\n %s',
|
||||||
g.name,
|
"\n ".join(set([g.name for g in glyphsToRemoveOverlaps])),
|
||||||
basename(g.getParent().path)
|
|
||||||
)
|
)
|
||||||
|
for g in glyphsToRemoveOverlaps:
|
||||||
rmoverlapFilter.filter(g)
|
rmoverlapFilter.filter(g)
|
||||||
|
|
||||||
|
# handle control back to fontmake
|
||||||
if ttf:
|
return designspace
|
||||||
return ufo2ft.compileInterpolatableTTFsFromDS(
|
|
||||||
designspace,
|
|
||||||
useProductionNames=use_production_names,
|
|
||||||
reverseDirection=reverse_direction,
|
|
||||||
cubicConversionError=conversion_error,
|
|
||||||
featureWriters=feature_writers,
|
|
||||||
inplace=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return ufo2ft.compileInterpolatableOTFsFromDS(
|
|
||||||
designspace,
|
|
||||||
useProductionNames=use_production_names,
|
|
||||||
roundTolerance=cff_round_tolerance,
|
|
||||||
featureWriters=feature_writers,
|
|
||||||
inplace=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def updateFontVersion(font, dummy=False):
|
|
||||||
|
def updateFontVersion(font, dummy=False, isVF=False):
|
||||||
version = getVersion()
|
version = getVersion()
|
||||||
buildtag = getGitHash()
|
buildtag = getGitHash()
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
@ -242,11 +216,13 @@ def updateFontVersion(font, dummy=False):
|
|||||||
font.info.woffMajorVersion = versionMajor
|
font.info.woffMajorVersion = versionMajor
|
||||||
font.info.woffMinorVersion = versionMinor
|
font.info.woffMinorVersion = versionMinor
|
||||||
font.info.year = now.year
|
font.info.year = now.year
|
||||||
font.info.openTypeNameVersion = "Version %d.%03d;git-%s" % (
|
font.info.openTypeNameVersion = "Version %d.%03d;git-%s" % (versionMajor, versionMinor, buildtag)
|
||||||
versionMajor, versionMinor, buildtag)
|
psFamily = re.sub(r'\s', '', font.info.familyName)
|
||||||
font.info.openTypeNameUniqueID = "%s %s:%d:%s" % (
|
if isVF:
|
||||||
font.info.familyName, font.info.styleName, now.year, buildtag)
|
font.info.openTypeNameUniqueID = "%s:VF:%d:%s" % (psFamily, now.year, buildtag)
|
||||||
# creation date & time (YYYY/MM/DD HH:MM:SS)
|
else:
|
||||||
|
psStyle = re.sub(r'\s', '', font.info.styleName)
|
||||||
|
font.info.openTypeNameUniqueID = "%s-%s:%d:%s" % (psFamily, psStyle, now.year, buildtag)
|
||||||
font.info.openTypeHeadCreated = now.strftime("%Y/%m/%d %H:%M:%S")
|
font.info.openTypeHeadCreated = now.strftime("%Y/%m/%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
@ -362,6 +338,8 @@ class Main(object):
|
|||||||
compile-var Build variable font files
|
compile-var Build variable font files
|
||||||
glyphsync Generate designspace and UFOs from Glyphs file
|
glyphsync Generate designspace and UFOs from Glyphs file
|
||||||
instancegen Generate instance UFOs for designspace
|
instancegen Generate instance UFOs for designspace
|
||||||
|
checkfont Verify integrity of font files
|
||||||
|
rename Rename fonts
|
||||||
'''.strip().replace('\n ', '\n'))
|
'''.strip().replace('\n ', '\n'))
|
||||||
|
|
||||||
argparser.add_argument('-v', '--verbose', action='store_true',
|
argparser.add_argument('-v', '--verbose', action='store_true',
|
||||||
@ -426,9 +404,6 @@ class Main(object):
|
|||||||
argparser.add_argument('-o', '--output', metavar='<fontfile>',
|
argparser.add_argument('-o', '--output', metavar='<fontfile>',
|
||||||
help='Output font file')
|
help='Output font file')
|
||||||
|
|
||||||
argparser.add_argument('--name', metavar='<family-name>',
|
|
||||||
help='Override family name, replacing "Inter"')
|
|
||||||
|
|
||||||
argparser.add_argument('--compact-style-names', action='store_true',
|
argparser.add_argument('--compact-style-names', action='store_true',
|
||||||
help="Produce font files with style names that doesn't contain spaces. "\
|
help="Produce font files with style names that doesn't contain spaces. "\
|
||||||
"E.g. \"SemiBoldItalic\" instead of \"Semi Bold Italic\"")
|
"E.g. \"SemiBoldItalic\" instead of \"Semi Bold Italic\"")
|
||||||
@ -437,27 +412,14 @@ class Main(object):
|
|||||||
|
|
||||||
# decide output filename (or check user-provided name)
|
# decide output filename (or check user-provided name)
|
||||||
outfilename = args.output
|
outfilename = args.output
|
||||||
outformat = 'variable' # TTF
|
|
||||||
if outfilename is None or outfilename == '':
|
if outfilename is None or outfilename == '':
|
||||||
outfilename = os.path.splitext(basename(args.srcfile))[0] + '.var.ttf'
|
outfilename = os.path.splitext(basename(args.srcfile))[0] + '.var.otf'
|
||||||
log.info('setting --output %r' % outfilename)
|
log.info('setting --output %r' % outfilename)
|
||||||
else:
|
|
||||||
outfileext = os.path.splitext(outfilename)[1]
|
|
||||||
if outfileext.lower() == '.otf':
|
|
||||||
outformat = 'variable-cff2'
|
|
||||||
elif outfileext.lower() != '.ttf':
|
|
||||||
fatal('Invalid file extension %r (expected ".ttf")' % outfileext)
|
|
||||||
|
|
||||||
mkdirs(dirname(outfilename))
|
mkdirs(dirname(outfilename))
|
||||||
|
|
||||||
# override family name?
|
|
||||||
familyName = None
|
|
||||||
if args.name is not None and len(args.name) > 0:
|
|
||||||
familyName = args.name
|
|
||||||
|
|
||||||
project = VarFontProject(
|
project = VarFontProject(
|
||||||
verbose=self.logLevelName,
|
verbose=self.logLevelName,
|
||||||
familyName=familyName,
|
|
||||||
compact_style_names=args.compact_style_names,
|
compact_style_names=args.compact_style_names,
|
||||||
)
|
)
|
||||||
project.run_from_designspace(
|
project.run_from_designspace(
|
||||||
@ -467,11 +429,22 @@ class Main(object):
|
|||||||
use_production_names=True,
|
use_production_names=True,
|
||||||
round_instances=True,
|
round_instances=True,
|
||||||
output_path=outfilename,
|
output_path=outfilename,
|
||||||
output=[outformat],
|
output=["variable"], # "variable-cff2" in the future
|
||||||
optimize_cff=CFFOptimization.SUBROUTINIZE,
|
optimize_cff=CFFOptimization.SUBROUTINIZE,
|
||||||
overlaps_backend='pathops', # use Skia's pathops
|
overlaps_backend='pathops', # use Skia's pathops
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Rename fullName record to familyName (VF only)
|
||||||
|
# Note: Even though we set openTypeNameCompatibleFullName it seems that the fullName
|
||||||
|
# record is still computed by fonttools, so we override it here.
|
||||||
|
font = font_names.loadFont(outfilename)
|
||||||
|
try:
|
||||||
|
familyName = font_names.getFamilyName(font)
|
||||||
|
font_names.setFullName(font, familyName)
|
||||||
|
font.save(outfilename)
|
||||||
|
finally:
|
||||||
|
font.close()
|
||||||
|
|
||||||
self.log("write %s" % outfilename)
|
self.log("write %s" % outfilename)
|
||||||
|
|
||||||
# Note: we can't run ots-sanitize on the generated file as OTS
|
# Note: we can't run ots-sanitize on the generated file as OTS
|
||||||
@ -652,7 +625,7 @@ class Main(object):
|
|||||||
italic = False
|
italic = False
|
||||||
if tag == 'italic':
|
if tag == 'italic':
|
||||||
italic = True
|
italic = True
|
||||||
elif tag != 'upright':
|
elif tag != 'roman':
|
||||||
raise Exception('unexpected tag ' + tag)
|
raise Exception('unexpected tag ' + tag)
|
||||||
|
|
||||||
for a in ds.axes:
|
for a in ds.axes:
|
||||||
@ -785,11 +758,11 @@ class Main(object):
|
|||||||
self.log("write %s" % relpath(designspace_file, os.getcwd()))
|
self.log("write %s" % relpath(designspace_file, os.getcwd()))
|
||||||
designspace.write(designspace_file)
|
designspace.write(designspace_file)
|
||||||
|
|
||||||
# upright designspace
|
# roman designspace
|
||||||
upright_designspace_file = pjoin(outdir, 'Inter-upright.designspace')
|
roman_designspace_file = pjoin(outdir, 'Inter-roman.designspace')
|
||||||
p = Process(
|
p = Process(
|
||||||
target=self._genSubsetDesignSpace,
|
target=self._genSubsetDesignSpace,
|
||||||
args=(designspace, 'upright', upright_designspace_file)
|
args=(designspace, 'roman', roman_designspace_file)
|
||||||
)
|
)
|
||||||
p.start()
|
p.start()
|
||||||
procs.append(p)
|
procs.append(p)
|
||||||
@ -929,5 +902,48 @@ class Main(object):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_rename(self, argv):
|
||||||
|
argparser = argparse.ArgumentParser(
|
||||||
|
usage='%(prog)s rename <options> <file>',
|
||||||
|
description='Rename family and/or styles of font'
|
||||||
|
)
|
||||||
|
a = lambda *args, **kwargs: argparser.add_argument(*args, **kwargs)
|
||||||
|
|
||||||
|
a('-o', '--output', metavar='<file>',
|
||||||
|
help='Output font file. Defaults to input file (overwrite.)')
|
||||||
|
a('--family', metavar='<name>',
|
||||||
|
help='Rename family to <name>')
|
||||||
|
a('--compact-style', action='store_true',
|
||||||
|
help='Rename style names to CamelCase. e.g. "Extra Bold Italic" -> "ExtraBoldItalic"')
|
||||||
|
a('input', metavar='<file>',
|
||||||
|
help='Input font file')
|
||||||
|
|
||||||
|
args = argparser.parse_args(argv)
|
||||||
|
|
||||||
|
infile = args.input
|
||||||
|
outfile = args.output or infile
|
||||||
|
|
||||||
|
font = font_names.loadFont(infile)
|
||||||
|
editCount = 0
|
||||||
|
try:
|
||||||
|
if args.family:
|
||||||
|
editCount += 1
|
||||||
|
font_names.setFamilyName(font, args.family)
|
||||||
|
|
||||||
|
if args.compact_style:
|
||||||
|
editCount += 1
|
||||||
|
font_names.removeWhitespaceFromStyles(font)
|
||||||
|
|
||||||
|
if editCount > 0:
|
||||||
|
font.save(outfile)
|
||||||
|
else:
|
||||||
|
print("no rename options provided", file=sys.stderr)
|
||||||
|
argparser.print_usage(sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
font.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
Main().main(sys.argv)
|
Main().main(sys.argv)
|
||||||
|
148
misc/tools/font_names.py
Normal file
148
misc/tools/font_names.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
import os, sys, re
|
||||||
|
|
||||||
|
# Adoptation of fonttools/blob/master/Snippets/rename-fonts.py
|
||||||
|
|
||||||
|
WINDOWS_ENGLISH_IDS = 3, 1, 0x409
|
||||||
|
MAC_ROMAN_IDS = 1, 0, 0
|
||||||
|
|
||||||
|
LEGACY_FAMILY = 1
|
||||||
|
TRUETYPE_UNIQUE_ID = 3
|
||||||
|
FULL_NAME = 4
|
||||||
|
POSTSCRIPT_NAME = 6
|
||||||
|
PREFERRED_FAMILY = 16
|
||||||
|
SUBFAMILY_NAME = 17
|
||||||
|
WWS_FAMILY = 21
|
||||||
|
|
||||||
|
|
||||||
|
FAMILY_RELATED_IDS = set([
|
||||||
|
LEGACY_FAMILY,
|
||||||
|
TRUETYPE_UNIQUE_ID,
|
||||||
|
FULL_NAME,
|
||||||
|
POSTSCRIPT_NAME,
|
||||||
|
PREFERRED_FAMILY,
|
||||||
|
WWS_FAMILY,
|
||||||
|
])
|
||||||
|
|
||||||
|
whitespace_re = re.compile(r'\s+')
|
||||||
|
|
||||||
|
|
||||||
|
def removeWhitespace(s):
|
||||||
|
return whitespace_re.sub("", s)
|
||||||
|
|
||||||
|
|
||||||
|
def setFullName(font, fullName):
|
||||||
|
nameTable = font["name"]
|
||||||
|
nameTable.setName(fullName, FULL_NAME, 1, 0, 0) # mac
|
||||||
|
nameTable.setName(fullName, FULL_NAME, 3, 1, 0x409) # windows
|
||||||
|
|
||||||
|
|
||||||
|
def getFamilyName(font):
|
||||||
|
nameTable = font["name"]
|
||||||
|
r = None
|
||||||
|
for plat_id, enc_id, lang_id in (WINDOWS_ENGLISH_IDS, MAC_ROMAN_IDS):
|
||||||
|
for name_id in (PREFERRED_FAMILY, LEGACY_FAMILY):
|
||||||
|
r = nameTable.getName(nameID=name_id, platformID=plat_id, platEncID=enc_id, langID=lang_id)
|
||||||
|
if r is not None:
|
||||||
|
break
|
||||||
|
if r is not None:
|
||||||
|
break
|
||||||
|
if not r:
|
||||||
|
raise ValueError("family name not found")
|
||||||
|
return r.toUnicode()
|
||||||
|
|
||||||
|
|
||||||
|
def removeWhitespaceFromStyles(font):
|
||||||
|
familyName = getFamilyName(font)
|
||||||
|
|
||||||
|
# collect subfamily (style) name IDs for variable font's named instances
|
||||||
|
vfInstanceSubfamilyNameIds = set()
|
||||||
|
if "fvar" in font:
|
||||||
|
for namedInstance in font["fvar"].instances:
|
||||||
|
vfInstanceSubfamilyNameIds.add(namedInstance.subfamilyNameID)
|
||||||
|
|
||||||
|
nameTable = font["name"]
|
||||||
|
for rec in nameTable.names:
|
||||||
|
rid = rec.nameID
|
||||||
|
if rid in (FULL_NAME, LEGACY_FAMILY):
|
||||||
|
# style part of family name
|
||||||
|
s = rec.toUnicode()
|
||||||
|
start = s.find(familyName)
|
||||||
|
if start != -1:
|
||||||
|
s = familyName + " " + removeWhitespace(s[start + len(familyName):])
|
||||||
|
else:
|
||||||
|
s = removeWhitespace(s)
|
||||||
|
rec.string = s
|
||||||
|
if rid in (SUBFAMILY_NAME,) or rid in vfInstanceSubfamilyNameIds:
|
||||||
|
rec.string = removeWhitespace(rec.toUnicode())
|
||||||
|
# else: ignore standard names unrelated to style
|
||||||
|
|
||||||
|
|
||||||
|
def setFamilyName(font, nextFamilyName):
|
||||||
|
prevFamilyName = getFamilyName(font)
|
||||||
|
if prevFamilyName == nextFamilyName:
|
||||||
|
return
|
||||||
|
# raise Exception("identical family name")
|
||||||
|
|
||||||
|
def renameRecord(nameRecord, prevFamilyName, nextFamilyName):
|
||||||
|
# replaces prevFamilyName with nextFamilyName in nameRecord
|
||||||
|
s = nameRecord.toUnicode()
|
||||||
|
start = s.find(prevFamilyName)
|
||||||
|
if start != -1:
|
||||||
|
end = start + len(prevFamilyName)
|
||||||
|
nextFamilyName = s[:start] + nextFamilyName + s[end:]
|
||||||
|
nameRecord.string = nextFamilyName
|
||||||
|
return s, nextFamilyName
|
||||||
|
|
||||||
|
# postcript name can't contain spaces
|
||||||
|
psPrevFamilyName = prevFamilyName.replace(" ", "")
|
||||||
|
psNextFamilyName = nextFamilyName.replace(" ", "")
|
||||||
|
for rec in font["name"].names:
|
||||||
|
name_id = rec.nameID
|
||||||
|
if name_id not in FAMILY_RELATED_IDS:
|
||||||
|
# leave uninteresting records unmodified
|
||||||
|
continue
|
||||||
|
if name_id == POSTSCRIPT_NAME:
|
||||||
|
old, new = renameRecord(rec, psPrevFamilyName, psNextFamilyName)
|
||||||
|
elif name_id == TRUETYPE_UNIQUE_ID:
|
||||||
|
# The Truetype Unique ID rec may contain either the PostScript Name or the Full Name
|
||||||
|
if psPrevFamilyName in rec.toUnicode():
|
||||||
|
# Note: This is flawed -- a font called "Foo" renamed to "Bar Lol";
|
||||||
|
# if this record is not a PS record, it will incorrectly be rename "BarLol".
|
||||||
|
# However, in practice this is not abig deal since it's just an ID.
|
||||||
|
old, new = renameRecord(rec, psPrevFamilyName, psNextFamilyName)
|
||||||
|
else:
|
||||||
|
old, new = renameRecord(rec, prevFamilyName, nextFamilyName)
|
||||||
|
else:
|
||||||
|
old, new = renameRecord(rec, prevFamilyName, nextFamilyName)
|
||||||
|
# print(" %r: '%s' -> '%s'" % (rec, old, new))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def loadFont(file):
|
||||||
|
return TTFont(file, recalcBBoxes=False, recalcTimestamp=False)
|
||||||
|
|
||||||
|
|
||||||
|
def renameFontFamily(infile, outfile, newFamilyName):
|
||||||
|
font = loadFont(infile)
|
||||||
|
setFamilyName(font, newFamilyName)
|
||||||
|
# print('write "%s"' % outfile)
|
||||||
|
font.save(outfile)
|
||||||
|
font.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
infile = "./build/fonts/var/Inter.var.ttf"
|
||||||
|
outfile = "./build/tmp/var2.otf"
|
||||||
|
renameFontFamily(infile, outfile, "Inter V")
|
||||||
|
print("%s familyName: %r" % (infile, getFamilyName(loadFont(infile)) ))
|
||||||
|
print("%s familyName: %r" % (outfile, getFamilyName(loadFont(outfile)) ))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
# Similar to:
|
||||||
|
# ttx -i -e -o ./build/tmp/var.ttx ./build/fonts/var/Inter.var.ttf
|
||||||
|
# ttx -b --no-recalc-timestamp -o ./build/tmp/var.otf ./build/tmp/var.ttx
|
Loading…
Reference in New Issue
Block a user