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
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:33 +0000
1"""sstruct.py -- SuperStruct
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.
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.
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.
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.
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.
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).
44calcsize(fmt)
45 like struct.calcsize(), but uses our own fmt strings:
46 it returns the size of the data in bytes.
47"""
49from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
50from fontTools.misc.textTools import tobytes, tostr
51import struct
52import re
54__version__ = "1.2"
55__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>"
58class Error(Exception):
59 pass
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
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
104def unpack2(fmt, data, obj=None):
105 length = calcsize(fmt)
106 return unpack(fmt, data[:length], obj), data[length:]
109def calcsize(fmt):
110 formatstring, names, fixes = getformat(fmt)
111 return struct.calcsize(formatstring)
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)
126# matches the special struct fmt chars and 'x' (pad byte)
127_extraRE = re.compile(r"\s*([x@=<>!])\s*(#.*)?$")
129# matches an "empty" string, possibly containing whitespace and/or a comment
130_emptyRE = re.compile(r"\s*(#.*)?$")
132_fixedpointmappings = {8: "b", 16: "h", 32: "l"}
134_formatcache = {}
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
177def _test():
178 fmt = """
179 # comments are allowed
180 > # big endian (see documentation for struct)
181 # empty lines are allowed:
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 """
194 print("size:", calcsize(fmt))
196 class foo(object):
197 pass
199 i = foo()
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
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))
219if __name__ == "__main__":
220 _test()