Coverage for /usr/lib/python3/dist-packages/fontTools/ttLib/tables/sbixStrike.py: 15%
96 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from fontTools.misc import sstruct
2from fontTools.misc.textTools import safeEval
3from .sbixGlyph import Glyph
4import struct
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"""
14sbixGlyphDataOffsetFormat = """
15 >
16 glyphDataOffset: L # Offset from the beginning of the strike data record
17 # to data for the individual glyph
18"""
20sbixStrikeHeaderFormatSize = sstruct.calcsize(sbixStrikeHeaderFormat)
21sbixGlyphDataOffsetFormatSize = sstruct.calcsize(sbixGlyphDataOffsetFormat)
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 = {}
31 def decompile(self, ttFont):
32 if self.data is None:
33 from fontTools import ttLib
35 raise ttLib.TTLibError
36 if len(self.data) < sbixStrikeHeaderFormatSize:
37 from fontTools import ttLib
39 raise (
40 ttLib.TTLibError,
41 "Strike header too short: Expected %x, got %x.",
42 ) % (sbixStrikeHeaderFormatSize, len(self.data))
44 # read Strike header from raw data
45 sstruct.unpack(
46 sbixStrikeHeaderFormat, self.data[:sbixStrikeHeaderFormatSize], self
47 )
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
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)
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
87 def compile(self, ttFont):
88 self.glyphDataOffsets = b""
89 self.bitmapData = b""
91 glyphOrder = ttFont.getGlyphOrder()
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 )
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)
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
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()
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
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
177 raise ttLib.TTLibError("can't handle '%s' element" % name)