aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/merge/tables.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/merge/tables.py')
-rw-r--r--Lib/fontTools/merge/tables.py311
1 files changed, 311 insertions, 0 deletions
diff --git a/Lib/fontTools/merge/tables.py b/Lib/fontTools/merge/tables.py
new file mode 100644
index 00000000..b266f7a9
--- /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