xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ttLib/tables/sbixStrike.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.misc import sstruct
2from fontTools.misc.textTools import safeEval
3from .sbixGlyph import Glyph
4import struct
5
6sbixStrikeHeaderFormat = """
7	>
8	ppem:          H	# The PPEM for which this strike was designed (e.g., 9,
9						# 12, 24)
10	resolution:    H	# The screen resolution (in dpi) for which this strike
11						# was designed (e.g., 72)
12"""
13
14sbixGlyphDataOffsetFormat = """
15	>
16	glyphDataOffset:   L	# Offset from the beginning of the strike data record
17							# to data for the individual glyph
18"""
19
20sbixStrikeHeaderFormatSize = sstruct.calcsize(sbixStrikeHeaderFormat)
21sbixGlyphDataOffsetFormatSize = sstruct.calcsize(sbixGlyphDataOffsetFormat)
22
23
24class Strike(object):
25    def __init__(self, rawdata=None, ppem=0, resolution=72):
26        self.data = rawdata
27        self.ppem = ppem
28        self.resolution = resolution
29        self.glyphs = {}
30
31    def decompile(self, ttFont):
32        if self.data is None:
33            from fontTools import ttLib
34
35            raise ttLib.TTLibError
36        if len(self.data) < sbixStrikeHeaderFormatSize:
37            from fontTools import ttLib
38
39            raise (
40                ttLib.TTLibError,
41                "Strike header too short: Expected %x, got %x.",
42            ) % (sbixStrikeHeaderFormatSize, len(self.data))
43
44        # read Strike header from raw data
45        sstruct.unpack(
46            sbixStrikeHeaderFormat, self.data[:sbixStrikeHeaderFormatSize], self
47        )
48
49        # calculate number of glyphs
50        (firstGlyphDataOffset,) = struct.unpack(
51            ">L",
52            self.data[
53                sbixStrikeHeaderFormatSize : sbixStrikeHeaderFormatSize
54                + sbixGlyphDataOffsetFormatSize
55            ],
56        )
57        self.numGlyphs = (
58            firstGlyphDataOffset - sbixStrikeHeaderFormatSize
59        ) // sbixGlyphDataOffsetFormatSize - 1
60        # ^ -1 because there's one more offset than glyphs
61
62        # build offset list for single glyph data offsets
63        self.glyphDataOffsets = []
64        for i in range(
65            self.numGlyphs + 1
66        ):  # + 1 because there's one more offset than glyphs
67            start = i * sbixGlyphDataOffsetFormatSize + sbixStrikeHeaderFormatSize
68            (current_offset,) = struct.unpack(
69                ">L", self.data[start : start + sbixGlyphDataOffsetFormatSize]
70            )
71            self.glyphDataOffsets.append(current_offset)
72
73        # iterate through offset list and slice raw data into glyph data records
74        for i in range(self.numGlyphs):
75            current_glyph = Glyph(
76                rawdata=self.data[
77                    self.glyphDataOffsets[i] : self.glyphDataOffsets[i + 1]
78                ],
79                gid=i,
80            )
81            current_glyph.decompile(ttFont)
82            self.glyphs[current_glyph.glyphName] = current_glyph
83        del self.glyphDataOffsets
84        del self.numGlyphs
85        del self.data
86
87    def compile(self, ttFont):
88        self.glyphDataOffsets = b""
89        self.bitmapData = b""
90
91        glyphOrder = ttFont.getGlyphOrder()
92
93        # first glyph starts right after the header
94        currentGlyphDataOffset = (
95            sbixStrikeHeaderFormatSize
96            + sbixGlyphDataOffsetFormatSize * (len(glyphOrder) + 1)
97        )
98        for glyphName in glyphOrder:
99            if glyphName in self.glyphs:
100                # we have glyph data for this glyph
101                current_glyph = self.glyphs[glyphName]
102            else:
103                # must add empty glyph data record for this glyph
104                current_glyph = Glyph(glyphName=glyphName)
105            current_glyph.compile(ttFont)
106            current_glyph.glyphDataOffset = currentGlyphDataOffset
107            self.bitmapData += current_glyph.rawdata
108            currentGlyphDataOffset += len(current_glyph.rawdata)
109            self.glyphDataOffsets += sstruct.pack(
110                sbixGlyphDataOffsetFormat, current_glyph
111            )
112
113        # add last "offset", really the end address of the last glyph data record
114        dummy = Glyph()
115        dummy.glyphDataOffset = currentGlyphDataOffset
116        self.glyphDataOffsets += sstruct.pack(sbixGlyphDataOffsetFormat, dummy)
117
118        # pack header
119        self.data = sstruct.pack(sbixStrikeHeaderFormat, self)
120        # add offsets and image data after header
121        self.data += self.glyphDataOffsets + self.bitmapData
122
123    def toXML(self, xmlWriter, ttFont):
124        xmlWriter.begintag("strike")
125        xmlWriter.newline()
126        xmlWriter.simpletag("ppem", value=self.ppem)
127        xmlWriter.newline()
128        xmlWriter.simpletag("resolution", value=self.resolution)
129        xmlWriter.newline()
130        glyphOrder = ttFont.getGlyphOrder()
131        for i in range(len(glyphOrder)):
132            if glyphOrder[i] in self.glyphs:
133                self.glyphs[glyphOrder[i]].toXML(xmlWriter, ttFont)
134                # TODO: what if there are more glyph data records than (glyf table) glyphs?
135        xmlWriter.endtag("strike")
136        xmlWriter.newline()
137
138    def fromXML(self, name, attrs, content, ttFont):
139        if name in ["ppem", "resolution"]:
140            setattr(self, name, safeEval(attrs["value"]))
141        elif name == "glyph":
142            if "graphicType" in attrs:
143                myFormat = safeEval("'''" + attrs["graphicType"] + "'''")
144            else:
145                myFormat = None
146            if "glyphname" in attrs:
147                myGlyphName = safeEval("'''" + attrs["glyphname"] + "'''")
148            elif "name" in attrs:
149                myGlyphName = safeEval("'''" + attrs["name"] + "'''")
150            else:
151                from fontTools import ttLib
152
153                raise ttLib.TTLibError("Glyph must have a glyph name.")
154            if "originOffsetX" in attrs:
155                myOffsetX = safeEval(attrs["originOffsetX"])
156            else:
157                myOffsetX = 0
158            if "originOffsetY" in attrs:
159                myOffsetY = safeEval(attrs["originOffsetY"])
160            else:
161                myOffsetY = 0
162            current_glyph = Glyph(
163                glyphName=myGlyphName,
164                graphicType=myFormat,
165                originOffsetX=myOffsetX,
166                originOffsetY=myOffsetY,
167            )
168            for element in content:
169                if isinstance(element, tuple):
170                    name, attrs, content = element
171                    current_glyph.fromXML(name, attrs, content, ttFont)
172                    current_glyph.compile(ttFont)
173            self.glyphs[current_glyph.glyphName] = current_glyph
174        else:
175            from fontTools import ttLib
176
177            raise ttLib.TTLibError("can't handle '%s' element" % name)
178