ttCollection.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. from fontTools.ttLib.ttFont import TTFont
  2. from fontTools.ttLib.sfnt import readTTCHeader, writeTTCHeader
  3. from io import BytesIO
  4. import struct
  5. import logging
  6. log = logging.getLogger(__name__)
  7. class TTCollection(object):
  8. """Object representing a TrueType Collection / OpenType Collection.
  9. The main API is self.fonts being a list of TTFont instances.
  10. If shareTables is True, then different fonts in the collection
  11. might point to the same table object if the data for the table was
  12. the same in the font file. Note, however, that this might result
  13. in suprises and incorrect behavior if the different fonts involved
  14. have different GlyphOrder. Use only if you know what you are doing.
  15. """
  16. def __init__(self, file=None, shareTables=False, **kwargs):
  17. fonts = self.fonts = []
  18. if file is None:
  19. return
  20. assert "fontNumber" not in kwargs, kwargs
  21. closeStream = False
  22. if not hasattr(file, "read"):
  23. file = open(file, "rb")
  24. closeStream = True
  25. tableCache = {} if shareTables else None
  26. header = readTTCHeader(file)
  27. for i in range(header.numFonts):
  28. font = TTFont(file, fontNumber=i, _tableCache=tableCache, **kwargs)
  29. fonts.append(font)
  30. # don't close file if lazy=True, as the TTFont hold a reference to the original
  31. # file; the file will be closed once the TTFonts are closed in the
  32. # TTCollection.close(). We still want to close the file if lazy is None or
  33. # False, because in that case the TTFont no longer need the original file
  34. # and we want to avoid 'ResourceWarning: unclosed file'.
  35. if not kwargs.get("lazy") and closeStream:
  36. file.close()
  37. def __enter__(self):
  38. return self
  39. def __exit__(self, type, value, traceback):
  40. self.close()
  41. def close(self):
  42. for font in self.fonts:
  43. font.close()
  44. def save(self, file, shareTables=True):
  45. """Save the font to disk. Similarly to the constructor,
  46. the 'file' argument can be either a pathname or a writable
  47. file object.
  48. """
  49. if not hasattr(file, "write"):
  50. final = None
  51. file = open(file, "wb")
  52. else:
  53. # assume "file" is a writable file object
  54. # write to a temporary stream to allow saving to unseekable streams
  55. final = file
  56. file = BytesIO()
  57. tableCache = {} if shareTables else None
  58. offsets_offset = writeTTCHeader(file, len(self.fonts))
  59. offsets = []
  60. for font in self.fonts:
  61. offsets.append(file.tell())
  62. font._save(file, tableCache=tableCache)
  63. file.seek(0, 2)
  64. file.seek(offsets_offset)
  65. file.write(struct.pack(">%dL" % len(self.fonts), *offsets))
  66. if final:
  67. final.write(file.getvalue())
  68. file.close()
  69. def saveXML(self, fileOrPath, newlinestr="\n", writeVersion=True, **kwargs):
  70. from fontTools.misc import xmlWriter
  71. writer = xmlWriter.XMLWriter(fileOrPath, newlinestr=newlinestr)
  72. if writeVersion:
  73. from fontTools import version
  74. version = ".".join(version.split(".")[:2])
  75. writer.begintag("ttCollection", ttLibVersion=version)
  76. else:
  77. writer.begintag("ttCollection")
  78. writer.newline()
  79. writer.newline()
  80. for font in self.fonts:
  81. font._saveXML(writer, writeVersion=False, **kwargs)
  82. writer.newline()
  83. writer.endtag("ttCollection")
  84. writer.newline()
  85. writer.close()
  86. def __getitem__(self, item):
  87. return self.fonts[item]
  88. def __setitem__(self, item, value):
  89. self.fonts[item] = value
  90. def __delitem__(self, item):
  91. return self.fonts[item]
  92. def __len__(self):
  93. return len(self.fonts)
  94. def __iter__(self):
  95. return iter(self.fonts)