2013-09-18 20:47:53 -04:00
|
|
|
# Copyright 2013 Google, Inc. All Rights Reserved.
|
|
|
|
#
|
2013-12-18 00:45:12 -08:00
|
|
|
# Google Author(s): Behdad Esfahbod, Roozbeh Pournader
|
2013-09-18 20:47:53 -04:00
|
|
|
|
2021-12-16 08:40:38 -07:00
|
|
|
from fontTools import ttLib
|
|
|
|
from fontTools.merge.base import *
|
2021-12-16 10:13:33 -07:00
|
|
|
from fontTools.merge.cmap import *
|
2021-12-16 08:47:44 -07:00
|
|
|
from fontTools.merge.layout import *
|
2021-12-16 08:58:37 -07:00
|
|
|
from fontTools.merge.options import *
|
2021-12-16 08:35:24 -07:00
|
|
|
from fontTools.merge.util import *
|
2016-01-24 16:16:11 +00:00
|
|
|
from fontTools.misc.loggingTools import Timer
|
2013-12-18 17:14:26 -05:00
|
|
|
from functools import reduce
|
2013-09-18 20:47:53 -04:00
|
|
|
import sys
|
2016-01-24 16:16:11 +00:00
|
|
|
import logging
|
2021-11-17 05:12:21 +02:00
|
|
|
|
2016-01-24 16:16:11 +00:00
|
|
|
|
2017-01-11 11:58:17 +00:00
|
|
|
log = logging.getLogger("fontTools.merge")
|
2016-01-24 16:16:11 +00:00
|
|
|
timer = Timer(logger=logging.getLogger(__name__+".timer"), level=logging.INFO)
|
2013-09-18 20:47:53 -04:00
|
|
|
|
|
|
|
|
2013-12-19 04:20:26 -05:00
|
|
|
class Merger(object):
|
2020-05-14 10:06:13 +01:00
|
|
|
"""Font merger.
|
|
|
|
|
|
|
|
This class merges multiple files into a single OpenType font, taking into
|
|
|
|
account complexities such as OpenType layout (``GSUB``/``GPOS``) tables and
|
|
|
|
cross-font metrics (e.g. ``hhea.ascent`` is set to the maximum value across
|
|
|
|
all the fonts).
|
|
|
|
|
|
|
|
If multiple glyphs map to the same Unicode value, and the glyphs are considered
|
|
|
|
sufficiently different (that is, they differ in any of paths, widths, or
|
|
|
|
height), then subsequent glyphs are renamed and a lookup in the ``locl``
|
|
|
|
feature will be created to disambiguate them. For example, if the arguments
|
|
|
|
are an Arabic font and a Latin font and both contain a set of parentheses,
|
|
|
|
the Latin glyphs will be renamed to ``parenleft#1`` and ``parenright#1``,
|
|
|
|
and a lookup will be inserted into the to ``locl`` feature (creating it if
|
|
|
|
necessary) under the ``latn`` script to substitute ``parenleft`` with
|
|
|
|
``parenleft#1`` etc.
|
|
|
|
|
|
|
|
Restrictions:
|
|
|
|
|
|
|
|
- All fonts must have the same units per em.
|
|
|
|
- If duplicate glyph disambiguation takes place as described above then the
|
|
|
|
fonts must have a ``GSUB`` table.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
options: Currently unused.
|
|
|
|
"""
|
2013-09-18 20:47:53 -04:00
|
|
|
|
2016-01-24 16:16:11 +00:00
|
|
|
def __init__(self, options=None):
|
2013-09-19 16:16:39 -04:00
|
|
|
|
|
|
|
if not options:
|
|
|
|
options = Options()
|
|
|
|
|
|
|
|
self.options = options
|
2013-09-18 20:47:53 -04:00
|
|
|
|
2021-12-16 10:42:56 -07:00
|
|
|
def _openFonts(self, fontfiles):
|
|
|
|
fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
|
|
|
|
for font,fontfile in zip(fonts, fontfiles):
|
|
|
|
font.fontfile = fontfile
|
2021-12-16 10:48:10 -07:00
|
|
|
font.name = font['name'].getDebugName(4)
|
2021-12-16 10:42:56 -07:00
|
|
|
return fonts
|
|
|
|
|
2013-09-19 16:16:39 -04:00
|
|
|
def merge(self, fontfiles):
|
2020-05-14 10:06:13 +01:00
|
|
|
"""Merges fonts together.
|
2018-09-18 14:24:44 +01:00
|
|
|
|
2020-05-14 10:06:13 +01:00
|
|
|
Args:
|
|
|
|
fontfiles: A list of file names to be merged
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A :class:`fontTools.ttLib.TTFont` object. Call the ``save`` method on
|
|
|
|
this to write it out to an OTF file.
|
|
|
|
"""
|
2013-09-18 20:47:53 -04:00
|
|
|
#
|
|
|
|
# Settle on a mega glyph order.
|
|
|
|
#
|
2021-12-16 10:42:56 -07:00
|
|
|
fonts = self._openFonts(fontfiles)
|
2021-12-16 13:29:43 -07:00
|
|
|
glyphOrders = [list(font.getGlyphOrder()) for font in fonts]
|
|
|
|
computeMegaGlyphOrder(self, glyphOrders)
|
2021-11-17 06:32:04 +02:00
|
|
|
|
2021-11-17 06:44:39 +02:00
|
|
|
# Take first input file sfntVersion
|
|
|
|
sfntVersion = fonts[0].sfntVersion
|
|
|
|
|
2013-09-18 20:47:53 -04:00
|
|
|
# Reload fonts and set new glyph names on them.
|
2021-12-16 10:42:56 -07:00
|
|
|
fonts = self._openFonts(fontfiles)
|
2021-12-16 13:29:43 -07:00
|
|
|
for font,glyphOrder in zip(fonts, glyphOrders):
|
2021-11-17 07:09:10 +02:00
|
|
|
font.setGlyphOrder(glyphOrder)
|
2021-12-16 13:29:43 -07:00
|
|
|
if 'CFF ' in font:
|
|
|
|
renameCFFCharStrings(self, glyphOrder, font['CFF '])
|
|
|
|
|
|
|
|
cmaps = [font['cmap'] for font in fonts]
|
|
|
|
self.duplicateGlyphsPerFont = [{} for _ in fonts]
|
|
|
|
computeMegaCmap(self, cmaps)
|
2018-09-18 15:06:08 +01:00
|
|
|
|
2021-11-17 06:44:39 +02:00
|
|
|
mega = ttLib.TTFont(sfntVersion=sfntVersion)
|
2021-12-16 13:29:43 -07:00
|
|
|
mega.setGlyphOrder(self.glyphOrder)
|
2013-09-18 20:47:53 -04:00
|
|
|
|
2013-12-19 11:53:47 -05:00
|
|
|
for font in fonts:
|
|
|
|
self._preMerge(font)
|
|
|
|
|
2018-02-18 14:38:57 -08:00
|
|
|
self.fonts = fonts
|
2021-12-16 10:13:33 -07:00
|
|
|
|
2013-12-18 17:14:26 -05:00
|
|
|
allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
|
2013-09-18 20:47:53 -04:00
|
|
|
allTags.remove('GlyphOrder')
|
2014-07-09 17:13:16 -04:00
|
|
|
|
2013-09-18 20:47:53 -04:00
|
|
|
for tag in allTags:
|
2021-12-14 16:13:24 -07:00
|
|
|
if tag in self.options.drop_tables:
|
|
|
|
continue
|
|
|
|
|
2016-01-24 16:16:11 +00:00
|
|
|
with timer("merge '%s'" % tag):
|
|
|
|
tables = [font.get(tag, NotImplemented) for font in fonts]
|
2013-09-19 16:16:39 -04:00
|
|
|
|
2016-07-13 15:00:36 -06:00
|
|
|
log.info("Merging '%s'.", tag)
|
2016-01-24 16:16:11 +00:00
|
|
|
clazz = ttLib.getTableClass(tag)
|
|
|
|
table = clazz(tag).merge(self, tables)
|
|
|
|
# XXX Clean this up and use: table = mergeObjects(tables)
|
2014-03-28 15:37:18 -07:00
|
|
|
|
2016-01-24 16:16:11 +00:00
|
|
|
if table is not NotImplemented and table is not False:
|
|
|
|
mega[tag] = table
|
|
|
|
log.info("Merged '%s'.", tag)
|
|
|
|
else:
|
|
|
|
log.info("Dropped '%s'.", tag)
|
2013-09-18 20:47:53 -04:00
|
|
|
|
2014-03-28 17:41:01 -07:00
|
|
|
del self.duplicateGlyphsPerFont
|
2018-02-18 14:38:57 -08:00
|
|
|
del self.fonts
|
2014-03-28 17:41:01 -07:00
|
|
|
|
2013-12-19 11:53:47 -05:00
|
|
|
self._postMerge(mega)
|
|
|
|
|
2013-09-18 20:47:53 -04:00
|
|
|
return mega
|
|
|
|
|
2013-12-19 04:47:34 -05:00
|
|
|
def mergeObjects(self, returnTable, logic, tables):
|
2013-12-19 04:56:50 -05:00
|
|
|
# Right now we don't use self at all. Will use in the future
|
|
|
|
# for options and logging.
|
|
|
|
|
|
|
|
allKeys = set.union(set(), *(vars(table).keys() for table in tables if table is not NotImplemented))
|
2013-12-18 00:45:12 -08:00
|
|
|
for key in allKeys:
|
2013-12-18 12:15:46 -08:00
|
|
|
try:
|
2013-12-19 04:47:34 -05:00
|
|
|
mergeLogic = logic[key]
|
2013-12-18 12:15:46 -08:00
|
|
|
except KeyError:
|
2013-12-19 04:20:26 -05:00
|
|
|
try:
|
2013-12-19 04:47:34 -05:00
|
|
|
mergeLogic = logic['*']
|
2013-12-19 04:20:26 -05:00
|
|
|
except KeyError:
|
2015-04-26 02:01:01 -04:00
|
|
|
raise Exception("Don't know how to merge key %s of class %s" %
|
2013-12-19 04:47:34 -05:00
|
|
|
(key, returnTable.__class__.__name__))
|
2013-12-19 04:56:50 -05:00
|
|
|
if mergeLogic is NotImplemented:
|
2013-12-18 12:15:46 -08:00
|
|
|
continue
|
2013-12-19 04:56:50 -05:00
|
|
|
value = mergeLogic(getattr(table, key, NotImplemented) for table in tables)
|
|
|
|
if value is not NotImplemented:
|
|
|
|
setattr(returnTable, key, value)
|
|
|
|
|
|
|
|
return returnTable
|
2013-12-18 00:45:12 -08:00
|
|
|
|
2013-12-19 11:53:47 -05:00
|
|
|
def _preMerge(self, font):
|
2021-12-16 10:28:03 -07:00
|
|
|
layoutPreMerge(font)
|
2013-12-19 11:53:47 -05:00
|
|
|
|
|
|
|
def _postMerge(self, font):
|
2021-12-16 10:28:03 -07:00
|
|
|
layoutPostMerge(font)
|
2013-12-19 11:53:47 -05:00
|
|
|
|
2013-09-19 16:16:39 -04:00
|
|
|
|
|
|
|
__all__ = [
|
2015-08-09 00:33:50 -07:00
|
|
|
'Options',
|
|
|
|
'Merger',
|
|
|
|
'main'
|
2013-09-19 16:16:39 -04:00
|
|
|
]
|
|
|
|
|
2016-01-24 16:16:11 +00:00
|
|
|
@timer("make one with everything (TOTAL TIME)")
|
2015-05-20 11:02:43 +01:00
|
|
|
def main(args=None):
|
2020-05-12 06:31:13 +01:00
|
|
|
"""Merge multiple fonts into one"""
|
2016-01-24 16:16:11 +00:00
|
|
|
from fontTools import configLogger
|
2015-05-20 11:02:43 +01:00
|
|
|
|
|
|
|
if args is None:
|
|
|
|
args = sys.argv[1:]
|
2013-09-19 16:16:39 -04:00
|
|
|
|
|
|
|
options = Options()
|
2021-11-18 22:09:02 +02:00
|
|
|
args = options.parse_opts(args, ignore_unknown=['output-file'])
|
|
|
|
outfile = 'merged.ttf'
|
|
|
|
fontfiles = []
|
|
|
|
for g in args:
|
|
|
|
if g.startswith('--output-file='):
|
|
|
|
outfile = g[14:]
|
|
|
|
continue
|
|
|
|
fontfiles.append(g)
|
2013-09-19 16:16:39 -04:00
|
|
|
|
2013-09-18 20:47:53 -04:00
|
|
|
if len(args) < 1:
|
2013-12-18 17:14:26 -05:00
|
|
|
print("usage: pyftmerge font...", file=sys.stderr)
|
2017-01-11 12:10:58 +00:00
|
|
|
return 1
|
2013-09-19 16:16:39 -04:00
|
|
|
|
2016-01-24 16:16:11 +00:00
|
|
|
configLogger(level=logging.INFO if options.verbose else logging.WARNING)
|
|
|
|
if options.timing:
|
|
|
|
timer.logger.setLevel(logging.DEBUG)
|
|
|
|
else:
|
|
|
|
timer.logger.disabled = True
|
|
|
|
|
|
|
|
merger = Merger(options=options)
|
2021-11-18 22:09:02 +02:00
|
|
|
font = merger.merge(fontfiles)
|
2016-01-24 16:16:11 +00:00
|
|
|
with timer("compile and save font"):
|
|
|
|
font.save(outfile)
|
2013-09-19 20:12:56 -04:00
|
|
|
|
2013-09-18 20:47:53 -04:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2017-01-11 12:10:58 +00:00
|
|
|
sys.exit(main())
|