8000 Merger improvements by behdad · Pull Request #2473 · fonttools/fonttools · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Merger improvements #2473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Dec 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2e4b8ce
[merge] Show tolerance re glyph comparison for empty glyphs' width
behdad Dec 14, 2021
31dcd03
[merge] Ignore shape of Default_Ignorable glyphs
behdad Dec 14, 2021
584c748
[merge] Downgrade duplicates-resolution missing-GSUB from assert to warn
behdad Dec 14, 2021
d2983cc
[merge] Add --drop-tables
behdad Dec 14, 2021
3eff0a4
[merge] Move code into module directory
behdad Dec 16, 2021
bb1e1bd
[merge] Split some code into merge.util
behdad Dec 16, 2021
eaaeb7a
[merge] Move most tables into merge.base module
behdad Dec 16, 2021
40f9e2c
[merge] Move layout code into merge.layout
behdad Dec 16, 2021
99f9e07
[merge] Add merge.unicode
behdad Dec 16, 2021
a551826
[merge] Move helper dicts to merge.util
behdad Dec 16, 2021
fcdde52
[merge] Move options code to merge.options
behdad Dec 16, 2021
4955d83
[merge] Whitespace
behdad Dec 16, 2021
3bb1c5b
[merge] Move cmap code to merge.cmap
behdad Dec 16, 2021
8ee5f26
[merge] Move more code to merge.cmap
behdad Dec 16, 2021
01fcec3
[merge] Move renameCFFCharStrings to merge.util
behdad Dec 16, 2021
8dc3d5b
[merge] Move layout pre/post-Merge code to merge.layout
behdad Dec 16, 2021
c0e61b7
[merge] Move cmap computation closer to glyph-order computation
behdad Dec 16, 2021
23f0030
[merge] Save fontfile and fontname on opened fonts for debug purposes
behdad Dec 16, 2021
0f10166
[merge] Report font name instead of number
behdad Dec 16, 2021
30c5a8b
[merge.layout] Optimize log message interpolation
behdad Dec 16, 2021
915c077
[merge.util] Set CFF charset when renaming
behdad Dec 16, 2021
e6719f4
[merge] Clean up glyphOrder and cmap computation
behdad Dec 16, 2021
deaf30d
[merge] Use merger-private namespace in TTFont.__dict__
behdad Dec 16, 2021
7c54268
[merge] Remove most 'from .. import *'
behdad Dec 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,305 changes: 0 additions & 1,305 deletions Lib/fontTools/merge.py

This file was deleted.

200 changes: 200 additions & 0 deletions Lib/fontTools/merge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# 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 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)


__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())
6 changes: 6 additions & 0 deletions Lib/fontTools/merge/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import sys
from fontTools.merge import main


if __name__ == '__main__':
sys.exit(main())
76 changes: 76 additions & 0 deletions Lib/fontTools/merge/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright 2013 Google, Inc. All Rights Reserved.
#
# Google Author(s): Behdad Esfahbod, Roozbeh Pournader

from fontTools.ttLib.tables.DefaultTable import DefaultTable
import logging


log = logging.getLogger("fontTools.merge")


def add_method(*clazzes, **kwargs):
"""Returns a decorator function that adds a new method to one or
more classes."""
allowDefault = kwargs.get('allowDefaultTable', False)
def wrapper(method):
done = []
for clazz in clazzes:
if clazz in done: continue # Support multiple names of a clazz
done.append(clazz)
assert allowDefault or clazz != DefaultTable, 'Oops, table class not found.'
assert method.__name__ not in clazz.__dict__, \
"Oops, class '%s' has method '%s'." % (clazz.__name__, method.__name__)
setattr(clazz, method.__name__, method)
return None
return wrapper

def mergeObjects(lst):
lst = [item for item in lst if item is not NotImplemented]
if not lst:
return NotImplemented
lst = [item for item in lst if item is not None]
if not lst:
return None

clazz = lst[0].__class__
assert all(type(item) == clazz for item in lst), lst

logic = clazz.mergeMap
returnTable = clazz()
returnDict = {}

allKeys = set.union(set(), *(vars(table).keys() for table in lst))
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, clazz.__name__))
if mergeLogic is NotImplemented:
continue
value = mergeLogic(getattr(table, key, NotImplemented) for table in lst)
if value is not NotImplemented:
returnDict[key] = value

returnTable.__dict__ = returnDict

return returnTable

@add_method(DefaultTable, allowDefaultTable=True)
def merge(self, m, tables):
if not hasattr(self, 'mergeMap'):
log.info("Don't know how to merge '%s'.", self.tableTag)
return NotImplemented

logic = self.mergeMap

if isinstance(logic, dict):
return m.mergeObjects(self, self.mergeMap, tables)
else:
return logic(tables)


Loading
0