T_S_I__1.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
  2. tool to store its hinting source data.
  3. TSI1 contains the text of the glyph programs in the form of low-level assembly
  4. code, as well as the 'extra' programs 'fpgm', 'ppgm' (i.e. 'prep'), and 'cvt'.
  5. """
  6. from . import DefaultTable
  7. from fontTools.misc.loggingTools import LogMixin
  8. from fontTools.misc.textTools import strjoin, tobytes, tostr
  9. class table_T_S_I__1(LogMixin, DefaultTable.DefaultTable):
  10. extras = {0xFFFA: "ppgm", 0xFFFB: "cvt", 0xFFFC: "reserved", 0xFFFD: "fpgm"}
  11. indextable = "TSI0"
  12. def decompile(self, data, ttFont):
  13. totalLength = len(data)
  14. indextable = ttFont[self.indextable]
  15. for indices, isExtra in zip(
  16. (indextable.indices, indextable.extra_indices), (False, True)
  17. ):
  18. programs = {}
  19. for i, (glyphID, textLength, textOffset) in enumerate(indices):
  20. if isExtra:
  21. name = self.extras[glyphID]
  22. else:
  23. name = ttFont.getGlyphName(glyphID)
  24. if textOffset > totalLength:
  25. self.log.warning("textOffset > totalLength; %r skipped" % name)
  26. continue
  27. if textLength < 0x8000:
  28. # If the length stored in the record is less than 32768, then use
  29. # that as the length of the record.
  30. pass
  31. elif textLength == 0x8000:
  32. # If the length is 32768, compute the actual length as follows:
  33. isLast = i == (len(indices) - 1)
  34. if isLast:
  35. if isExtra:
  36. # For the last "extra" record (the very last record of the
  37. # table), the length is the difference between the total
  38. # length of the TSI1 table and the textOffset of the final
  39. # record.
  40. nextTextOffset = totalLength
  41. else:
  42. # For the last "normal" record (the last record just prior
  43. # to the record containing the "magic number"), the length
  44. # is the difference between the textOffset of the record
  45. # following the "magic number" (0xFFFE) record (i.e. the
  46. # first "extra" record), and the textOffset of the last
  47. # "normal" record.
  48. nextTextOffset = indextable.extra_indices[0][2]
  49. else:
  50. # For all other records with a length of 0x8000, the length is
  51. # the difference between the textOffset of the record in
  52. # question and the textOffset of the next record.
  53. nextTextOffset = indices[i + 1][2]
  54. assert nextTextOffset >= textOffset, "entries not sorted by offset"
  55. if nextTextOffset > totalLength:
  56. self.log.warning(
  57. "nextTextOffset > totalLength; %r truncated" % name
  58. )
  59. nextTextOffset = totalLength
  60. textLength = nextTextOffset - textOffset
  61. else:
  62. from fontTools import ttLib
  63. raise ttLib.TTLibError(
  64. "%r textLength (%d) must not be > 32768" % (name, textLength)
  65. )
  66. text = data[textOffset : textOffset + textLength]
  67. assert len(text) == textLength
  68. text = tostr(text, encoding="utf-8")
  69. if text:
  70. programs[name] = text
  71. if isExtra:
  72. self.extraPrograms = programs
  73. else:
  74. self.glyphPrograms = programs
  75. def compile(self, ttFont):
  76. if not hasattr(self, "glyphPrograms"):
  77. self.glyphPrograms = {}
  78. self.extraPrograms = {}
  79. data = b""
  80. indextable = ttFont[self.indextable]
  81. glyphNames = ttFont.getGlyphOrder()
  82. indices = []
  83. for i in range(len(glyphNames)):
  84. if len(data) % 2:
  85. data = (
  86. data + b"\015"
  87. ) # align on 2-byte boundaries, fill with return chars. Yum.
  88. name = glyphNames[i]
  89. if name in self.glyphPrograms:
  90. text = tobytes(self.glyphPrograms[name], encoding="utf-8")
  91. else:
  92. text = b""
  93. textLength = len(text)
  94. if textLength >= 0x8000:
  95. textLength = 0x8000
  96. indices.append((i, textLength, len(data)))
  97. data = data + text
  98. extra_indices = []
  99. codes = sorted(self.extras.items())
  100. for i in range(len(codes)):
  101. if len(data) % 2:
  102. data = (
  103. data + b"\015"
  104. ) # align on 2-byte boundaries, fill with return chars.
  105. code, name = codes[i]
  106. if name in self.extraPrograms:
  107. text = tobytes(self.extraPrograms[name], encoding="utf-8")
  108. else:
  109. text = b""
  110. textLength = len(text)
  111. if textLength >= 0x8000:
  112. textLength = 0x8000
  113. extra_indices.append((code, textLength, len(data)))
  114. data = data + text
  115. indextable.set(indices, extra_indices)
  116. return data
  117. def toXML(self, writer, ttFont):
  118. names = sorted(self.glyphPrograms.keys())
  119. writer.newline()
  120. for name in names:
  121. text = self.glyphPrograms[name]
  122. if not text:
  123. continue
  124. writer.begintag("glyphProgram", name=name)
  125. writer.newline()
  126. writer.write_noindent(text.replace("\r", "\n"))
  127. writer.newline()
  128. writer.endtag("glyphProgram")
  129. writer.newline()
  130. writer.newline()
  131. extra_names = sorted(self.extraPrograms.keys())
  132. for name in extra_names:
  133. text = self.extraPrograms[name]
  134. if not text:
  135. continue
  136. writer.begintag("extraProgram", name=name)
  137. writer.newline()
  138. writer.write_noindent(text.replace("\r", "\n"))
  139. writer.newline()
  140. writer.endtag("extraProgram")
  141. writer.newline()
  142. writer.newline()
  143. def fromXML(self, name, attrs, content, ttFont):
  144. if not hasattr(self, "glyphPrograms"):
  145. self.glyphPrograms = {}
  146. self.extraPrograms = {}
  147. lines = strjoin(content).replace("\r", "\n").split("\n")
  148. text = "\r".join(lines[1:-1])
  149. if name == "glyphProgram":
  150. self.glyphPrograms[attrs["name"]] = text
  151. elif name == "extraProgram":
  152. self.extraPrograms[attrs["name"]] = text