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