mongfontbuilder 0.4.0__tar.gz
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.
- mongfontbuilder-0.4.0/PKG-INFO +40 -0
- mongfontbuilder-0.4.0/README.md +15 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/__init__.py +275 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/__main__.py +36 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/README.md +3 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/__init__.py +100 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/aliases.json +484 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/glyphs/ligatures.yaml +544 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/ligatures.json +1106 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/locales.json +496 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/misc.py +5 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/particles.json +164 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/types.py +51 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/variants.json +11078 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/data/writtenUnits.json +142 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/otl.py +1565 -0
- mongfontbuilder-0.4.0/lib/mongfontbuilder/utils.py +24 -0
- mongfontbuilder-0.4.0/pyproject.toml +30 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mongfontbuilder
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Mongolian Font Builder.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Kushim Jiang
|
|
7
|
+
Author-email: kushim_jiang@outlook.com
|
|
8
|
+
Requires-Python: >= 3.10.0, < 4.0.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
|
|
16
|
+
Requires-Dist: cattrs (>=24.1.2,<25.0.0)
|
|
17
|
+
Requires-Dist: fontmake (>=3.7.1,<4.0.0)
|
|
18
|
+
Requires-Dist: tptq.feacomposer (>=1.5.0,<2.0.0)
|
|
19
|
+
Requires-Dist: ufonormalizer (>=0.6.2,<0.7.0)
|
|
20
|
+
Requires-Dist: unicodedata2 (>=16.0.0)
|
|
21
|
+
Project-URL: Repository, https://github.com/Kushim-Jiang/mongfontbuilder
|
|
22
|
+
Project-URL: releasenotes, https://github.com/Kushim-Jiang/mongfontbuilder/releases
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Mongolian Font Builder
|
|
26
|
+
|
|
27
|
+
# Tooling
|
|
28
|
+
|
|
29
|
+
The Python package `mongfontbuilder` is maintained in the [lib/](lib/) directory.
|
|
30
|
+
|
|
31
|
+
# Documentation
|
|
32
|
+
|
|
33
|
+
The documentation of this project is maintained in the [docs](docs/) directory, and will be published as a future version of the Mongolian UTN ([UTN \#57: Encoding an Shaping of the Mongolian Script](https://www.unicode.org/notes/tn57/)).
|
|
34
|
+
|
|
35
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guide.
|
|
36
|
+
|
|
37
|
+
## Data
|
|
38
|
+
|
|
39
|
+
The source of truth of the UTN’s data is maintained in the [data](data/) directory, which is then exported to [lib/mongfontbuilder/data.json](/lib/mongfontbuilder/data.json) for consuming from Python.
|
|
40
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Mongolian Font Builder
|
|
2
|
+
|
|
3
|
+
# Tooling
|
|
4
|
+
|
|
5
|
+
The Python package `mongfontbuilder` is maintained in the [lib/](lib/) directory.
|
|
6
|
+
|
|
7
|
+
# Documentation
|
|
8
|
+
|
|
9
|
+
The documentation of this project is maintained in the [docs](docs/) directory, and will be published as a future version of the Mongolian UTN ([UTN \#57: Encoding an Shaping of the Mongolian Script](https://www.unicode.org/notes/tn57/)).
|
|
10
|
+
|
|
11
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guide.
|
|
12
|
+
|
|
13
|
+
## Data
|
|
14
|
+
|
|
15
|
+
The source of truth of the UTN’s data is maintained in the [data](data/) directory, which is then exported to [lib/mongfontbuilder/data.json](/lib/mongfontbuilder/data.json) for consuming from Python.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from dataclasses import dataclass, field, replace
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
|
|
8
|
+
from fontTools import unicodedata
|
|
9
|
+
from ufoLib2.objects import Component, Font, Glyph
|
|
10
|
+
|
|
11
|
+
from .data import (
|
|
12
|
+
CharacterName,
|
|
13
|
+
JoiningPosition,
|
|
14
|
+
LocaleID,
|
|
15
|
+
VariantData,
|
|
16
|
+
VariantReference,
|
|
17
|
+
WrittenUnitID,
|
|
18
|
+
codePointToCmapVariant,
|
|
19
|
+
)
|
|
20
|
+
from .data.misc import fina, init, isol, medi
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def constructFont(font: Font, locales: list[LocaleID]) -> None:
|
|
24
|
+
from .otl import MongFeaComposer
|
|
25
|
+
|
|
26
|
+
constructPredefinedGlyphs(font, locales)
|
|
27
|
+
|
|
28
|
+
composer = MongFeaComposer(font, locales)
|
|
29
|
+
assert not font.features.text, font.features.text
|
|
30
|
+
font.features.text = composer.asFeatureFile().asFea().replace(":", "-") # HACK
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def splitWrittens(writtens: str | Iterable[str]) -> list[WrittenUnitID]:
|
|
34
|
+
"""
|
|
35
|
+
>>> splitWrittens("ABbCcc")
|
|
36
|
+
['A', 'Bb', 'Ccc']
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
if isinstance(writtens, str):
|
|
40
|
+
return re.sub(r"[A-Z]", lambda x: " " + x[0], writtens).removeprefix(" ").split(" ")
|
|
41
|
+
return list(writtens)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def getPosition(index: int, length: int):
|
|
45
|
+
return isol if length == 1 else (init if index == 0 else fina if index == length - 1 else medi)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def writtenCombinations(writtens: str, position: JoiningPosition) -> Iterator[list[str]]:
|
|
49
|
+
"""
|
|
50
|
+
>>> [*writtenCombinations("ABCD", "isol")]
|
|
51
|
+
[['A.init', 'B.medi', 'C.medi', 'D.fina'], ['A.init', 'B.medi', 'CD.fina'], ['A.init', 'BC.medi', 'D.fina'], ['A.init', 'BCD.fina'], ['AB.init', 'C.medi', 'D.fina'], ['AB.init', 'CD.fina'], ['ABC.init', 'D.fina'], ['ABCD.isol']]
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
parts = splitWrittens(writtens)
|
|
55
|
+
if "Lv" in parts:
|
|
56
|
+
index = parts.index("Lv")
|
|
57
|
+
if index > 0:
|
|
58
|
+
parts[index - 1] += parts.pop(index)
|
|
59
|
+
|
|
60
|
+
leftJoin = 1 if position in (medi, fina) else 0
|
|
61
|
+
rightJoin = 1 if position in (init, medi) else 0
|
|
62
|
+
placeholder = "X"
|
|
63
|
+
if leftJoin:
|
|
64
|
+
parts = [placeholder] + parts
|
|
65
|
+
if rightJoin:
|
|
66
|
+
parts += [placeholder]
|
|
67
|
+
|
|
68
|
+
combinations = [[]]
|
|
69
|
+
for part in parts:
|
|
70
|
+
newCombinations = []
|
|
71
|
+
for comb in combinations:
|
|
72
|
+
newCombinations.append(comb + [part])
|
|
73
|
+
if comb:
|
|
74
|
+
newCombinations.append(comb[:-1] + [comb[-1] + part])
|
|
75
|
+
combinations = newCombinations
|
|
76
|
+
|
|
77
|
+
for comb in combinations:
|
|
78
|
+
result = [
|
|
79
|
+
f"{written}.{getPosition(index, len(comb))}" for index, written in enumerate(comb)
|
|
80
|
+
][leftJoin : (len(comb) - rightJoin)]
|
|
81
|
+
if (
|
|
82
|
+
len([l for w in result for l in w if l.isupper()])
|
|
83
|
+
== len(list(l for l in str(writtens) if l.isupper()))
|
|
84
|
+
and result
|
|
85
|
+
):
|
|
86
|
+
yield result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class GlyphDescriptor:
|
|
91
|
+
codePoints: list[int]
|
|
92
|
+
units: list[WrittenUnitID]
|
|
93
|
+
position: JoiningPosition
|
|
94
|
+
suffixes: list[str] = field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def parse(cls, name: str) -> GlyphDescriptor:
|
|
98
|
+
"""
|
|
99
|
+
>>> GlyphDescriptor.parse('u1820.A.init')
|
|
100
|
+
GlyphDescriptor(codePoints=[6176], units=['A'], position='init', suffixes=[])
|
|
101
|
+
>>> GlyphDescriptor.parse('_A.init')
|
|
102
|
+
GlyphDescriptor(codePoints=[], units=['A'], position='init', suffixes=[])
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
x, y, position, *suffixes = (
|
|
106
|
+
"." + name.removeprefix("_") # _A.init
|
|
107
|
+
if name.startswith("_")
|
|
108
|
+
else name # u1820.A.init
|
|
109
|
+
).split(".")
|
|
110
|
+
units = splitWrittens(y)
|
|
111
|
+
assert units and all(i in data.writtenUnits for i in units), name
|
|
112
|
+
assert position in data.types.joiningPositions, name
|
|
113
|
+
instance = cls(
|
|
114
|
+
codePoints=[int(i.removeprefix("u"), 16) for i in x.split("_")] if x else [],
|
|
115
|
+
units=units,
|
|
116
|
+
position=position,
|
|
117
|
+
suffixes=suffixes,
|
|
118
|
+
)
|
|
119
|
+
assert str(instance) == name
|
|
120
|
+
return instance
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def fromData(
|
|
124
|
+
cls,
|
|
125
|
+
charName: CharacterName,
|
|
126
|
+
position: JoiningPosition,
|
|
127
|
+
variantData: VariantData | None = None,
|
|
128
|
+
suffixes: list[str] = [],
|
|
129
|
+
locale: LocaleID | None = None,
|
|
130
|
+
) -> GlyphDescriptor:
|
|
131
|
+
from .data import variantFromReference
|
|
132
|
+
|
|
133
|
+
if not variantData:
|
|
134
|
+
variantData = next(i for i in data.variants[charName][position].values() if i.default)
|
|
135
|
+
|
|
136
|
+
written = None
|
|
137
|
+
if locale and locale in variantData.locales:
|
|
138
|
+
written = variantData.locales[locale].written
|
|
139
|
+
if not written:
|
|
140
|
+
written = variantData.written
|
|
141
|
+
assert written, variantData
|
|
142
|
+
|
|
143
|
+
if isinstance(written, VariantReference):
|
|
144
|
+
units = variantFromReference(written, data.variants[charName])
|
|
145
|
+
suffixes = ["_" + position, *suffixes]
|
|
146
|
+
position = written.position
|
|
147
|
+
else:
|
|
148
|
+
units = written
|
|
149
|
+
return cls([ord(unicodedata.lookup(charName))], units, position, suffixes)
|
|
150
|
+
|
|
151
|
+
def __str__(self) -> str:
|
|
152
|
+
assert self.units, self
|
|
153
|
+
if self.codePoints:
|
|
154
|
+
name = "_".join(uNameFromCodePoint(i) for i in self.codePoints) + "."
|
|
155
|
+
else:
|
|
156
|
+
name = "_"
|
|
157
|
+
return name + ".".join(["".join(self.units), self.position, *self.suffixes])
|
|
158
|
+
|
|
159
|
+
def __add__(self, other: GlyphDescriptor) -> GlyphDescriptor:
|
|
160
|
+
"""
|
|
161
|
+
>>> GlyphDescriptor.parse('u1820.A.init') + GlyphDescriptor.parse('u1820.A.medi')
|
|
162
|
+
GlyphDescriptor(codePoints=[6176, 6176], units=['A', 'A'], position='init', suffixes=[])
|
|
163
|
+
"""
|
|
164
|
+
joiningType: dict[tuple[JoiningPosition, JoiningPosition], JoiningPosition] = {
|
|
165
|
+
("init", "fina"): "isol",
|
|
166
|
+
("init", "medi"): "init",
|
|
167
|
+
("medi", "medi"): "medi",
|
|
168
|
+
("medi", "fina"): "fina",
|
|
169
|
+
}
|
|
170
|
+
assert (self.position, other.position) in joiningType
|
|
171
|
+
return GlyphDescriptor(
|
|
172
|
+
self.codePoints + other.codePoints,
|
|
173
|
+
self.units + other.units,
|
|
174
|
+
joiningType[(self.position, other.position)],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def pseudoPosition(self) -> JoiningPosition | None:
|
|
178
|
+
if self.suffixes:
|
|
179
|
+
suffix = self.suffixes[0]
|
|
180
|
+
if suffix in pseudoPositionSuffixes:
|
|
181
|
+
position = suffix.removeprefix("_")
|
|
182
|
+
assert position in data.misc.joiningPositions
|
|
183
|
+
return position
|
|
184
|
+
|
|
185
|
+
def __hash__(self) -> int:
|
|
186
|
+
return hash(self.__str__())
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def uNameFromCodePoint(codePoint: int) -> str:
|
|
190
|
+
return f"u{codePoint:04X}"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
pseudoPositionSuffixes = ["_" + i for i in data.misc.joiningPositions]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def constructPredefinedGlyphs(
|
|
197
|
+
font: Font,
|
|
198
|
+
locales: list[LocaleID],
|
|
199
|
+
*,
|
|
200
|
+
initPadding: float = 40,
|
|
201
|
+
finaPadding: float = 100,
|
|
202
|
+
) -> None:
|
|
203
|
+
sources = list[GlyphDescriptor]()
|
|
204
|
+
for name in font.keys():
|
|
205
|
+
try:
|
|
206
|
+
target = GlyphDescriptor.parse(name)
|
|
207
|
+
except:
|
|
208
|
+
continue
|
|
209
|
+
sources.append(target)
|
|
210
|
+
|
|
211
|
+
targetedLocales = {*locales}
|
|
212
|
+
for charName, positionToFVSToVariant in data.variants.items():
|
|
213
|
+
variantNames = list[str]()
|
|
214
|
+
for position, fvsToVariant in positionToFVSToVariant.items():
|
|
215
|
+
for variant in fvsToVariant.values():
|
|
216
|
+
if not targetedLocales.intersection(variant.locales):
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
target = GlyphDescriptor.fromData(charName, position, variant)
|
|
220
|
+
targetName = str(target)
|
|
221
|
+
variantNames.append(targetName)
|
|
222
|
+
|
|
223
|
+
glyph = font.get(targetName)
|
|
224
|
+
if glyph is not None:
|
|
225
|
+
glyph.unicode = None
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
memberNames: list[str]
|
|
229
|
+
writtenTarget = replace(target, codePoints=[], suffixes=[])
|
|
230
|
+
for source in sources:
|
|
231
|
+
if source == writtenTarget:
|
|
232
|
+
memberNames = [str(source)]
|
|
233
|
+
break
|
|
234
|
+
else:
|
|
235
|
+
for source in sources:
|
|
236
|
+
if replace(source, codePoints=[]) == writtenTarget:
|
|
237
|
+
memberNames = [str(source)]
|
|
238
|
+
break
|
|
239
|
+
else:
|
|
240
|
+
for writtenVariants in writtenCombinations(
|
|
241
|
+
"".join(writtenTarget.units), writtenTarget.position
|
|
242
|
+
):
|
|
243
|
+
if len(writtenVariants) == len(writtenTarget.units):
|
|
244
|
+
memberNames = ["_" + i for i in writtenVariants]
|
|
245
|
+
break
|
|
246
|
+
else:
|
|
247
|
+
raise NotImplementedError(target)
|
|
248
|
+
|
|
249
|
+
members: list[Glyph | float] = [font[i] for i in memberNames]
|
|
250
|
+
if pseudoPosition := target.pseudoPosition():
|
|
251
|
+
if pseudoPosition in ["isol", "init"]:
|
|
252
|
+
members = [initPadding, *members]
|
|
253
|
+
if pseudoPosition in ["isol", "fina"]:
|
|
254
|
+
members = [*members, finaPadding]
|
|
255
|
+
composeGlyph(font, targetName, members)
|
|
256
|
+
|
|
257
|
+
if variantNames:
|
|
258
|
+
codePoint = ord(unicodedata.lookup(charName))
|
|
259
|
+
variant = GlyphDescriptor([codePoint], *codePointToCmapVariant[codePoint])
|
|
260
|
+
glyph = composeGlyph(font, uNameFromCodePoint(codePoint), [font[str(variant)]])
|
|
261
|
+
glyph.unicode = codePoint
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def composeGlyph(font: Font, name: str, members: list[Glyph | float]) -> Glyph:
|
|
265
|
+
font.lib.get("public.glyphOrder", []).append(name)
|
|
266
|
+
glyph = font.newGlyph(name)
|
|
267
|
+
for member in members:
|
|
268
|
+
if isinstance(member, Glyph):
|
|
269
|
+
component = Component(str(member.name))
|
|
270
|
+
component.move((glyph.width, 0))
|
|
271
|
+
glyph.components.append(component)
|
|
272
|
+
glyph.width += member.width
|
|
273
|
+
else:
|
|
274
|
+
glyph.width += member
|
|
275
|
+
return glyph
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
poetry run python -m mongfontbuilder
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from argparse import ArgumentParser
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ufoLib2 import Font
|
|
9
|
+
|
|
10
|
+
from . import constructFont
|
|
11
|
+
from .data import locales
|
|
12
|
+
|
|
13
|
+
parser = ArgumentParser()
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"input",
|
|
16
|
+
type=Path,
|
|
17
|
+
help="path to read source UFO font from",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"output",
|
|
21
|
+
type=Path,
|
|
22
|
+
help="path to write constructed UFO font to",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--locales",
|
|
26
|
+
metavar="LOCALE",
|
|
27
|
+
choices=locales,
|
|
28
|
+
nargs="+",
|
|
29
|
+
required=True,
|
|
30
|
+
help="targeted locales, one or more from: " + ", ".join(locales),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
font = Font.open(args.input)
|
|
35
|
+
constructFont(font, args.locales)
|
|
36
|
+
font.save(args.output, overwrite=True)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from importlib.resources import files
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from cattrs import structure
|
|
6
|
+
from fontTools import unicodedata
|
|
7
|
+
|
|
8
|
+
from .misc import JoiningPosition, joiningPositions
|
|
9
|
+
from .types import (
|
|
10
|
+
FVS,
|
|
11
|
+
AliasData,
|
|
12
|
+
CharacterName,
|
|
13
|
+
LocaleData,
|
|
14
|
+
LocaleID,
|
|
15
|
+
VariantData,
|
|
16
|
+
VariantReference,
|
|
17
|
+
WrittenUnitID,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
assert __package__
|
|
21
|
+
dir = files(__package__)
|
|
22
|
+
|
|
23
|
+
with (dir / "writtenUnits.json").open(encoding="utf-8") as f:
|
|
24
|
+
writtenUnits: list[WrittenUnitID] = json.load(f)
|
|
25
|
+
|
|
26
|
+
with (dir / "ligatures.json").open(encoding="utf-8") as f:
|
|
27
|
+
ligatures: dict[
|
|
28
|
+
Literal["required", "optional"],
|
|
29
|
+
dict[str, list[JoiningPosition]],
|
|
30
|
+
] = json.load(f)
|
|
31
|
+
|
|
32
|
+
with (dir / "locales.json").open(encoding="utf-8") as f:
|
|
33
|
+
locales = structure(
|
|
34
|
+
json.load(f),
|
|
35
|
+
dict[LocaleID, LocaleData],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
with (dir / "aliases.json").open(encoding="utf-8") as f:
|
|
39
|
+
aliases = structure(
|
|
40
|
+
json.load(f),
|
|
41
|
+
dict[CharacterName, AliasData],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
with (dir / "variants.json").open(encoding="utf-8") as f:
|
|
45
|
+
variants = structure(
|
|
46
|
+
json.load(f),
|
|
47
|
+
dict[CharacterName, dict[JoiningPosition, dict[FVS, VariantData]]],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
with (dir / "particles.json").open(encoding="utf-8") as f:
|
|
51
|
+
particles = structure(json.load(f), dict[LocaleID, dict[str, list[FVS]]])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def variantFromReference(
|
|
55
|
+
reference: VariantReference,
|
|
56
|
+
positionToFVSToVariantData: dict[JoiningPosition, dict[FVS, VariantData]],
|
|
57
|
+
) -> list[WrittenUnitID]:
|
|
58
|
+
position, fvs, locale = reference
|
|
59
|
+
if not locale:
|
|
60
|
+
written = positionToFVSToVariantData[position][fvs].written
|
|
61
|
+
else:
|
|
62
|
+
written = positionToFVSToVariantData[position][fvs].locales[locale].written
|
|
63
|
+
assert isinstance(written, list)
|
|
64
|
+
return written
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _resolveCmapVariants() -> dict[int, tuple[list[WrittenUnitID], JoiningPosition]]:
|
|
68
|
+
codePointToPositionToVariant = dict[
|
|
69
|
+
int, dict[JoiningPosition, tuple[list[WrittenUnitID], JoiningPosition]]
|
|
70
|
+
]()
|
|
71
|
+
for charName, positionToFVSToVariantData in variants.items():
|
|
72
|
+
codePoint = ord(unicodedata.lookup(charName))
|
|
73
|
+
for position, fvsToVariantData in positionToFVSToVariantData.items():
|
|
74
|
+
for data in fvsToVariantData.values():
|
|
75
|
+
if data.default:
|
|
76
|
+
written = data.written
|
|
77
|
+
if isinstance(written, VariantReference):
|
|
78
|
+
variant = (
|
|
79
|
+
variantFromReference(written, positionToFVSToVariantData),
|
|
80
|
+
written.position,
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
variant = written, position
|
|
84
|
+
codePointToPositionToVariant.setdefault(codePoint, {})[position] = variant
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
codePointToVariant = dict[int, tuple[list[WrittenUnitID], JoiningPosition]]()
|
|
88
|
+
for codePoint, positionToVariant in sorted(codePointToPositionToVariant.items()):
|
|
89
|
+
for position in joiningPositions:
|
|
90
|
+
if variant := positionToVariant.get(position):
|
|
91
|
+
if variant not in codePointToVariant.values():
|
|
92
|
+
codePointToVariant[codePoint] = variant
|
|
93
|
+
break
|
|
94
|
+
else:
|
|
95
|
+
raise NotImplementedError
|
|
96
|
+
|
|
97
|
+
return codePointToVariant
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
codePointToCmapVariant = _resolveCmapVariants()
|