Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fontTools/misc/sstruct.py: 58%

113 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:33 +0000

1"""sstruct.py -- SuperStruct 

2 

3Higher level layer on top of the struct module, enabling to 

4bind names to struct elements. The interface is similar to 

5struct, except the objects passed and returned are not tuples 

6(or argument lists), but dictionaries or instances. 

7 

8Just like struct, we use fmt strings to describe a data 

9structure, except we use one line per element. Lines are 

10separated by newlines or semi-colons. Each line contains 

11either one of the special struct characters ('@', '=', '<', 

12'>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f'). 

13Repetitions, like the struct module offers them are not useful 

14in this context, except for fixed length strings (eg. 'myInt:5h' 

15is not allowed but 'myString:5s' is). The 'x' fmt character 

16(pad byte) is treated as 'special', since it is by definition 

17anonymous. Extra whitespace is allowed everywhere. 

18 

19The sstruct module offers one feature that the "normal" struct 

20module doesn't: support for fixed point numbers. These are spelled 

21as "n.mF", where n is the number of bits before the point, and m 

22the number of bits after the point. Fixed point numbers get 

23converted to floats. 

24 

25pack(fmt, object): 

26 'object' is either a dictionary or an instance (or actually 

27 anything that has a __dict__ attribute). If it is a dictionary, 

28 its keys are used for names. If it is an instance, it's 

29 attributes are used to grab struct elements from. Returns 

30 a string containing the data. 

31 

32unpack(fmt, data, object=None) 

33 If 'object' is omitted (or None), a new dictionary will be 

34 returned. If 'object' is a dictionary, it will be used to add 

35 struct elements to. If it is an instance (or in fact anything 

36 that has a __dict__ attribute), an attribute will be added for 

37 each struct element. In the latter two cases, 'object' itself 

38 is returned. 

39 

40unpack2(fmt, data, object=None) 

41 Convenience function. Same as unpack, except data may be longer 

42 than needed. The returned value is a tuple: (object, leftoverdata). 

43 

44calcsize(fmt) 

45 like struct.calcsize(), but uses our own fmt strings: 

46 it returns the size of the data in bytes. 

47""" 

48 

49from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi 

50from fontTools.misc.textTools import tobytes, tostr 

51import struct 

52import re 

53 

54__version__ = "1.2" 

55__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>" 

56 

57 

58class Error(Exception): 

59 pass 

60 

61 

62def pack(fmt, obj): 

63 formatstring, names, fixes = getformat(fmt, keep_pad_byte=True) 

64 elements = [] 

65 if not isinstance(obj, dict): 

66 obj = obj.__dict__ 

67 for name in names: 

68 value = obj[name] 

69 if name in fixes: 

70 # fixed point conversion 

71 value = fl2fi(value, fixes[name]) 

72 elif isinstance(value, str): 

73 value = tobytes(value) 

74 elements.append(value) 

75 data = struct.pack(*(formatstring,) + tuple(elements)) 

76 return data 

77 

78 

79def unpack(fmt, data, obj=None): 

80 if obj is None: 

81 obj = {} 

82 data = tobytes(data) 

83 formatstring, names, fixes = getformat(fmt) 

84 if isinstance(obj, dict): 

85 d = obj 

86 else: 

87 d = obj.__dict__ 

88 elements = struct.unpack(formatstring, data) 

89 for i in range(len(names)): 

90 name = names[i] 

91 value = elements[i] 

92 if name in fixes: 

93 # fixed point conversion 

94 value = fi2fl(value, fixes[name]) 

95 elif isinstance(value, bytes): 

96 try: 

97 value = tostr(value) 

98 except UnicodeDecodeError: 

99 pass 

100 d[name] = value 

101 return obj 

102 

103 

104def unpack2(fmt, data, obj=None): 

105 length = calcsize(fmt) 

106 return unpack(fmt, data[:length], obj), data[length:] 

107 

108 

109def calcsize(fmt): 

110 formatstring, names, fixes = getformat(fmt) 

111 return struct.calcsize(formatstring) 

112 

113 

114# matches "name:formatchar" (whitespace is allowed) 

115_elementRE = re.compile( 

116 r"\s*" # whitespace 

117 r"([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier) 

118 r"\s*:\s*" # whitespace : whitespace 

119 r"([xcbB?hHiIlLqQfd]|" # formatchar... 

120 r"[0-9]+[ps]|" # ...formatchar... 

121 r"([0-9]+)\.([0-9]+)(F))" # ...formatchar 

122 r"\s*" # whitespace 

123 r"(#.*)?$" # [comment] + end of string 

124) 

125 

126# matches the special struct fmt chars and 'x' (pad byte) 

127_extraRE = re.compile(r"\s*([x@=<>!])\s*(#.*)?$") 

128 

129# matches an "empty" string, possibly containing whitespace and/or a comment 

130_emptyRE = re.compile(r"\s*(#.*)?$") 

131 

132_fixedpointmappings = {8: "b", 16: "h", 32: "l"} 

133 

134_formatcache = {} 

135 

136 

137def getformat(fmt, keep_pad_byte=False): 

138 fmt = tostr(fmt, encoding="ascii") 

139 try: 

140 formatstring, names, fixes = _formatcache[fmt] 

141 except KeyError: 

142 lines = re.split("[\n;]", fmt) 

143 formatstring = "" 

144 names = [] 

145 fixes = {} 

146 for line in lines: 

147 if _emptyRE.match(line): 

148 continue 

149 m = _extraRE.match(line) 

150 if m: 

151 formatchar = m.group(1) 

152 if formatchar != "x" and formatstring: 

153 raise Error("a special fmt char must be first") 

154 else: 

155 m = _elementRE.match(line) 

156 if not m: 

157 raise Error("syntax error in fmt: '%s'" % line) 

158 name = m.group(1) 

159 formatchar = m.group(2) 

160 if keep_pad_byte or formatchar != "x": 

161 names.append(name) 

162 if m.group(3): 

163 # fixed point 

164 before = int(m.group(3)) 

165 after = int(m.group(4)) 

166 bits = before + after 

167 if bits not in [8, 16, 32]: 

168 raise Error("fixed point must be 8, 16 or 32 bits long") 

169 formatchar = _fixedpointmappings[bits] 

170 assert m.group(5) == "F" 

171 fixes[name] = after 

172 formatstring = formatstring + formatchar 

173 _formatcache[fmt] = formatstring, names, fixes 

174 return formatstring, names, fixes 

175 

176 

177def _test(): 

178 fmt = """ 

179 # comments are allowed 

180 > # big endian (see documentation for struct) 

181 # empty lines are allowed: 

182 

183 ashort: h 

184 along: l 

185 abyte: b # a byte 

186 achar: c 

187 astr: 5s 

188 afloat: f; adouble: d # multiple "statements" are allowed 

189 afixed: 16.16F 

190 abool: ? 

191 apad: x 

192 """ 

193 

194 print("size:", calcsize(fmt)) 

195 

196 class foo(object): 

197 pass 

198 

199 i = foo() 

200 

201 i.ashort = 0x7FFF 

202 i.along = 0x7FFFFFFF 

203 i.abyte = 0x7F 

204 i.achar = "a" 

205 i.astr = "12345" 

206 i.afloat = 0.5 

207 i.adouble = 0.5 

208 i.afixed = 1.5 

209 i.abool = True 

210 

211 data = pack(fmt, i) 

212 print("data:", repr(data)) 

213 print(unpack(fmt, data)) 

214 i2 = foo() 

215 unpack(fmt, data, i2) 

216 print(vars(i2)) 

217 

218 

219if __name__ == "__main__": 

220 _test()