jkUnicode 2.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jkUnicode/__init__.py ADDED
@@ -0,0 +1,280 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import cached_property
4
+ from jkUnicode.aglfn import getGlyphnameForUnicode
5
+ from jkUnicode.uniBlock import get_block
6
+ from jkUnicode.uniCase import uniLowerCaseMapping, uniUpperCaseMapping
7
+ from jkUnicode.uniCat import uniCat
8
+ from jkUnicode.uniDecomposition import uniDecompositionMapping
9
+ from jkUnicode.uniName import uniName
10
+ from jkUnicode.uniNiceName import nice_name_rules
11
+ from jkUnicode.uniScript import get_script
12
+
13
+
14
+ categoryName = {
15
+ "Lu": "Letter, Uppercase",
16
+ "Ll": "Letter, Lowercase",
17
+ "Lt": "Letter, Titlecase",
18
+ "Lm": "Letter, Modifier",
19
+ "Lo": "Letter, Other",
20
+ "Mn": "Mark, Nonspacing",
21
+ "Mc": "Mark, Spacing Combining",
22
+ "Me": "Mark, Enclosing",
23
+ "Nd": "Number, Decimal Digit",
24
+ "Nl": "Number, Letter",
25
+ "No": "Number, Other",
26
+ "Pc": "Punctuation, Connector",
27
+ "Pd": "Punctuation, Dash",
28
+ "Ps": "Punctuation, Open",
29
+ "Pe": "Punctuation, Close",
30
+ "Pi": "Punctuation, Initial quote (may behave like Ps or Pe depending on usage)",
31
+ "Pf": "Punctuation, Final quote (may behave like Ps or Pe depending on usage)",
32
+ "Po": "Punctuation, Other",
33
+ "Sm": "Symbol, Math",
34
+ "Sc": "Symbol, Currency",
35
+ "Sk": "Symbol, Modifier",
36
+ "So": "Symbol, Other",
37
+ "Zs": "Separator, Space",
38
+ "Zl": "Separator, Line",
39
+ "Zp": "Separator, Paragraph",
40
+ "Cc": "Other, Control",
41
+ "Cf": "Other, Format",
42
+ "Cs": "Other, Surrogate",
43
+ "Co": "Other, Private Use",
44
+ "Cn": "Other, Not Assigned",
45
+ }
46
+
47
+
48
+ def get_expanded_glyph_list(
49
+ unicodes: list[int], ui: UniInfo | None = None
50
+ ) -> list[tuple[int, str | None]]:
51
+ """ "Expand" or annotate a list of codepoints.
52
+
53
+ For codepoints that have a case mapping (UC or LC), the target codepoint of
54
+ the case mapping will be added to the list. AGLFN glyph names are added to
55
+ the list too, so the returned list contains tuples of `(codepoint,
56
+ glyphname)`, sorted by the codepoint value.
57
+
58
+ :param unicodes: A list of codepoints
59
+ :type unicodes: list
60
+
61
+ :param ui: The UniInfo instance to use. If None, one will be instantiated.
62
+ :type ui: UniInfo"""
63
+ glyphs = []
64
+ if ui is None:
65
+ ui = UniInfo(0)
66
+ for ch in unicodes:
67
+ ui.unicode = ch
68
+ glyphs.append((ch, ui.glyphname))
69
+ if ui.lc_mapping is not None:
70
+ ui.unicode = ui.lc_mapping
71
+ glyphs.append((ui.unicode, ui.glyphname))
72
+ elif ui.uc_mapping is not None:
73
+ ui.unicode = ui.uc_mapping
74
+ glyphs.append((ui.unicode, ui.glyphname))
75
+ return sorted(list(set(glyphs)))
76
+
77
+
78
+ def getUnicodeChar(code: int) -> str:
79
+ """Return the Unicode character for a Unicode codepoint.
80
+
81
+ :param code: The codepoint
82
+ :type code: int"""
83
+ return chr(code)
84
+
85
+
86
+ class UniInfo:
87
+ """The main Unicode Info object. It gets its Unicode information from the
88
+ submodules aglfn, uniCase, uniCat, uniDecomposition, uniName, and
89
+ uniRangesBits which are generated from the official Unicode data. You can
90
+ find tools to download and regenerate the data in the `tools` subfolder.
91
+ """
92
+
93
+ def __init__(self, uni: int | None = None) -> None:
94
+ """The Unicode Info object is meant to be instantiated once and then
95
+ reused to get information about different codepoints. Avoid to
96
+ instantiate it often, because it is rather expensive.
97
+
98
+ Initialize the Info object with a None e.g. before a loop and then in
99
+ the loop assign the actual codepoints that you want information about by
100
+ setting the `unicode` instance variable. This will automatically update
101
+ the other instance variables with the correct information from the
102
+ Unicode standard.
103
+
104
+ :param uni: The codepoint.
105
+ :type uni: int"""
106
+ self._unicode = None
107
+ if uni is not None:
108
+ self.unicode = uni
109
+
110
+ @property
111
+ def unicode(self) -> int | None:
112
+ """The Unicode codepoint. Setting this value will look up and fill the
113
+ other pieces of information, like category, range, decomposition
114
+ mapping, and case mapping."""
115
+ return self._unicode
116
+
117
+ @unicode.setter
118
+ def unicode(self, value: int | None):
119
+ if value == self._unicode:
120
+ return
121
+
122
+ self._unicode = value
123
+
124
+ # Invalidate cached properties
125
+ for attr in (
126
+ "block",
127
+ "category",
128
+ "category_short",
129
+ "decomposition_mapping",
130
+ "glyphname",
131
+ "lc_mapping",
132
+ "name",
133
+ "nice_name",
134
+ "script",
135
+ "uc_mapping",
136
+ ):
137
+ try:
138
+ delattr(self, attr)
139
+ except AttributeError:
140
+ pass
141
+
142
+ if self._unicode is None:
143
+ self.category_short = "<undefined>"
144
+ self.category = "<undefined>"
145
+ self.decomposition_mapping = []
146
+
147
+ def __repr__(self) -> str:
148
+ if self.unicode is None:
149
+ s = " Unicode: None"
150
+ else:
151
+ s = " Unicode: 0x{:04X} (dec. {})".format(
152
+ self.unicode,
153
+ self.unicode,
154
+ )
155
+ s += "\n Name: %s" % self.name
156
+ s += "\n Category: {} ({})".format(self.category_short, self.category)
157
+ if self.uc_mapping:
158
+ s += "\n Uppercase: 0x%04X" % self.uc_mapping
159
+ if self.lc_mapping:
160
+ s += "\n Lowercase: 0x%04X" % self.lc_mapping
161
+ if self.decomposition_mapping:
162
+ s += "\nDecomposition: %s" % (
163
+ " ".join(["0x%04X" % m for m in self.decomposition_mapping])
164
+ )
165
+ return s
166
+
167
+ @cached_property
168
+ def block(self) -> str | None:
169
+ """The name of the block for the current codepoint."""
170
+ return get_block(self._unicode)
171
+
172
+ @cached_property
173
+ def category(self) -> str | None:
174
+ """The name of the category for the current codepoint."""
175
+ if self._unicode is None:
176
+ return None
177
+
178
+ if self.category_short is None:
179
+ return "<undefined>"
180
+
181
+ return categoryName.get(self.category_short, "<undefined>")
182
+
183
+ @cached_property
184
+ def category_short(self) -> str | None:
185
+ """The short name of the category for the current codepoint."""
186
+ if self._unicode is None:
187
+ return None
188
+
189
+ return uniCat.get(self._unicode, "<undefined>")
190
+
191
+ @property
192
+ def char(self) -> str | None:
193
+ """The character for the current codepoint."""
194
+ if self.unicode is None:
195
+ return None
196
+
197
+ return getUnicodeChar(self.unicode)
198
+
199
+ @char.setter
200
+ def char(self, value: str | None) -> None:
201
+ if value is None:
202
+ self.unicode = None
203
+ else:
204
+ self.unicode = ord(value)
205
+
206
+ @cached_property
207
+ def glyphname(self) -> str | None:
208
+ """The AGLFN glyph name for the current codepoint."""
209
+
210
+ return getGlyphnameForUnicode(self.unicode)
211
+
212
+ @cached_property
213
+ def name(self) -> str | None:
214
+ """The Unicode name for the current codepoint."""
215
+ if self._unicode is None:
216
+ return None
217
+
218
+ name = uniName.get(self._unicode, None)
219
+ # TODO: Add nicer names based on original Unicode names?
220
+ if name is None:
221
+ if 0xE000 <= self._unicode < 0xF8FF:
222
+ return "<Private Use>"
223
+ if 0xD800 <= self._unicode < 0xDB7F:
224
+ return "<Non Private Use High Surrogate #%i>" % (
225
+ self._unicode - 0xD8000
226
+ )
227
+ if 0xDB80 <= self._unicode < 0xDBFF:
228
+ return "<Private Use High Surrogate #%i>" % (self._unicode - 0xDB80)
229
+ if 0xDC00 <= self._unicode < 0xDFFF:
230
+ return "<Low Surrogate #%i>" % (self._unicode - 0xDC00)
231
+ return "<undefined>"
232
+ return name
233
+
234
+ @cached_property
235
+ def nice_name(self) -> str | None:
236
+ """A more human-readable Unicode name for the current codepoint."""
237
+ if self.name is None:
238
+ return None
239
+
240
+ for transform_function in nice_name_rules:
241
+ result = transform_function(self.name)
242
+ if result:
243
+ return result
244
+
245
+ return self.name.capitalize()
246
+
247
+ @cached_property
248
+ def decomposition_mapping(self) -> list[int]:
249
+ """The decomposition mapping for the current codepoint."""
250
+ if self._unicode is None:
251
+ return []
252
+
253
+ try:
254
+ dc = uniDecompositionMapping[self._unicode]
255
+ except KeyError:
256
+ dc = []
257
+ return dc
258
+
259
+ @cached_property
260
+ def lc_mapping(self) -> int | None:
261
+ """The lowercase mapping for the current codepoint."""
262
+ if self._unicode is None:
263
+ return None
264
+
265
+ return uniLowerCaseMapping.get(self._unicode, None)
266
+
267
+ @cached_property
268
+ def uc_mapping(self) -> int | None:
269
+ """The uppercase mapping for the current codepoint."""
270
+ if self._unicode is None:
271
+ return None
272
+
273
+ return uniUpperCaseMapping.get(self._unicode, None)
274
+
275
+ @cached_property
276
+ def script(self) -> str | None:
277
+ if self._unicode is None:
278
+ return None
279
+
280
+ return get_script(self._unicode)
jkUnicode/aglfn.py ADDED
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from re import compile
4
+ from .aglfnData import nameToUnicode
5
+
6
+ # Build reverse dictionary
7
+ unicodeToName = {value: key for key, value in nameToUnicode.items()}
8
+
9
+
10
+ def getUnicodeForGlyphname(name: str) -> int | None:
11
+ """Return the Unicode value as integer or None that is assigned to the
12
+ specified glyph name. It handles AGLFN names, uniXXXX names, uXXXXX names,
13
+ ligature names, variant names, and PUA-encoded ornament names (orn001 -
14
+ orn999, starting at 0xEA01).
15
+
16
+ :param name: The glyph name.
17
+ :type name: str"""
18
+ ornName = compile("^orn[0-9]{3}$")
19
+ length = len(name)
20
+ if "_" in name:
21
+ return None
22
+ elif "." in name[1:]:
23
+ return None
24
+ elif name in nameToUnicode.keys():
25
+ return nameToUnicode[name]
26
+ elif length == 7 and name.startswith("uni"):
27
+ return int(name[3:], 16)
28
+ elif length == 6 and name.startswith("u"):
29
+ try:
30
+ return int(name[1:], 16)
31
+ except ValueError:
32
+ return None
33
+ elif ornName.match(name):
34
+ return 0xEA00 + int(name[3:6])
35
+ else:
36
+ return None
37
+
38
+
39
+ def getGlyphnameForUnicode(code: int | None) -> str | None:
40
+ """Return the name as string or None that is assigned to the specified
41
+ Unicode value.
42
+
43
+ :param code: The codepoint.
44
+ :type code: int"""
45
+ if code is None:
46
+ return None
47
+ elif code in unicodeToName.keys():
48
+ return unicodeToName[code]
49
+ elif code < 0xFFFF:
50
+ return "uni%04X" % code
51
+ else:
52
+ return "u%05X" % code