cff.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from fontTools.misc import psCharStrings
  2. from fontTools import ttLib
  3. from fontTools.pens.basePen import NullPen
  4. from fontTools.misc.roundTools import otRound
  5. from fontTools.misc.loggingTools import deprecateFunction
  6. from fontTools.subset.util import _add_method, _uniq_sort
  7. class _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler):
  8. def __init__(self, components, localSubrs, globalSubrs):
  9. psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs)
  10. self.components = components
  11. def op_endchar(self, index):
  12. args = self.popall()
  13. if len(args) >= 4:
  14. from fontTools.encodings.StandardEncoding import StandardEncoding
  15. # endchar can do seac accent bulding; The T2 spec says it's deprecated,
  16. # but recent software that shall remain nameless does output it.
  17. adx, ady, bchar, achar = args[-4:]
  18. baseGlyph = StandardEncoding[bchar]
  19. accentGlyph = StandardEncoding[achar]
  20. self.components.add(baseGlyph)
  21. self.components.add(accentGlyph)
  22. @_add_method(ttLib.getTableClass("CFF "))
  23. def closure_glyphs(self, s):
  24. cff = self.cff
  25. assert len(cff) == 1
  26. font = cff[cff.keys()[0]]
  27. glyphSet = font.CharStrings
  28. decompose = s.glyphs
  29. while decompose:
  30. components = set()
  31. for g in decompose:
  32. if g not in glyphSet:
  33. continue
  34. gl = glyphSet[g]
  35. subrs = getattr(gl.private, "Subrs", [])
  36. decompiler = _ClosureGlyphsT2Decompiler(components, subrs, gl.globalSubrs)
  37. decompiler.execute(gl)
  38. components -= s.glyphs
  39. s.glyphs.update(components)
  40. decompose = components
  41. def _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False):
  42. c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName)
  43. if isCFF2 or ignoreWidth:
  44. # CFF2 charstrings have no widths nor 'endchar' operators
  45. c.setProgram([] if isCFF2 else ["endchar"])
  46. else:
  47. if hasattr(font, "FDArray") and font.FDArray is not None:
  48. private = font.FDArray[fdSelectIndex].Private
  49. else:
  50. private = font.Private
  51. dfltWdX = private.defaultWidthX
  52. nmnlWdX = private.nominalWidthX
  53. pen = NullPen()
  54. c.draw(pen) # this will set the charstring's width
  55. if c.width != dfltWdX:
  56. c.program = [c.width - nmnlWdX, "endchar"]
  57. else:
  58. c.program = ["endchar"]
  59. @_add_method(ttLib.getTableClass("CFF "))
  60. def prune_pre_subset(self, font, options):
  61. cff = self.cff
  62. # CFF table must have one font only
  63. cff.fontNames = cff.fontNames[:1]
  64. if options.notdef_glyph and not options.notdef_outline:
  65. isCFF2 = cff.major > 1
  66. for fontname in cff.keys():
  67. font = cff[fontname]
  68. _empty_charstring(font, ".notdef", isCFF2=isCFF2)
  69. # Clear useless Encoding
  70. for fontname in cff.keys():
  71. font = cff[fontname]
  72. # https://github.com/fonttools/fonttools/issues/620
  73. font.Encoding = "StandardEncoding"
  74. return True # bool(cff.fontNames)
  75. @_add_method(ttLib.getTableClass("CFF "))
  76. def subset_glyphs(self, s):
  77. cff = self.cff
  78. for fontname in cff.keys():
  79. font = cff[fontname]
  80. cs = font.CharStrings
  81. glyphs = s.glyphs.union(s.glyphs_emptied)
  82. # Load all glyphs
  83. for g in font.charset:
  84. if g not in glyphs:
  85. continue
  86. c, _ = cs.getItemAndSelector(g)
  87. if cs.charStringsAreIndexed:
  88. indices = [i for i, g in enumerate(font.charset) if g in glyphs]
  89. csi = cs.charStringsIndex
  90. csi.items = [csi.items[i] for i in indices]
  91. del csi.file, csi.offsets
  92. if hasattr(font, "FDSelect"):
  93. sel = font.FDSelect
  94. sel.format = None
  95. sel.gidArray = [sel.gidArray[i] for i in indices]
  96. newCharStrings = {}
  97. for indicesIdx, charsetIdx in enumerate(indices):
  98. g = font.charset[charsetIdx]
  99. if g in cs.charStrings:
  100. newCharStrings[g] = indicesIdx
  101. cs.charStrings = newCharStrings
  102. else:
  103. cs.charStrings = {g: v for g, v in cs.charStrings.items() if g in glyphs}
  104. font.charset = [g for g in font.charset if g in glyphs]
  105. font.numGlyphs = len(font.charset)
  106. if s.options.retain_gids:
  107. isCFF2 = cff.major > 1
  108. for g in s.glyphs_emptied:
  109. _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True)
  110. return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
  111. @_add_method(ttLib.getTableClass("CFF "))
  112. def prune_post_subset(self, ttfFont, options):
  113. cff = self.cff
  114. for fontname in cff.keys():
  115. font = cff[fontname]
  116. cs = font.CharStrings
  117. # Drop unused FontDictionaries
  118. if hasattr(font, "FDSelect"):
  119. sel = font.FDSelect
  120. indices = _uniq_sort(sel.gidArray)
  121. sel.gidArray = [indices.index(ss) for ss in sel.gidArray]
  122. arr = font.FDArray
  123. arr.items = [arr[i] for i in indices]
  124. del arr.file, arr.offsets
  125. # Desubroutinize if asked for
  126. if options.desubroutinize:
  127. cff.desubroutinize()
  128. # Drop hints if not needed
  129. if not options.hinting:
  130. self.remove_hints()
  131. elif not options.desubroutinize:
  132. self.remove_unused_subroutines()
  133. return True
  134. @deprecateFunction(
  135. "use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning
  136. )
  137. @_add_method(ttLib.getTableClass("CFF "))
  138. def desubroutinize(self):
  139. self.cff.desubroutinize()
  140. @deprecateFunction(
  141. "use 'CFFFontSet.remove_hints()' instead", category=DeprecationWarning
  142. )
  143. @_add_method(ttLib.getTableClass("CFF "))
  144. def remove_hints(self):
  145. self.cff.remove_hints()
  146. @deprecateFunction(
  147. "use 'CFFFontSet.remove_unused_subroutines' instead", category=DeprecationWarning
  148. )
  149. @_add_method(ttLib.getTableClass("CFF "))
  150. def remove_unused_subroutines(self):
  151. self.cff.remove_unused_subroutines()