diff --git a/Lib/fontTools/merge/__init__.py b/Lib/fontTools/merge/__init__.py index 8c00ed14f..152bf0790 100644 --- a/Lib/fontTools/merge/__init__.py +++ b/Lib/fontTools/merge/__init__.py @@ -3,11 +3,11 @@ # Google Author(s): Behdad Esfahbod, Roozbeh Pournader from fontTools import ttLib -from fontTools.merge.base import * -from fontTools.merge.cmap import * -from fontTools.merge.layout import * -from fontTools.merge.options import * -from fontTools.merge.util import * +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 diff --git a/Lib/fontTools/merge/base.py b/Lib/fontTools/merge/base.py index b36006411..868b51a41 100644 --- a/Lib/fontTools/merge/base.py +++ b/Lib/fontTools/merge/base.py @@ -2,305 +2,75 @@ # # Google Author(s): Behdad Esfahbod, Roozbeh Pournader -from fontTools import ttLib, cffLib from fontTools.ttLib.tables.DefaultTable import DefaultTable -from fontTools.merge.cmap import * -from fontTools.merge.util import * +import logging -ttLib.getTableClass('maxp').mergeMap = { - '*': max, - 'tableTag': equal, - 'tableVersion': equal, - 'numGlyphs': sum, - 'maxStorage': first, - 'maxFunctionDefs': first, - 'maxInstructionDefs': first, - # TODO When we correctly merge hinting data, update these values: - # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions -} - -headFlagsMergeBitMap = { - 'size': 16, - '*': bitwise_or, - 1: bitwise_and, # Baseline at y = 0 - 2: bitwise_and, # lsb at x = 0 - 3: bitwise_and, # Force ppem to integer values. FIXME? - 5: bitwise_and, # Font is vertical - 6: lambda bit: 0, # Always set to zero - 11: bitwise_and, # Font data is 'lossless' - 13: bitwise_and, # Optimized for ClearType - 14: bitwise_and, # Last resort font. FIXME? equal or first may be better - 15: lambda bit: 0, # Always set to zero -} - -ttLib.getTableClass('head').mergeMap = { - 'tableTag': equal, - 'tableVersion': max, - 'fontRevision': max, - 'checkSumAdjustment': lambda lst: 0, # We need *something* here - 'magicNumber': equal, - 'flags': mergeBits(headFlagsMergeBitMap), - 'unitsPerEm': equal, - 'created': current_time, - 'modified': current_time, - 'xMin': min, - 'yMin': min, - 'xMax': max, - 'yMax': max, - 'macStyle': first, - 'lowestRecPPEM': max, - 'fontDirectionHint': lambda lst: 2, - 'indexToLocFormat': first, - 'glyphDataFormat': equal, -} - -ttLib.getTableClass('hhea').mergeMap = { - '*': equal, - 'tableTag': equal, - 'tableVersion': max, - 'ascent': max, - 'descent': min, - 'lineGap': max, - 'advanceWidthMax': max, - 'minLeftSideBearing': min, - 'minRightSideBearing': min, - 'xMaxExtent': max, - 'caretSlopeRise': first, - 'caretSlopeRun': first, - 'caretOffset': first, - 'numberOfHMetrics': recalculate, -} - -ttLib.getTableClass('vhea').mergeMap = { - '*': equal, - 'tableTag': equal, - 'tableVersion': max, - 'ascent': max, - 'descent': min, - 'lineGap': max, - 'advanceHeightMax': max, - 'minTopSideBearing': min, - 'minBottomSideBearing': min, - 'yMaxExtent': max, - 'caretSlopeRise': first, - 'caretSlopeRun': first, - 'caretOffset': first, - 'numberOfVMetrics': recalculate, -} - -os2FsTypeMergeBitMap = { - 'size': 16, - '*': lambda bit: 0, - 1: bitwise_or, # no embedding permitted - 2: bitwise_and, # allow previewing and printing documents - 3: bitwise_and, # allow editing documents - 8: bitwise_or, # no subsetting permitted - 9: bitwise_or, # no embedding of outlines permitted -} - -def mergeOs2FsType(lst): - lst = list(lst) - if all(item == 0 for item in lst): - return 0 - - # Compute least restrictive logic for each fsType value - for i in range(len(lst)): - # unset bit 1 (no embedding permitted) if either bit 2 or 3 is set - if lst[i] & 0x000C: - lst[i] &= ~0x0002 - # set bit 2 (allow previewing) if bit 3 is set (allow editing) - elif lst[i] & 0x0008: - lst[i] |= 0x0004 - # set bits 2 and 3 if everything is allowed - elif lst[i] == 0: - lst[i] = 0x000C - - fsType = mergeBits(os2FsTypeMergeBitMap)(lst) - # unset bits 2 and 3 if bit 1 is set (some font is "no embedding") - if fsType & 0x0002: - fsType &= ~0x000C - return fsType +log = logging.getLogger("fontTools.merge") -ttLib.getTableClass('OS/2').mergeMap = { - '*': first, - 'tableTag': equal, - 'version': max, - 'xAvgCharWidth': avg_int, # Apparently fontTools doesn't recalc this - 'fsType': mergeOs2FsType, # Will be overwritten - 'panose': first, # FIXME: should really be the first Latin font - 'ulUnicodeRange1': bitwise_or, - 'ulUnicodeRange2': bitwise_or, - 'ulUnicodeRange3': bitwise_or, - 'ulUnicodeRange4': bitwise_or, - 'fsFirstCharIndex': min, - 'fsLastCharIndex': max, - 'sTypoAscender': max, - 'sTypoDescender': min, - 'sTypoLineGap': max, - 'usWinAscent': max, - 'usWinDescent': max, - # Version 1 - 'ulCodePageRange1': onlyExisting(bitwise_or), - 'ulCodePageRange2': onlyExisting(bitwise_or), - # Version 2, 3, 4 - 'sxHeight': onlyExisting(max), - 'sCapHeight': onlyExisting(max), - 'usDefaultChar': onlyExisting(first), - 'usBreakChar': onlyExisting(first), - 'usMaxContext': onlyExisting(max), - # version 5 - 'usLowerOpticalPointSize': onlyExisting(min), - 'usUpperOpticalPointSize': onlyExisting(max), -} +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 -@add_method(ttLib.getTableClass('OS/2')) +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): - DefaultTable.merge(self, m, tables) - if self.version < 2: - # bits 8 and 9 are reserved and should be set to zero - self.fsType &= ~0x0300 - if self.version >= 3: - # Only one of bits 1, 2, and 3 may be set. We already take - # care of bit 1 implications in mergeOs2FsType. So unset - # bit 2 if bit 3 is already set. - if self.fsType & 0x0008: - self.fsType &= ~0x0004 - return self + if not hasattr(self, 'mergeMap'): + log.info("Don't know how to merge '%s'.", self.tableTag) + return NotImplemented -ttLib.getTableClass('post').mergeMap = { - '*': first, - 'tableTag': equal, - 'formatType': max, - 'isFixedPitch': min, - 'minMemType42': max, - 'maxMemType42': lambda lst: 0, - 'minMemType1': max, - 'maxMemType1': lambda lst: 0, - 'mapping': onlyExisting(sumDicts), - 'extraNames': lambda lst: [], -} + logic = self.mergeMap -ttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = { - 'tableTag': equal, - 'metrics': sumDicts, -} + if isinstance(logic, dict): + return m.mergeObjects(self, self.mergeMap, tables) + else: + return logic(tables) -ttLib.getTableClass('name').mergeMap = { - 'tableTag': equal, - 'names': first, # FIXME? Does mixing name records make sense? -} -ttLib.getTableClass('loca').mergeMap = { - '*': recalculate, - 'tableTag': equal, -} - -ttLib.getTableClass('glyf').mergeMap = { - 'tableTag': equal, - 'glyphs': sumDicts, - 'glyphOrder': sumLists, -} - -@add_method(ttLib.getTableClass('glyf')) -def merge(self, m, tables): - for i,table in enumerate(tables): - for g in table.glyphs.values(): - if i: - # Drop hints for all but first font, since - # we don't map functions / CVT values. - g.removeHinting() - # Expand composite glyphs to load their - # composite glyph names. - if g.isComposite(): - g.expand(table) - return DefaultTable.merge(self, m, tables) - -ttLib.getTableClass('prep').mergeMap = lambda self, lst: first(lst) -ttLib.getTableClass('fpgm').mergeMap = lambda self, lst: first(lst) -ttLib.getTableClass('cvt ').mergeMap = lambda self, lst: first(lst) -ttLib.getTableClass('gasp').mergeMap = lambda self, lst: first(lst) # FIXME? Appears irreconcilable - -@add_method(ttLib.getTableClass('CFF ')) -def merge(self, m, tables): - - if any(hasattr(table, "FDSelect") for table in tables): - raise NotImplementedError( - "Merging CID-keyed CFF tables is not supported yet" - ) - - for table in tables: - table.cff.desubroutinize() - - newcff = tables[0] - newfont = newcff.cff[0] - private = newfont.Private - storedNamesStrings = [] - glyphOrderStrings = [] - glyphOrder = set(newfont.getGlyphOrder()) - - for name in newfont.strings.strings: - if name not in glyphOrder: - storedNamesStrings.append(name) - else: - glyphOrderStrings.append(name) - - chrset = list(newfont.charset) - newcs = newfont.CharStrings - log.debug("FONT 0 CharStrings: %d.", len(newcs)) - - for i, table in enumerate(tables[1:], start=1): - font = table.cff[0] - font.Private = private - fontGlyphOrder = set(font.getGlyphOrder()) - for name in font.strings.strings: - if name in fontGlyphOrder: - glyphOrderStrings.append(name) - cs = font.CharStrings - gs = table.cff.GlobalSubrs - log.debug("Font %d CharStrings: %d.", i, len(cs)) - chrset.extend(font.charset) - if newcs.charStringsAreIndexed: - for i, name in enumerate(cs.charStrings, start=len(newcs)): - newcs.charStrings[name] = i - newcs.charStringsIndex.items.append(None) - for name in cs.charStrings: - newcs[name] = cs[name] - - newfont.charset = chrset - newfont.numGlyphs = len(chrset) - newfont.strings.strings = glyphOrderStrings + storedNamesStrings - - return newcff - -@add_method(ttLib.getTableClass('cmap')) -def merge(self, m, tables): - - # TODO Handle format=14. - if not hasattr(m, 'cmap'): - computeMegaCmap(m, tables) - cmap = m.cmap - - cmapBmpOnly = {uni: gid for uni,gid in cmap.items() if uni <= 0xFFFF} - self.tables = [] - module = ttLib.getTableModule('cmap') - if len(cmapBmpOnly) != len(cmap): - # format-12 required. - cmapTable = module.cmap_classes[12](12) - cmapTable.platformID = 3 - cmapTable.platEncID = 10 - cmapTable.language = 0 - cmapTable.cmap = cmap - self.tables.append(cmapTable) - # always create format-4 - cmapTable = module.cmap_classes[4](4) - cmapTable.platformID = 3 - cmapTable.platEncID = 1 - cmapTable.language = 0 - cmapTable.cmap = cmapBmpOnly - # ordered by platform then encoding - self.tables.insert(0, cmapTable) - self.tableVersion = 0 - self.numSubTables = len(self.tables) - return self diff --git a/Lib/fontTools/merge/cmap.py b/Lib/fontTools/merge/cmap.py index e71079dca..7ade4ac9d 100644 --- a/Lib/fontTools/merge/cmap.py +++ b/Lib/fontTools/merge/cmap.py @@ -2,7 +2,7 @@ # # Google Author(s): Behdad Esfahbod, Roozbeh Pournader -from fontTools.merge.unicode import * +from fontTools.merge.unicode import is_Default_Ignorable from fontTools.pens.recordingPen import DecomposingRecordingPen import logging @@ -52,7 +52,7 @@ def _glyphsAreSame(glyphSet1, glyphSet2, glyph1, glyph2, # Unicode BMP-only and Unicode Full Repertoire semantics. # Cf. OpenType spec for "Platform specific encodings": # https://docs.microsoft.com/en-us/typography/opentype/spec/name -class CmapUnicodePlatEncodings: +class _CmapUnicodePlatEncodings: BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)} FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)} @@ -68,9 +68,9 @@ def computeMegaCmap(merger, cmapTables): format12 = None for subtable in table.tables: properties = (subtable.format, subtable.platformID, subtable.platEncID) - if properties in CmapUnicodePlatEncodings.BMP: + if properties in _CmapUnicodePlatEncodings.BMP: format4 = subtable - elif properties in CmapUnicodePlatEncodings.FullRepertoire: + elif properties in _CmapUnicodePlatEncodings.FullRepertoire: format12 = subtable else: log.warning( @@ -114,3 +114,16 @@ def computeMegaCmap(merger, cmapTables): # gid, because of another Unicode character. # TODO: Try harder to do something about these. log.warning("Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid) + + +def renameCFFCharStrings(merger, glyphOrder, cffTable): + """Rename topDictIndex charStrings based on glyphOrder.""" + td = cffTable.cff.topDictIndex[0] + + charStrings = {} + for i, v in enumerate(td.CharStrings.charStrings.values()): + glyphName = glyphOrder[i] + charStrings[glyphName] = v + td.CharStrings.charStrings = charStrings + + td.charset = list(glyphOrder) diff --git a/Lib/fontTools/merge/layout.py b/Lib/fontTools/merge/layout.py index bfc2b4341..4bf01c372 100644 --- a/Lib/fontTools/merge/layout.py +++ b/Lib/fontTools/merge/layout.py @@ -5,9 +5,13 @@ from fontTools import ttLib from fontTools.ttLib.tables.DefaultTable import DefaultTable from fontTools.ttLib.tables import otTables +from fontTools.merge.base import add_method, mergeObjects from fontTools.merge.util import * +import logging +log = logging.getLogger("fontTools.merge") + def mergeLookupLists(lst): # TODO Do smarter merge. diff --git a/Lib/fontTools/merge/tables.py b/Lib/fontTools/merge/tables.py new file mode 100644 index 000000000..b266f7a97 --- /dev/null +++ b/Lib/fontTools/merge/tables.py @@ -0,0 +1,311 @@ +# Copyright 2013 Google, Inc. All Rights Reserved. +# +# Google Author(s): Behdad Esfahbod, Roozbeh Pournader + +from fontTools import ttLib, cffLib +from fontTools.ttLib.tables.DefaultTable import DefaultTable +from fontTools.merge.base import add_method, mergeObjects +from fontTools.merge.cmap import computeMegaCmap +from fontTools.merge.util import * +import logging + + +log = logging.getLogger("fontTools.merge") + + +ttLib.getTableClass('maxp').mergeMap = { + '*': max, + 'tableTag': equal, + 'tableVersion': equal, + 'numGlyphs': sum, + 'maxStorage': first, + 'maxFunctionDefs': first, + 'maxInstructionDefs': first, + # TODO When we correctly merge hinting data, update these values: + # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions +} + +headFlagsMergeBitMap = { + 'size': 16, + '*': bitwise_or, + 1: bitwise_and, # Baseline at y = 0 + 2: bitwise_and, # lsb at x = 0 + 3: bitwise_and, # Force ppem to integer values. FIXME? + 5: bitwise_and, # Font is vertical + 6: lambda bit: 0, # Always set to zero + 11: bitwise_and, # Font data is 'lossless' + 13: bitwise_and, # Optimized for ClearType + 14: bitwise_and, # Last resort font. FIXME? equal or first may be better + 15: lambda bit: 0, # Always set to zero +} + +ttLib.getTableClass('head').mergeMap = { + 'tableTag': equal, + 'tableVersion': max, + 'fontRevision': max, + 'checkSumAdjustment': lambda lst: 0, # We need *something* here + 'magicNumber': equal, + 'flags': mergeBits(headFlagsMergeBitMap), + 'unitsPerEm': equal, + 'created': current_time, + 'modified': current_time, + 'xMin': min, + 'yMin': min, + 'xMax': max, + 'yMax': max, + 'macStyle': first, + 'lowestRecPPEM': max, + 'fontDirectionHint': lambda lst: 2, + 'indexToLocFormat': first, + 'glyphDataFormat': equal, +} + +ttLib.getTableClass('hhea').mergeMap = { + '*': equal, + 'tableTag': equal, + 'tableVersion': max, + 'ascent': max, + 'descent': min, + 'lineGap': max, + 'advanceWidthMax': max, + 'minLeftSideBearing': min, + 'minRightSideBearing': min, + 'xMaxExtent': max, + 'caretSlopeRise': first, + 'caretSlopeRun': first, + 'caretOffset': first, + 'numberOfHMetrics': recalculate, +} + +ttLib.getTableClass('vhea').mergeMap = { + '*': equal, + 'tableTag': equal, + 'tableVersion': max, + 'ascent': max, + 'descent': min, + 'lineGap': max, + 'advanceHeightMax': max, + 'minTopSideBearing': min, + 'minBottomSideBearing': min, + 'yMaxExtent': max, + 'caretSlopeRise': first, + 'caretSlopeRun': first, + 'caretOffset': first, + 'numberOfVMetrics': recalculate, +} + +os2FsTypeMergeBitMap = { + 'size': 16, + '*': lambda bit: 0, + 1: bitwise_or, # no embedding permitted + 2: bitwise_and, # allow previewing and printing documents + 3: bitwise_and, # allow editing documents + 8: bitwise_or, # no subsetting permitted + 9: bitwise_or, # no embedding of outlines permitted +} + +def mergeOs2FsType(lst): + lst = list(lst) + if all(item == 0 for item in lst): + return 0 + + # Compute least restrictive logic for each fsType value + for i in range(len(lst)): + # unset bit 1 (no embedding permitted) if either bit 2 or 3 is set + if lst[i] & 0x000C: + lst[i] &= ~0x0002 + # set bit 2 (allow previewing) if bit 3 is set (allow editing) + elif lst[i] & 0x0008: + lst[i] |= 0x0004 + # set bits 2 and 3 if everything is allowed + elif lst[i] == 0: + lst[i] = 0x000C + + fsType = mergeBits(os2FsTypeMergeBitMap)(lst) + # unset bits 2 and 3 if bit 1 is set (some font is "no embedding") + if fsType & 0x0002: + fsType &= ~0x000C + return fsType + + +ttLib.getTableClass('OS/2').mergeMap = { + '*': first, + 'tableTag': equal, + 'version': max, + 'xAvgCharWidth': avg_int, # Apparently fontTools doesn't recalc this + 'fsType': mergeOs2FsType, # Will be overwritten + 'panose': first, # FIXME: should really be the first Latin font + 'ulUnicodeRange1': bitwise_or, + 'ulUnicodeRange2': bitwise_or, + 'ulUnicodeRange3': bitwise_or, + 'ulUnicodeRange4': bitwise_or, + 'fsFirstCharIndex': min, + 'fsLastCharIndex': max, + 'sTypoAscender': max, + 'sTypoDescender': min, + 'sTypoLineGap': max, + 'usWinAscent': max, + 'usWinDescent': max, + # Version 1 + 'ulCodePageRange1': onlyExisting(bitwise_or), + 'ulCodePageRange2': onlyExisting(bitwise_or), + # Version 2, 3, 4 + 'sxHeight': onlyExisting(max), + 'sCapHeight': onlyExisting(max), + 'usDefaultChar': onlyExisting(first), + 'usBreakChar': onlyExisting(first), + 'usMaxContext': onlyExisting(max), + # version 5 + 'usLowerOpticalPointSize': onlyExisting(min), + 'usUpperOpticalPointSize': onlyExisting(max), +} + +@add_method(ttLib.getTableClass('OS/2')) +def merge(self, m, tables): + DefaultTable.merge(self, m, tables) + if self.version < 2: + # bits 8 and 9 are reserved and should be set to zero + self.fsType &= ~0x0300 + if self.version >= 3: + # Only one of bits 1, 2, and 3 may be set. We already take + # care of bit 1 implications in mergeOs2FsType. So unset + # bit 2 if bit 3 is already set. + if self.fsType & 0x0008: + self.fsType &= ~0x0004 + return self + +ttLib.getTableClass('post').mergeMap = { + '*': first, + 'tableTag': equal, + 'formatType': max, + 'isFixedPitch': min, + 'minMemType42': max, + 'maxMemType42': lambda lst: 0, + 'minMemType1': max, + 'maxMemType1': lambda lst: 0, + 'mapping': onlyExisting(sumDicts), + 'extraNames': lambda lst: [], +} + +ttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = { + 'tableTag': equal, + 'metrics': sumDicts, +} + +ttLib.getTableClass('name').mergeMap = { + 'tableTag': equal, + 'names': first, # FIXME? Does mixing name records make sense? +} + +ttLib.getTableClass('loca').mergeMap = { + '*': recalculate, + 'tableTag': equal, +} + +ttLib.getTableClass('glyf').mergeMap = { + 'tableTag': equal, + 'glyphs': sumDicts, + 'glyphOrder': sumLists, +} + +@add_method(ttLib.getTableClass('glyf')) +def merge(self, m, tables): + for i,table in enumerate(tables): + for g in table.glyphs.values(): + if i: + # Drop hints for all but first font, since + # we don't map functions / CVT values. + g.removeHinting() + # Expand composite glyphs to load their + # composite glyph names. + if g.isComposite(): + g.expand(table) + return DefaultTable.merge(self, m, tables) + +ttLib.getTableClass('prep').mergeMap = lambda self, lst: first(lst) +ttLib.getTableClass('fpgm').mergeMap = lambda self, lst: first(lst) +ttLib.getTableClass('cvt ').mergeMap = lambda self, lst: first(lst) +ttLib.getTableClass('gasp').mergeMap = lambda self, lst: first(lst) # FIXME? Appears irreconcilable + +@add_method(ttLib.getTableClass('CFF ')) +def merge(self, m, tables): + + if any(hasattr(table, "FDSelect") for table in tables): + raise NotImplementedError( + "Merging CID-keyed CFF tables is not supported yet" + ) + + for table in tables: + table.cff.desubroutinize() + + newcff = tables[0] + newfont = newcff.cff[0] + private = newfont.Private + storedNamesStrings = [] + glyphOrderStrings = [] + glyphOrder = set(newfont.getGlyphOrder()) + + for name in newfont.strings.strings: + if name not in glyphOrder: + storedNamesStrings.append(name) + else: + glyphOrderStrings.append(name) + + chrset = list(newfont.charset) + newcs = newfont.CharStrings + log.debug("FONT 0 CharStrings: %d.", len(newcs)) + + for i, table in enumerate(tables[1:], start=1): + font = table.cff[0] + font.Private = private + fontGlyphOrder = set(font.getGlyphOrder()) + for name in font.strings.strings: + if name in fontGlyphOrder: + glyphOrderStrings.append(name) + cs = font.CharStrings + gs = table.cff.GlobalSubrs + log.debug("Font %d CharStrings: %d.", i, len(cs)) + chrset.extend(font.charset) + if newcs.charStringsAreIndexed: + for i, name in enumerate(cs.charStrings, start=len(newcs)): + newcs.charStrings[name] = i + newcs.charStringsIndex.items.append(None) + for name in cs.charStrings: + newcs[name] = cs[name] + + newfont.charset = chrset + newfont.numGlyphs = len(chrset) + newfont.strings.strings = glyphOrderStrings + storedNamesStrings + + return newcff + +@add_method(ttLib.getTableClass('cmap')) +def merge(self, m, tables): + + # TODO Handle format=14. + if not hasattr(m, 'cmap'): + computeMegaCmap(m, tables) + cmap = m.cmap + + cmapBmpOnly = {uni: gid for uni,gid in cmap.items() if uni <= 0xFFFF} + self.tables = [] + module = ttLib.getTableModule('cmap') + if len(cmapBmpOnly) != len(cmap): + # format-12 required. + cmapTable = module.cmap_classes[12](12) + cmapTable.platformID = 3 + cmapTable.platEncID = 10 + cmapTable.language = 0 + cmapTable.cmap = cmap + self.tables.append(cmapTable) + # always create format-4 + cmapTable = module.cmap_classes[4](4) + cmapTable.platformID = 3 + cmapTable.platEncID = 1 + cmapTable.language = 0 + cmapTable.cmap = cmapBmpOnly + # ordered by platform then encoding + self.tables.insert(0, cmapTable) + self.tableVersion = 0 + self.numSubTables = len(self.tables) + return self diff --git a/Lib/fontTools/merge/util.py b/Lib/fontTools/merge/util.py index 73bc1bb7f..66cea4d52 100644 --- a/Lib/fontTools/merge/util.py +++ b/Lib/fontTools/merge/util.py @@ -11,21 +11,6 @@ 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 # General utility functions for merging values from different fonts @@ -79,41 +64,6 @@ def sumDicts(lst): d.update(item) return d -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 - def mergeBits(bitmap): def wrapper(lst): @@ -135,20 +85,6 @@ def mergeBits(bitmap): return wrapper -@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) - - class AttendanceRecordingIdentityDict(object): """A dictionary-like object that records indices of items actually accessed from a list.""" @@ -193,16 +129,3 @@ class NonhashableDict(object): def __delitem__(self, k): del self.d[id(k)] - - -def renameCFFCharStrings(merger, glyphOrder, cffTable): - """Rename topDictIndex charStrings based on glyphOrder.""" - td = cffTable.cff.topDictIndex[0] - - charStrings = {} - for i, v in enumerate(td.CharStrings.charStrings.values()): - glyphName = glyphOrder[i] - charStrings[glyphName] = v - td.CharStrings.charStrings = charStrings - - td.charset = list(glyphOrder)