sbixGlyph.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from fontTools.misc import sstruct
  2. from fontTools.misc.textTools import readHex, safeEval
  3. import struct
  4. sbixGlyphHeaderFormat = """
  5. >
  6. originOffsetX: h # The x-value of the point in the glyph relative to its
  7. # lower-left corner which corresponds to the origin of
  8. # the glyph on the screen, that is the point on the
  9. # baseline at the left edge of the glyph.
  10. originOffsetY: h # The y-value of the point in the glyph relative to its
  11. # lower-left corner which corresponds to the origin of
  12. # the glyph on the screen, that is the point on the
  13. # baseline at the left edge of the glyph.
  14. graphicType: 4s # e.g. "png "
  15. """
  16. sbixGlyphHeaderFormatSize = sstruct.calcsize(sbixGlyphHeaderFormat)
  17. class Glyph(object):
  18. def __init__(
  19. self,
  20. glyphName=None,
  21. referenceGlyphName=None,
  22. originOffsetX=0,
  23. originOffsetY=0,
  24. graphicType=None,
  25. imageData=None,
  26. rawdata=None,
  27. gid=0,
  28. ):
  29. self.gid = gid
  30. self.glyphName = glyphName
  31. self.referenceGlyphName = referenceGlyphName
  32. self.originOffsetX = originOffsetX
  33. self.originOffsetY = originOffsetY
  34. self.rawdata = rawdata
  35. self.graphicType = graphicType
  36. self.imageData = imageData
  37. # fix self.graphicType if it is null terminated or too short
  38. if self.graphicType is not None:
  39. if self.graphicType[-1] == "\0":
  40. self.graphicType = self.graphicType[:-1]
  41. if len(self.graphicType) > 4:
  42. from fontTools import ttLib
  43. raise ttLib.TTLibError(
  44. "Glyph.graphicType must not be longer than 4 characters."
  45. )
  46. elif len(self.graphicType) < 4:
  47. # pad with spaces
  48. self.graphicType += " "[: (4 - len(self.graphicType))]
  49. def is_reference_type(self):
  50. """Returns True if this glyph is a reference to another glyph's image data."""
  51. return self.graphicType == "dupe" or self.graphicType == "flip"
  52. def decompile(self, ttFont):
  53. self.glyphName = ttFont.getGlyphName(self.gid)
  54. if self.rawdata is None:
  55. from fontTools import ttLib
  56. raise ttLib.TTLibError("No table data to decompile")
  57. if len(self.rawdata) > 0:
  58. if len(self.rawdata) < sbixGlyphHeaderFormatSize:
  59. from fontTools import ttLib
  60. # print "Glyph %i header too short: Expected %x, got %x." % (self.gid, sbixGlyphHeaderFormatSize, len(self.rawdata))
  61. raise ttLib.TTLibError("Glyph header too short.")
  62. sstruct.unpack(
  63. sbixGlyphHeaderFormat, self.rawdata[:sbixGlyphHeaderFormatSize], self
  64. )
  65. if self.is_reference_type():
  66. # this glyph is a reference to another glyph's image data
  67. (gid,) = struct.unpack(">H", self.rawdata[sbixGlyphHeaderFormatSize:])
  68. self.referenceGlyphName = ttFont.getGlyphName(gid)
  69. else:
  70. self.imageData = self.rawdata[sbixGlyphHeaderFormatSize:]
  71. self.referenceGlyphName = None
  72. # clean up
  73. del self.rawdata
  74. del self.gid
  75. def compile(self, ttFont):
  76. if self.glyphName is None:
  77. from fontTools import ttLib
  78. raise ttLib.TTLibError("Can't compile Glyph without glyph name")
  79. # TODO: if ttFont has no maxp, cmap etc., ignore glyph names and compile by index?
  80. # (needed if you just want to compile the sbix table on its own)
  81. self.gid = struct.pack(">H", ttFont.getGlyphID(self.glyphName))
  82. if self.graphicType is None:
  83. rawdata = b""
  84. else:
  85. rawdata = sstruct.pack(sbixGlyphHeaderFormat, self)
  86. if self.is_reference_type():
  87. rawdata += struct.pack(">H", ttFont.getGlyphID(self.referenceGlyphName))
  88. else:
  89. assert self.imageData is not None
  90. rawdata += self.imageData
  91. self.rawdata = rawdata
  92. def toXML(self, xmlWriter, ttFont):
  93. if self.graphicType is None:
  94. # TODO: ignore empty glyphs?
  95. # a glyph data entry is required for each glyph,
  96. # but empty ones can be calculated at compile time
  97. xmlWriter.simpletag("glyph", name=self.glyphName)
  98. xmlWriter.newline()
  99. return
  100. xmlWriter.begintag(
  101. "glyph",
  102. graphicType=self.graphicType,
  103. name=self.glyphName,
  104. originOffsetX=self.originOffsetX,
  105. originOffsetY=self.originOffsetY,
  106. )
  107. xmlWriter.newline()
  108. if self.is_reference_type():
  109. # this glyph is a reference to another glyph id.
  110. xmlWriter.simpletag("ref", glyphname=self.referenceGlyphName)
  111. else:
  112. xmlWriter.begintag("hexdata")
  113. xmlWriter.newline()
  114. xmlWriter.dumphex(self.imageData)
  115. xmlWriter.endtag("hexdata")
  116. xmlWriter.newline()
  117. xmlWriter.endtag("glyph")
  118. xmlWriter.newline()
  119. def fromXML(self, name, attrs, content, ttFont):
  120. if name == "ref":
  121. # this glyph i.e. a reference to another glyph's image data.
  122. # in this case imageData contains the glyph id of the reference glyph
  123. # get glyph id from glyphname
  124. glyphname = safeEval("'''" + attrs["glyphname"] + "'''")
  125. self.imageData = struct.pack(">H", ttFont.getGlyphID(glyphname))
  126. self.referenceGlyphName = glyphname
  127. elif name == "hexdata":
  128. self.imageData = readHex(content)
  129. else:
  130. from fontTools import ttLib
  131. raise ttLib.TTLibError("can't handle '%s' element" % name)