tables.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. # Copyright 2013 Google, Inc. All Rights Reserved.
  2. #
  3. # Google Author(s): Behdad Esfahbod, Roozbeh Pournader
  4. from fontTools import ttLib, cffLib
  5. from fontTools.misc.psCharStrings import T2WidthExtractor
  6. from fontTools.ttLib.tables.DefaultTable import DefaultTable
  7. from fontTools.merge.base import add_method, mergeObjects
  8. from fontTools.merge.cmap import computeMegaCmap
  9. from fontTools.merge.util import *
  10. import logging
  11. log = logging.getLogger("fontTools.merge")
  12. ttLib.getTableClass("maxp").mergeMap = {
  13. "*": max,
  14. "tableTag": equal,
  15. "tableVersion": equal,
  16. "numGlyphs": sum,
  17. "maxStorage": first,
  18. "maxFunctionDefs": first,
  19. "maxInstructionDefs": first,
  20. # TODO When we correctly merge hinting data, update these values:
  21. # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
  22. }
  23. headFlagsMergeBitMap = {
  24. "size": 16,
  25. "*": bitwise_or,
  26. 1: bitwise_and, # Baseline at y = 0
  27. 2: bitwise_and, # lsb at x = 0
  28. 3: bitwise_and, # Force ppem to integer values. FIXME?
  29. 5: bitwise_and, # Font is vertical
  30. 6: lambda bit: 0, # Always set to zero
  31. 11: bitwise_and, # Font data is 'lossless'
  32. 13: bitwise_and, # Optimized for ClearType
  33. 14: bitwise_and, # Last resort font. FIXME? equal or first may be better
  34. 15: lambda bit: 0, # Always set to zero
  35. }
  36. ttLib.getTableClass("head").mergeMap = {
  37. "tableTag": equal,
  38. "tableVersion": max,
  39. "fontRevision": max,
  40. "checkSumAdjustment": lambda lst: 0, # We need *something* here
  41. "magicNumber": equal,
  42. "flags": mergeBits(headFlagsMergeBitMap),
  43. "unitsPerEm": equal,
  44. "created": current_time,
  45. "modified": current_time,
  46. "xMin": min,
  47. "yMin": min,
  48. "xMax": max,
  49. "yMax": max,
  50. "macStyle": first,
  51. "lowestRecPPEM": max,
  52. "fontDirectionHint": lambda lst: 2,
  53. "indexToLocFormat": first,
  54. "glyphDataFormat": equal,
  55. }
  56. ttLib.getTableClass("hhea").mergeMap = {
  57. "*": equal,
  58. "tableTag": equal,
  59. "tableVersion": max,
  60. "ascent": max,
  61. "descent": min,
  62. "lineGap": max,
  63. "advanceWidthMax": max,
  64. "minLeftSideBearing": min,
  65. "minRightSideBearing": min,
  66. "xMaxExtent": max,
  67. "caretSlopeRise": first,
  68. "caretSlopeRun": first,
  69. "caretOffset": first,
  70. "numberOfHMetrics": recalculate,
  71. }
  72. ttLib.getTableClass("vhea").mergeMap = {
  73. "*": equal,
  74. "tableTag": equal,
  75. "tableVersion": max,
  76. "ascent": max,
  77. "descent": min,
  78. "lineGap": max,
  79. "advanceHeightMax": max,
  80. "minTopSideBearing": min,
  81. "minBottomSideBearing": min,
  82. "yMaxExtent": max,
  83. "caretSlopeRise": first,
  84. "caretSlopeRun": first,
  85. "caretOffset": first,
  86. "numberOfVMetrics": recalculate,
  87. }
  88. os2FsTypeMergeBitMap = {
  89. "size": 16,
  90. "*": lambda bit: 0,
  91. 1: bitwise_or, # no embedding permitted
  92. 2: bitwise_and, # allow previewing and printing documents
  93. 3: bitwise_and, # allow editing documents
  94. 8: bitwise_or, # no subsetting permitted
  95. 9: bitwise_or, # no embedding of outlines permitted
  96. }
  97. def mergeOs2FsType(lst):
  98. lst = list(lst)
  99. if all(item == 0 for item in lst):
  100. return 0
  101. # Compute least restrictive logic for each fsType value
  102. for i in range(len(lst)):
  103. # unset bit 1 (no embedding permitted) if either bit 2 or 3 is set
  104. if lst[i] & 0x000C:
  105. lst[i] &= ~0x0002
  106. # set bit 2 (allow previewing) if bit 3 is set (allow editing)
  107. elif lst[i] & 0x0008:
  108. lst[i] |= 0x0004
  109. # set bits 2 and 3 if everything is allowed
  110. elif lst[i] == 0:
  111. lst[i] = 0x000C
  112. fsType = mergeBits(os2FsTypeMergeBitMap)(lst)
  113. # unset bits 2 and 3 if bit 1 is set (some font is "no embedding")
  114. if fsType & 0x0002:
  115. fsType &= ~0x000C
  116. return fsType
  117. ttLib.getTableClass("OS/2").mergeMap = {
  118. "*": first,
  119. "tableTag": equal,
  120. "version": max,
  121. "xAvgCharWidth": first, # Will be recalculated at the end on the merged font
  122. "fsType": mergeOs2FsType, # Will be overwritten
  123. "panose": first, # FIXME: should really be the first Latin font
  124. "ulUnicodeRange1": bitwise_or,
  125. "ulUnicodeRange2": bitwise_or,
  126. "ulUnicodeRange3": bitwise_or,
  127. "ulUnicodeRange4": bitwise_or,
  128. "fsFirstCharIndex": min,
  129. "fsLastCharIndex": max,
  130. "sTypoAscender": max,
  131. "sTypoDescender": min,
  132. "sTypoLineGap": max,
  133. "usWinAscent": max,
  134. "usWinDescent": max,
  135. # Version 1
  136. "ulCodePageRange1": onlyExisting(bitwise_or),
  137. "ulCodePageRange2": onlyExisting(bitwise_or),
  138. # Version 2, 3, 4
  139. "sxHeight": onlyExisting(max),
  140. "sCapHeight": onlyExisting(max),
  141. "usDefaultChar": onlyExisting(first),
  142. "usBreakChar": onlyExisting(first),
  143. "usMaxContext": onlyExisting(max),
  144. # version 5
  145. "usLowerOpticalPointSize": onlyExisting(min),
  146. "usUpperOpticalPointSize": onlyExisting(max),
  147. }
  148. @add_method(ttLib.getTableClass("OS/2"))
  149. def merge(self, m, tables):
  150. DefaultTable.merge(self, m, tables)
  151. if self.version < 2:
  152. # bits 8 and 9 are reserved and should be set to zero
  153. self.fsType &= ~0x0300
  154. if self.version >= 3:
  155. # Only one of bits 1, 2, and 3 may be set. We already take
  156. # care of bit 1 implications in mergeOs2FsType. So unset
  157. # bit 2 if bit 3 is already set.
  158. if self.fsType & 0x0008:
  159. self.fsType &= ~0x0004
  160. return self
  161. ttLib.getTableClass("post").mergeMap = {
  162. "*": first,
  163. "tableTag": equal,
  164. "formatType": max,
  165. "isFixedPitch": min,
  166. "minMemType42": max,
  167. "maxMemType42": lambda lst: 0,
  168. "minMemType1": max,
  169. "maxMemType1": lambda lst: 0,
  170. "mapping": onlyExisting(sumDicts),
  171. "extraNames": lambda lst: [],
  172. }
  173. ttLib.getTableClass("vmtx").mergeMap = ttLib.getTableClass("hmtx").mergeMap = {
  174. "tableTag": equal,
  175. "metrics": sumDicts,
  176. }
  177. ttLib.getTableClass("name").mergeMap = {
  178. "tableTag": equal,
  179. "names": first, # FIXME? Does mixing name records make sense?
  180. }
  181. ttLib.getTableClass("loca").mergeMap = {
  182. "*": recalculate,
  183. "tableTag": equal,
  184. }
  185. ttLib.getTableClass("glyf").mergeMap = {
  186. "tableTag": equal,
  187. "glyphs": sumDicts,
  188. "glyphOrder": sumLists,
  189. "_reverseGlyphOrder": recalculate,
  190. "axisTags": equal,
  191. }
  192. @add_method(ttLib.getTableClass("glyf"))
  193. def merge(self, m, tables):
  194. for i, table in enumerate(tables):
  195. for g in table.glyphs.values():
  196. if i:
  197. # Drop hints for all but first font, since
  198. # we don't map functions / CVT values.
  199. g.removeHinting()
  200. # Expand composite glyphs to load their
  201. # composite glyph names.
  202. if g.isComposite() or g.isVarComposite():
  203. g.expand(table)
  204. return DefaultTable.merge(self, m, tables)
  205. ttLib.getTableClass("prep").mergeMap = lambda self, lst: first(lst)
  206. ttLib.getTableClass("fpgm").mergeMap = lambda self, lst: first(lst)
  207. ttLib.getTableClass("cvt ").mergeMap = lambda self, lst: first(lst)
  208. ttLib.getTableClass("gasp").mergeMap = lambda self, lst: first(
  209. lst
  210. ) # FIXME? Appears irreconcilable
  211. @add_method(ttLib.getTableClass("CFF "))
  212. def merge(self, m, tables):
  213. if any(hasattr(table.cff[0], "FDSelect") for table in tables):
  214. raise NotImplementedError("Merging CID-keyed CFF tables is not supported yet")
  215. for table in tables:
  216. table.cff.desubroutinize()
  217. newcff = tables[0]
  218. newfont = newcff.cff[0]
  219. private = newfont.Private
  220. newDefaultWidthX, newNominalWidthX = private.defaultWidthX, private.nominalWidthX
  221. storedNamesStrings = []
  222. glyphOrderStrings = []
  223. glyphOrder = set(newfont.getGlyphOrder())
  224. for name in newfont.strings.strings:
  225. if name not in glyphOrder:
  226. storedNamesStrings.append(name)
  227. else:
  228. glyphOrderStrings.append(name)
  229. chrset = list(newfont.charset)
  230. newcs = newfont.CharStrings
  231. log.debug("FONT 0 CharStrings: %d.", len(newcs))
  232. for i, table in enumerate(tables[1:], start=1):
  233. font = table.cff[0]
  234. defaultWidthX, nominalWidthX = (
  235. font.Private.defaultWidthX,
  236. font.Private.nominalWidthX,
  237. )
  238. widthsDiffer = (
  239. defaultWidthX != newDefaultWidthX or nominalWidthX != newNominalWidthX
  240. )
  241. font.Private = private
  242. fontGlyphOrder = set(font.getGlyphOrder())
  243. for name in font.strings.strings:
  244. if name in fontGlyphOrder:
  245. glyphOrderStrings.append(name)
  246. cs = font.CharStrings
  247. gs = table.cff.GlobalSubrs
  248. log.debug("Font %d CharStrings: %d.", i, len(cs))
  249. chrset.extend(font.charset)
  250. if newcs.charStringsAreIndexed:
  251. for i, name in enumerate(cs.charStrings, start=len(newcs)):
  252. newcs.charStrings[name] = i
  253. newcs.charStringsIndex.items.append(None)
  254. for name in cs.charStrings:
  255. if widthsDiffer:
  256. c = cs[name]
  257. defaultWidthXToken = object()
  258. extractor = T2WidthExtractor([], [], nominalWidthX, defaultWidthXToken)
  259. extractor.execute(c)
  260. width = extractor.width
  261. if width is not defaultWidthXToken:
  262. c.program.pop(0)
  263. else:
  264. width = defaultWidthX
  265. if width != newDefaultWidthX:
  266. c.program.insert(0, width - newNominalWidthX)
  267. newcs[name] = cs[name]
  268. newfont.charset = chrset
  269. newfont.numGlyphs = len(chrset)
  270. newfont.strings.strings = glyphOrderStrings + storedNamesStrings
  271. return newcff
  272. @add_method(ttLib.getTableClass("cmap"))
  273. def merge(self, m, tables):
  274. # TODO Handle format=14.
  275. if not hasattr(m, "cmap"):
  276. computeMegaCmap(m, tables)
  277. cmap = m.cmap
  278. cmapBmpOnly = {uni: gid for uni, gid in cmap.items() if uni <= 0xFFFF}
  279. self.tables = []
  280. module = ttLib.getTableModule("cmap")
  281. if len(cmapBmpOnly) != len(cmap):
  282. # format-12 required.
  283. cmapTable = module.cmap_classes[12](12)
  284. cmapTable.platformID = 3
  285. cmapTable.platEncID = 10
  286. cmapTable.language = 0
  287. cmapTable.cmap = cmap
  288. self.tables.append(cmapTable)
  289. # always create format-4
  290. cmapTable = module.cmap_classes[4](4)
  291. cmapTable.platformID = 3
  292. cmapTable.platEncID = 1
  293. cmapTable.language = 0
  294. cmapTable.cmap = cmapBmpOnly
  295. # ordered by platform then encoding
  296. self.tables.insert(0, cmapTable)
  297. self.tableVersion = 0
  298. self.numSubTables = len(self.tables)
  299. return self