1"""fontTools.misc.textTools.py -- miscellaneous routines."""
2
3from __future__ import annotations
4
5import ast
6import string
7
8
9# alias kept for backward compatibility
10safeEval = ast.literal_eval
11
12
13class Tag(str):
14 @staticmethod
15 def transcode(blob):
16 if isinstance(blob, bytes):
17 blob = blob.decode("latin-1")
18 return blob
19
20 def __new__(self, content):
21 return str.__new__(self, self.transcode(content))
22
23 def __ne__(self, other):
24 return not self.__eq__(other)
25
26 def __eq__(self, other):
27 return str.__eq__(self, self.transcode(other))
28
29 def __hash__(self):
30 return str.__hash__(self)
31
32 def tobytes(self):
33 return self.encode("latin-1")
34
35
36def readHex(content):
37 """Convert a list of hex strings to binary data."""
38 return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, str)))
39
40
41def deHexStr(hexdata):
42 """Convert a hex string to binary data."""
43 hexdata = strjoin(hexdata.split())
44 if len(hexdata) % 2:
45 hexdata = hexdata + "0"
46 data = []
47 for i in range(0, len(hexdata), 2):
48 data.append(bytechr(int(hexdata[i : i + 2], 16)))
49 return bytesjoin(data)
50
51
52def hexStr(data):
53 """Convert binary data to a hex string."""
54 h = string.hexdigits
55 r = ""
56 for c in data:
57 i = byteord(c)
58 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
59 return r
60
61
62def num2binary(l, bits=32):
63 items = []
64 binary = ""
65 for i in range(bits):
66 if l & 0x1:
67 binary = "1" + binary
68 else:
69 binary = "0" + binary
70 l = l >> 1
71 if not ((i + 1) % 8):
72 items.append(binary)
73 binary = ""
74 if binary:
75 items.append(binary)
76 items.reverse()
77 assert l in (0, -1), "number doesn't fit in number of bits"
78 return " ".join(items)
79
80
81def binary2num(bin):
82 bin = strjoin(bin.split())
83 l = 0
84 for digit in bin:
85 l = l << 1
86 if digit != "0":
87 l = l | 0x1
88 return l
89
90
91def caselessSort(alist):
92 """Return a sorted copy of a list. If there are only strings
93 in the list, it will not consider case.
94 """
95
96 try:
97 return sorted(alist, key=lambda a: (a.lower(), a))
98 except TypeError:
99 return sorted(alist)
100
101
102def pad(data, size):
103 r"""Pad byte string 'data' with null bytes until its length is a
104 multiple of 'size'.
105
106 >>> len(pad(b'abcd', 4))
107 4
108 >>> len(pad(b'abcde', 2))
109 6
110 >>> len(pad(b'abcde', 4))
111 8
112 >>> pad(b'abcdef', 4) == b'abcdef\x00\x00'
113 True
114 """
115 data = tobytes(data)
116 if size > 1:
117 remainder = len(data) % size
118 if remainder:
119 data += b"\0" * (size - remainder)
120 return data
121
122
123def tostr(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> str:
124 if not isinstance(s, str):
125 return s.decode(encoding, errors)
126 else:
127 return s
128
129
130def tobytes(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> bytes:
131 if isinstance(s, str):
132 return s.encode(encoding, errors)
133 else:
134 return bytes(s)
135
136
137def bytechr(n):
138 return bytes([n])
139
140
141def byteord(c):
142 return c if isinstance(c, int) else ord(c)
143
144
145def strjoin(iterable, joiner=""):
146 return tostr(joiner).join(iterable)
147
148
149def bytesjoin(iterable, joiner=b""):
150 return tobytes(joiner).join(tobytes(item) for item in iterable)
151
152
153if __name__ == "__main__":
154 import doctest, sys
155
156 sys.exit(doctest.testmod().failed)