M_E_T_A_.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. from fontTools.misc import sstruct
  2. from fontTools.misc.textTools import byteord, safeEval
  3. from . import DefaultTable
  4. import pdb
  5. import struct
  6. METAHeaderFormat = """
  7. > # big endian
  8. tableVersionMajor: H
  9. tableVersionMinor: H
  10. metaEntriesVersionMajor: H
  11. metaEntriesVersionMinor: H
  12. unicodeVersion: L
  13. metaFlags: H
  14. nMetaRecs: H
  15. """
  16. # This record is followed by nMetaRecs of METAGlyphRecordFormat.
  17. # This in turn is followd by as many METAStringRecordFormat entries
  18. # as specified by the METAGlyphRecordFormat entries
  19. # this is followed by the strings specifried in the METAStringRecordFormat
  20. METAGlyphRecordFormat = """
  21. > # big endian
  22. glyphID: H
  23. nMetaEntry: H
  24. """
  25. # This record is followd by a variable data length field:
  26. # USHORT or ULONG hdrOffset
  27. # Offset from start of META table to the beginning
  28. # of this glyphs array of ns Metadata string entries.
  29. # Size determined by metaFlags field
  30. # METAGlyphRecordFormat entries must be sorted by glyph ID
  31. METAStringRecordFormat = """
  32. > # big endian
  33. labelID: H
  34. stringLen: H
  35. """
  36. # This record is followd by a variable data length field:
  37. # USHORT or ULONG stringOffset
  38. # METAStringRecordFormat entries must be sorted in order of labelID
  39. # There may be more than one entry with the same labelID
  40. # There may be more than one strign with the same content.
  41. # Strings shall be Unicode UTF-8 encoded, and null-terminated.
  42. METALabelDict = {
  43. 0: "MojikumiX4051", # An integer in the range 1-20
  44. 1: "UNIUnifiedBaseChars",
  45. 2: "BaseFontName",
  46. 3: "Language",
  47. 4: "CreationDate",
  48. 5: "FoundryName",
  49. 6: "FoundryCopyright",
  50. 7: "OwnerURI",
  51. 8: "WritingScript",
  52. 10: "StrokeCount",
  53. 11: "IndexingRadical",
  54. }
  55. def getLabelString(labelID):
  56. try:
  57. label = METALabelDict[labelID]
  58. except KeyError:
  59. label = "Unknown label"
  60. return str(label)
  61. class table_M_E_T_A_(DefaultTable.DefaultTable):
  62. dependencies = []
  63. def decompile(self, data, ttFont):
  64. dummy, newData = sstruct.unpack2(METAHeaderFormat, data, self)
  65. self.glyphRecords = []
  66. for i in range(self.nMetaRecs):
  67. glyphRecord, newData = sstruct.unpack2(
  68. METAGlyphRecordFormat, newData, GlyphRecord()
  69. )
  70. if self.metaFlags == 0:
  71. [glyphRecord.offset] = struct.unpack(">H", newData[:2])
  72. newData = newData[2:]
  73. elif self.metaFlags == 1:
  74. [glyphRecord.offset] = struct.unpack(">H", newData[:4])
  75. newData = newData[4:]
  76. else:
  77. assert 0, (
  78. "The metaFlags field in the META table header has a value other than 0 or 1 :"
  79. + str(self.metaFlags)
  80. )
  81. glyphRecord.stringRecs = []
  82. newData = data[glyphRecord.offset :]
  83. for j in range(glyphRecord.nMetaEntry):
  84. stringRec, newData = sstruct.unpack2(
  85. METAStringRecordFormat, newData, StringRecord()
  86. )
  87. if self.metaFlags == 0:
  88. [stringRec.offset] = struct.unpack(">H", newData[:2])
  89. newData = newData[2:]
  90. else:
  91. [stringRec.offset] = struct.unpack(">H", newData[:4])
  92. newData = newData[4:]
  93. stringRec.string = data[
  94. stringRec.offset : stringRec.offset + stringRec.stringLen
  95. ]
  96. glyphRecord.stringRecs.append(stringRec)
  97. self.glyphRecords.append(glyphRecord)
  98. def compile(self, ttFont):
  99. offsetOK = 0
  100. self.nMetaRecs = len(self.glyphRecords)
  101. count = 0
  102. while offsetOK != 1:
  103. count = count + 1
  104. if count > 4:
  105. pdb.set_trace()
  106. metaData = sstruct.pack(METAHeaderFormat, self)
  107. stringRecsOffset = len(metaData) + self.nMetaRecs * (
  108. 6 + 2 * (self.metaFlags & 1)
  109. )
  110. stringRecSize = 6 + 2 * (self.metaFlags & 1)
  111. for glyphRec in self.glyphRecords:
  112. glyphRec.offset = stringRecsOffset
  113. if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0):
  114. self.metaFlags = self.metaFlags + 1
  115. offsetOK = -1
  116. break
  117. metaData = metaData + glyphRec.compile(self)
  118. stringRecsOffset = stringRecsOffset + (
  119. glyphRec.nMetaEntry * stringRecSize
  120. )
  121. # this will be the String Record offset for the next GlyphRecord.
  122. if offsetOK == -1:
  123. offsetOK = 0
  124. continue
  125. # metaData now contains the header and all of the GlyphRecords. Its length should bw
  126. # the offset to the first StringRecord.
  127. stringOffset = stringRecsOffset
  128. for glyphRec in self.glyphRecords:
  129. assert glyphRec.offset == len(
  130. metaData
  131. ), "Glyph record offset did not compile correctly! for rec:" + str(
  132. glyphRec
  133. )
  134. for stringRec in glyphRec.stringRecs:
  135. stringRec.offset = stringOffset
  136. if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0):
  137. self.metaFlags = self.metaFlags + 1
  138. offsetOK = -1
  139. break
  140. metaData = metaData + stringRec.compile(self)
  141. stringOffset = stringOffset + stringRec.stringLen
  142. if offsetOK == -1:
  143. offsetOK = 0
  144. continue
  145. if ((self.metaFlags & 1) == 1) and (stringOffset < 65536):
  146. self.metaFlags = self.metaFlags - 1
  147. continue
  148. else:
  149. offsetOK = 1
  150. # metaData now contains the header and all of the GlyphRecords and all of the String Records.
  151. # Its length should be the offset to the first string datum.
  152. for glyphRec in self.glyphRecords:
  153. for stringRec in glyphRec.stringRecs:
  154. assert stringRec.offset == len(
  155. metaData
  156. ), "String offset did not compile correctly! for string:" + str(
  157. stringRec.string
  158. )
  159. metaData = metaData + stringRec.string
  160. return metaData
  161. def toXML(self, writer, ttFont):
  162. writer.comment(
  163. "Lengths and number of entries in this table will be recalculated by the compiler"
  164. )
  165. writer.newline()
  166. formatstring, names, fixes = sstruct.getformat(METAHeaderFormat)
  167. for name in names:
  168. value = getattr(self, name)
  169. writer.simpletag(name, value=value)
  170. writer.newline()
  171. for glyphRec in self.glyphRecords:
  172. glyphRec.toXML(writer, ttFont)
  173. def fromXML(self, name, attrs, content, ttFont):
  174. if name == "GlyphRecord":
  175. if not hasattr(self, "glyphRecords"):
  176. self.glyphRecords = []
  177. glyphRec = GlyphRecord()
  178. self.glyphRecords.append(glyphRec)
  179. for element in content:
  180. if isinstance(element, str):
  181. continue
  182. name, attrs, content = element
  183. glyphRec.fromXML(name, attrs, content, ttFont)
  184. glyphRec.offset = -1
  185. glyphRec.nMetaEntry = len(glyphRec.stringRecs)
  186. else:
  187. setattr(self, name, safeEval(attrs["value"]))
  188. class GlyphRecord(object):
  189. def __init__(self):
  190. self.glyphID = -1
  191. self.nMetaEntry = -1
  192. self.offset = -1
  193. self.stringRecs = []
  194. def toXML(self, writer, ttFont):
  195. writer.begintag("GlyphRecord")
  196. writer.newline()
  197. writer.simpletag("glyphID", value=self.glyphID)
  198. writer.newline()
  199. writer.simpletag("nMetaEntry", value=self.nMetaEntry)
  200. writer.newline()
  201. for stringRec in self.stringRecs:
  202. stringRec.toXML(writer, ttFont)
  203. writer.endtag("GlyphRecord")
  204. writer.newline()
  205. def fromXML(self, name, attrs, content, ttFont):
  206. if name == "StringRecord":
  207. stringRec = StringRecord()
  208. self.stringRecs.append(stringRec)
  209. for element in content:
  210. if isinstance(element, str):
  211. continue
  212. stringRec.fromXML(name, attrs, content, ttFont)
  213. stringRec.stringLen = len(stringRec.string)
  214. else:
  215. setattr(self, name, safeEval(attrs["value"]))
  216. def compile(self, parentTable):
  217. data = sstruct.pack(METAGlyphRecordFormat, self)
  218. if parentTable.metaFlags == 0:
  219. datum = struct.pack(">H", self.offset)
  220. elif parentTable.metaFlags == 1:
  221. datum = struct.pack(">L", self.offset)
  222. data = data + datum
  223. return data
  224. def __repr__(self):
  225. return (
  226. "GlyphRecord[ glyphID: "
  227. + str(self.glyphID)
  228. + ", nMetaEntry: "
  229. + str(self.nMetaEntry)
  230. + ", offset: "
  231. + str(self.offset)
  232. + " ]"
  233. )
  234. # XXX The following two functions are really broken around UTF-8 vs Unicode
  235. def mapXMLToUTF8(string):
  236. uString = str()
  237. strLen = len(string)
  238. i = 0
  239. while i < strLen:
  240. prefixLen = 0
  241. if string[i : i + 3] == "&#x":
  242. prefixLen = 3
  243. elif string[i : i + 7] == "&amp;#x":
  244. prefixLen = 7
  245. if prefixLen:
  246. i = i + prefixLen
  247. j = i
  248. while string[i] != ";":
  249. i = i + 1
  250. valStr = string[j:i]
  251. uString = uString + chr(eval("0x" + valStr))
  252. else:
  253. uString = uString + chr(byteord(string[i]))
  254. i = i + 1
  255. return uString.encode("utf_8")
  256. def mapUTF8toXML(string):
  257. uString = string.decode("utf_8")
  258. string = ""
  259. for uChar in uString:
  260. i = ord(uChar)
  261. if (i < 0x80) and (i > 0x1F):
  262. string = string + uChar
  263. else:
  264. string = string + "&#x" + hex(i)[2:] + ";"
  265. return string
  266. class StringRecord(object):
  267. def toXML(self, writer, ttFont):
  268. writer.begintag("StringRecord")
  269. writer.newline()
  270. writer.simpletag("labelID", value=self.labelID)
  271. writer.comment(getLabelString(self.labelID))
  272. writer.newline()
  273. writer.newline()
  274. writer.simpletag("string", value=mapUTF8toXML(self.string))
  275. writer.newline()
  276. writer.endtag("StringRecord")
  277. writer.newline()
  278. def fromXML(self, name, attrs, content, ttFont):
  279. for element in content:
  280. if isinstance(element, str):
  281. continue
  282. name, attrs, content = element
  283. value = attrs["value"]
  284. if name == "string":
  285. self.string = mapXMLToUTF8(value)
  286. else:
  287. setattr(self, name, safeEval(value))
  288. def compile(self, parentTable):
  289. data = sstruct.pack(METAStringRecordFormat, self)
  290. if parentTable.metaFlags == 0:
  291. datum = struct.pack(">H", self.offset)
  292. elif parentTable.metaFlags == 1:
  293. datum = struct.pack(">L", self.offset)
  294. data = data + datum
  295. return data
  296. def __repr__(self):
  297. return (
  298. "StringRecord [ labelID: "
  299. + str(self.labelID)
  300. + " aka "
  301. + getLabelString(self.labelID)
  302. + ", offset: "
  303. + str(self.offset)
  304. + ", length: "
  305. + str(self.stringLen)
  306. + ", string: "
  307. + self.string
  308. + " ]"
  309. )