123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- # Copyright 2013 Google, Inc. All Rights Reserved.
- #
- # Google Author(s): Behdad Esfahbod, Roozbeh Pournader
- from fontTools import ttLib, cffLib
- from fontTools.misc.psCharStrings import T2WidthExtractor
- 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": first, # Will be recalculated at the end on the merged font
- "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,
- "_reverseGlyphOrder": recalculate,
- "axisTags": equal,
- }
- @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() or g.isVarComposite():
- 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.cff[0], "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
- newDefaultWidthX, newNominalWidthX = private.defaultWidthX, private.nominalWidthX
- 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]
- defaultWidthX, nominalWidthX = (
- font.Private.defaultWidthX,
- font.Private.nominalWidthX,
- )
- widthsDiffer = (
- defaultWidthX != newDefaultWidthX or nominalWidthX != newNominalWidthX
- )
- 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:
- if widthsDiffer:
- c = cs[name]
- defaultWidthXToken = object()
- extractor = T2WidthExtractor([], [], nominalWidthX, defaultWidthXToken)
- extractor.execute(c)
- width = extractor.width
- if width is not defaultWidthXToken:
- c.program.pop(0)
- else:
- width = defaultWidthX
- if width != newDefaultWidthX:
- c.program.insert(0, width - newNominalWidthX)
- 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
|