123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- # Copyright 2013 Google, Inc. All Rights Reserved.
- #
- # Google Author(s): Behdad Esfahbod, Roozbeh Pournader
- from fontTools import ttLib
- import fontTools.merge.base
- from fontTools.merge.cmap import (
- computeMegaGlyphOrder,
- computeMegaCmap,
- renameCFFCharStrings,
- )
- from fontTools.merge.layout import layoutPreMerge, layoutPostMerge
- from fontTools.merge.options import Options
- import fontTools.merge.tables
- from fontTools.misc.loggingTools import Timer
- from functools import reduce
- import sys
- import logging
- log = logging.getLogger("fontTools.merge")
- timer = Timer(logger=logging.getLogger(__name__ + ".timer"), level=logging.INFO)
- class Merger(object):
- """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.
- """
- def __init__(self, options=None):
- if not options:
- options = Options()
- self.options = options
- def _openFonts(self, fontfiles):
- fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
- for font, fontfile in zip(fonts, fontfiles):
- font._merger__fontfile = fontfile
- font._merger__name = font["name"].getDebugName(4)
- return fonts
- def merge(self, fontfiles):
- """Merges fonts together.
- 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.
- """
- #
- # Settle on a mega glyph order.
- #
- fonts = self._openFonts(fontfiles)
- glyphOrders = [list(font.getGlyphOrder()) for font in fonts]
- computeMegaGlyphOrder(self, glyphOrders)
- # Take first input file sfntVersion
- sfntVersion = fonts[0].sfntVersion
- # Reload fonts and set new glyph names on them.
- fonts = self._openFonts(fontfiles)
- for font, glyphOrder in zip(fonts, glyphOrders):
- font.setGlyphOrder(glyphOrder)
- if "CFF " in font:
- renameCFFCharStrings(self, glyphOrder, font["CFF "])
- cmaps = [font["cmap"] for font in fonts]
- self.duplicateGlyphsPerFont = [{} for _ in fonts]
- computeMegaCmap(self, cmaps)
- mega = ttLib.TTFont(sfntVersion=sfntVersion)
- mega.setGlyphOrder(self.glyphOrder)
- for font in fonts:
- self._preMerge(font)
- self.fonts = fonts
- allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
- allTags.remove("GlyphOrder")
- for tag in sorted(allTags):
- if tag in self.options.drop_tables:
- continue
- with timer("merge '%s'" % tag):
- tables = [font.get(tag, NotImplemented) for font in fonts]
- log.info("Merging '%s'.", tag)
- clazz = ttLib.getTableClass(tag)
- table = clazz(tag).merge(self, tables)
- # XXX Clean this up and use: table = mergeObjects(tables)
- if table is not NotImplemented and table is not False:
- mega[tag] = table
- log.info("Merged '%s'.", tag)
- else:
- log.info("Dropped '%s'.", tag)
- del self.duplicateGlyphsPerFont
- del self.fonts
- self._postMerge(mega)
- return mega
- def mergeObjects(self, returnTable, logic, tables):
- # 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),
- )
- for key in allKeys:
- try:
- mergeLogic = logic[key]
- except KeyError:
- try:
- mergeLogic = logic["*"]
- except KeyError:
- raise Exception(
- "Don't know how to merge key %s of class %s"
- % (key, returnTable.__class__.__name__)
- )
- if mergeLogic is NotImplemented:
- continue
- value = mergeLogic(getattr(table, key, NotImplemented) for table in tables)
- if value is not NotImplemented:
- setattr(returnTable, key, value)
- return returnTable
- def _preMerge(self, font):
- layoutPreMerge(font)
- def _postMerge(self, font):
- layoutPostMerge(font)
- if "OS/2" in font:
- # https://github.com/fonttools/fonttools/issues/2538
- # TODO: Add an option to disable this?
- font["OS/2"].recalcAvgCharWidth(font)
- __all__ = ["Options", "Merger", "main"]
- @timer("make one with everything (TOTAL TIME)")
- def main(args=None):
- """Merge multiple fonts into one"""
- from fontTools import configLogger
- if args is None:
- args = sys.argv[1:]
- options = Options()
- 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)
- if len(args) < 1:
- print("usage: pyftmerge font...", file=sys.stderr)
- return 1
- 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)
- font = merger.merge(fontfiles)
- with timer("compile and save font"):
- font.save(outfile)
- if __name__ == "__main__":
- sys.exit(main())
|