Repository navigation
Expand file tree
/
Copy pathreport_unicode_coverage.py
More file actions
544 lines (515 loc) · 17.2 KB
/
Copy pathreport_unicode_coverage.py
File metadata and controls
544 lines (515 loc) · 17.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
#FLM: Reports · Unicode Coverage
# this_file: samples/report_unicode_coverage.py
"""Unicode coverage of the current font, block by block, with progress bars.
For every Unicode block that the font touches, shows how many of the
block's assigned characters (per Python's unicodedata) have a glyph.
Double-click a block to select its glyphs in the Font window. Block ranges
are embedded (planes 0 and 1). Nothing in the font changes.
"""
import sys
import unicodedata
import fontlab
from PythonQt import QtCore, QtGui
TITLE = "Unicode Coverage"
KEY = "report_unicode_coverage"
BLOCKS_TEXT = """
0000 007F Basic Latin
0080 00FF Latin-1 Supplement
0100 017F Latin Extended-A
0180 024F Latin Extended-B
0250 02AF IPA Extensions
02B0 02FF Spacing Modifier Letters
0300 036F Combining Diacritical Marks
0370 03FF Greek and Coptic
0400 04FF Cyrillic
0500 052F Cyrillic Supplement
0530 058F Armenian
0590 05FF Hebrew
0600 06FF Arabic
0700 074F Syriac
0750 077F Arabic Supplement
0780 07BF Thaana
07C0 07FF NKo
0800 083F Samaritan
0840 085F Mandaic
0860 086F Syriac Supplement
0870 089F Arabic Extended-B
08A0 08FF Arabic Extended-A
0900 097F Devanagari
0980 09FF Bengali
0A00 0A7F Gurmukhi
0A80 0AFF Gujarati
0B00 0B7F Oriya
0B80 0BFF Tamil
0C00 0C7F Telugu
0C80 0CFF Kannada
0D00 0D7F Malayalam
0D80 0DFF Sinhala
0E00 0E7F Thai
0E80 0EFF Lao
0F00 0FFF Tibetan
1000 109F Myanmar
10A0 10FF Georgian
1100 11FF Hangul Jamo
1200 137F Ethiopic
1380 139F Ethiopic Supplement
13A0 13FF Cherokee
1400 167F Unified Canadian Aboriginal Syllabics
1680 169F Ogham
16A0 16FF Runic
1700 171F Tagalog
1720 173F Hanunoo
1740 175F Buhid
1760 177F Tagbanwa
1780 17FF Khmer
1800 18AF Mongolian
18B0 18FF Unified Canadian Aboriginal Syllabics Extended
1900 194F Limbu
1950 197F Tai Le
1980 19DF New Tai Lue
19E0 19FF Khmer Symbols
1A00 1A1F Buginese
1A20 1AAF Tai Tham
1AB0 1AFF Combining Diacritical Marks Extended
1B00 1B7F Balinese
1B80 1BBF Sundanese
1BC0 1BFF Batak
1C00 1C4F Lepcha
1C50 1C7F Ol Chiki
1C80 1C8F Cyrillic Extended-C
1C90 1CBF Georgian Extended
1CC0 1CCF Sundanese Supplement
1CD0 1CFF Vedic Extensions
1D00 1D7F Phonetic Extensions
1D80 1DBF Phonetic Extensions Supplement
1DC0 1DFF Combining Diacritical Marks Supplement
1E00 1EFF Latin Extended Additional
1F00 1FFF Greek Extended
2000 206F General Punctuation
2070 209F Superscripts and Subscripts
20A0 20CF Currency Symbols
20D0 20FF Combining Diacritical Marks for Symbols
2100 214F Letterlike Symbols
2150 218F Number Forms
2190 21FF Arrows
2200 22FF Mathematical Operators
2300 23FF Miscellaneous Technical
2400 243F Control Pictures
2440 245F Optical Character Recognition
2460 24FF Enclosed Alphanumerics
2500 257F Box Drawing
2580 259F Block Elements
25A0 25FF Geometric Shapes
2600 26FF Miscellaneous Symbols
2700 27BF Dingbats
27C0 27EF Miscellaneous Mathematical Symbols-A
27F0 27FF Supplemental Arrows-A
2800 28FF Braille Patterns
2900 297F Supplemental Arrows-B
2980 29FF Miscellaneous Mathematical Symbols-B
2A00 2AFF Supplemental Mathematical Operators
2B00 2BFF Miscellaneous Symbols and Arrows
2C00 2C5F Glagolitic
2C60 2C7F Latin Extended-C
2C80 2CFF Coptic
2D00 2D2F Georgian Supplement
2D30 2D7F Tifinagh
2D80 2DDF Ethiopic Extended
2DE0 2DFF Cyrillic Extended-A
2E00 2E7F Supplemental Punctuation
2E80 2EFF CJK Radicals Supplement
2F00 2FDF Kangxi Radicals
2FF0 2FFF Ideographic Description Characters
3000 303F CJK Symbols and Punctuation
3040 309F Hiragana
30A0 30FF Katakana
3100 312F Bopomofo
3130 318F Hangul Compatibility Jamo
3190 319F Kanbun
31A0 31BF Bopomofo Extended
31C0 31EF CJK Strokes
31F0 31FF Katakana Phonetic Extensions
3200 32FF Enclosed CJK Letters and Months
3300 33FF CJK Compatibility
3400 4DBF CJK Unified Ideographs Extension A
4DC0 4DFF Yijing Hexagram Symbols
4E00 9FFF CJK Unified Ideographs
A000 A48F Yi Syllables
A490 A4CF Yi Radicals
A4D0 A4FF Lisu
A500 A63F Vai
A640 A69F Cyrillic Extended-B
A6A0 A6FF Bamum
A700 A71F Modifier Tone Letters
A720 A7FF Latin Extended-D
A800 A82F Syloti Nagri
A830 A83F Common Indic Number Forms
A840 A87F Phags-pa
A880 A8DF Saurashtra
A8E0 A8FF Devanagari Extended
A900 A92F Kayah Li
A930 A95F Rejang
A960 A97F Hangul Jamo Extended-A
A980 A9DF Javanese
A9E0 A9FF Myanmar Extended-B
AA00 AA5F Cham
AA60 AA7F Myanmar Extended-A
AA80 AADF Tai Viet
AAE0 AAFF Meetei Mayek Extensions
AB00 AB2F Ethiopic Extended-A
AB30 AB6F Latin Extended-E
AB70 ABBF Cherokee Supplement
ABC0 ABFF Meetei Mayek
AC00 D7AF Hangul Syllables
D7B0 D7FF Hangul Jamo Extended-B
D800 DB7F High Surrogates
DB80 DBFF High Private Use Surrogates
DC00 DFFF Low Surrogates
E000 F8FF Private Use Area
F900 FAFF CJK Compatibility Ideographs
FB00 FB4F Alphabetic Presentation Forms
FB50 FDFF Arabic Presentation Forms-A
FE00 FE0F Variation Selectors
FE10 FE1F Vertical Forms
FE20 FE2F Combining Half Marks
FE30 FE4F CJK Compatibility Forms
FE50 FE6F Small Form Variants
FE70 FEFF Arabic Presentation Forms-B
FF00 FFEF Halfwidth and Fullwidth Forms
FFF0 FFFF Specials
10000 1007F Linear B Syllabary
10080 100FF Linear B Ideograms
10100 1013F Aegean Numbers
10140 1018F Ancient Greek Numbers
10190 101CF Ancient Symbols
101D0 101FF Phaistos Disc
10280 1029F Lycian
102A0 102DF Carian
102E0 102FF Coptic Epact Numbers
10300 1032F Old Italic
10330 1034F Gothic
10350 1037F Old Permic
10380 1039F Ugaritic
103A0 103DF Old Persian
10400 1044F Deseret
10450 1047F Shavian
10480 104AF Osmanya
104B0 104FF Osage
10500 1052F Elbasan
10530 1056F Caucasian Albanian
10570 105BF Vithkuqi
105C0 105FF Todhri
10600 1077F Linear A
10780 107BF Latin Extended-F
10800 1083F Cypriot Syllabary
10840 1085F Imperial Aramaic
10860 1087F Palmyrene
10880 108AF Nabataean
108E0 108FF Hatran
10900 1091F Phoenician
10920 1093F Lydian
10940 1095F Sidetic
10980 1099F Meroitic Hieroglyphs
109A0 109FF Meroitic Cursive
10A00 10A5F Kharoshthi
10A60 10A7F Old South Arabian
10A80 10A9F Old North Arabian
10AC0 10AFF Manichaean
10B00 10B3F Avestan
10B40 10B5F Inscriptional Parthian
10B60 10B7F Inscriptional Pahlavi
10B80 10BAF Psalter Pahlavi
10C00 10C4F Old Turkic
10C80 10CFF Old Hungarian
10D00 10D3F Hanifi Rohingya
10D40 10D8F Garay
10E60 10E7F Rumi Numeral Symbols
10E80 10EBF Yezidi
10EC0 10EFF Arabic Extended-C
10F00 10F2F Old Sogdian
10F30 10F6F Sogdian
10F70 10FAF Old Uyghur
10FB0 10FDF Chorasmian
10FE0 10FFF Elymaic
11000 1107F Brahmi
11080 110CF Kaithi
110D0 110FF Sora Sompeng
11100 1114F Chakma
11150 1117F Mahajani
11180 111DF Sharada
111E0 111FF Sinhala Archaic Numbers
11200 1124F Khojki
11280 112AF Multani
112B0 112FF Khudawadi
11300 1137F Grantha
11380 113FF Tulu-Tigalari
11400 1147F Newa
11480 114DF Tirhuta
11580 115FF Siddham
11600 1165F Modi
11660 1167F Mongolian Supplement
11680 116CF Takri
116D0 116FF Myanmar Extended-C
11700 1174F Ahom
11800 1184F Dogra
118A0 118FF Warang Citi
11900 1195F Dives Akuru
119A0 119FF Nandinagari
11A00 11A4F Zanabazar Square
11A50 11AAF Soyombo
11AB0 11ABF Unified Canadian Aboriginal Syllabics Extended-A
11AC0 11AFF Pau Cin Hau
11B00 11B5F Devanagari Extended-A
11B60 11B7F Sharada Supplement
11BC0 11BFF Sunuwar
11C00 11C6F Bhaiksuki
11C70 11CBF Marchen
11D00 11D5F Masaram Gondi
11D60 11DAF Gunjala Gondi
11DB0 11DEF Tolong Siki
11DF0 11DFF Bengali Supplement
11EE0 11EFF Makasar
11F00 11F5F Kawi
11FB0 11FBF Lisu Supplement
11FC0 11FFF Tamil Supplement
12000 123FF Cuneiform
12400 1247F Cuneiform Numbers and Punctuation
12480 1254F Early Dynastic Cuneiform
12550 1268F Archaic Cuneiform Numerals
12F90 12FFF Cypro-Minoan
13000 1342F Egyptian Hieroglyphs
13430 1345F Egyptian Hieroglyph Format Controls
13460 143FF Egyptian Hieroglyphs Extended-A
14400 1467F Anatolian Hieroglyphs
16100 1613F Gurung Khema
16800 16A3F Bamum Supplement
16A40 16A6F Mro
16A70 16ACF Tangsa
16AD0 16AFF Bassa Vah
16B00 16B8F Pahawh Hmong
16D40 16D7F Kirat Rai
16E40 16E9F Medefaidrin
16EA0 16EDF Beria Erfe
16F00 16F9F Miao
16FE0 16FFF Ideographic Symbols and Punctuation
17000 187FF Tangut
18800 18AFF Tangut Components
18B00 18CFF Khitan Small Script
18D00 18D7F Tangut Supplement
18D80 18DFF Tangut Components Supplement
18E00 1919F Jurchen
191A0 191DF Jurchen Radicals
1AFF0 1AFFF Kana Extended-B
1B000 1B0FF Kana Supplement
1B100 1B12F Kana Extended-A
1B130 1B16F Small Kana Extension
1B170 1B2FF Nushu
1BC00 1BC9F Duployan
1BCA0 1BCAF Shorthand Format Controls
1CC00 1CEBF Symbols for Legacy Computing Supplement
1CEC0 1CEFF Miscellaneous Symbols Supplement
1CF00 1CFCF Znamenny Musical Notation
1D000 1D0FF Byzantine Musical Symbols
1D100 1D1FF Musical Symbols
1D200 1D24F Ancient Greek Musical Notation
1D250 1D28F Musical Symbols Supplement
1D2C0 1D2DF Kaktovik Numerals
1D2E0 1D2FF Mayan Numerals
1D300 1D35F Tai Xuan Jing Symbols
1D360 1D37F Counting Rod Numerals
1D400 1D7FF Mathematical Alphanumeric Symbols
1D800 1DAAF Sutton SignWriting
1DB00 1DBFF Miscellaneous Symbols and Arrows Extended
1DF00 1DFFF Latin Extended-G
1E000 1E02F Glagolitic Supplement
1E030 1E08F Cyrillic Extended-D
1E100 1E14F Nyiakeng Puachue Hmong
1E290 1E2BF Toto
1E2C0 1E2FF Wancho
1E4D0 1E4FF Nag Mundari
1E5D0 1E5FF Ol Onal
1E6C0 1E6FF Tai Yo
1E7E0 1E7FF Ethiopic Extended-B
1E800 1E8DF Mende Kikakui
1E900 1E95F Adlam
1EC70 1ECBF Indic Siyaq Numbers
1ED00 1ED4F Ottoman Siyaq Numbers
1EE00 1EEFF Arabic Mathematical Alphabetic Symbols
1F000 1F02F Mahjong Tiles
1F030 1F09F Domino Tiles
1F0A0 1F0FF Playing Cards
1F100 1F1FF Enclosed Alphanumeric Supplement
1F200 1F2FF Enclosed Ideographic Supplement
1F300 1F5FF Miscellaneous Symbols and Pictographs
1F600 1F64F Emoticons
1F650 1F67F Ornamental Dingbats
1F680 1F6FF Transport and Map Symbols
1F700 1F77F Alchemical Symbols
1F780 1F7FF Geometric Shapes Extended
1F800 1F8FF Supplemental Arrows-C
1F900 1F9FF Supplemental Symbols and Pictographs
1FA00 1FA6F Chess Symbols
1FA70 1FAFF Symbols and Pictographs Extended-A
1FB00 1FBFF Symbols for Legacy Computing
"""
BLOCKS = [(int(a, 16), int(b, 16), name) for a, b, name in
(line.split(" ", 2) for line in BLOCKS_TEXT.strip().splitlines())]
# ---------------------------------------------------------------- font access
def font_codes():
"""{code point: glyph name} of the current font (first glyph wins)."""
font = fontlab.CurrentFont()
out = {}
for glyph in (font.glyphs if font is not None else []):
for code in glyph.unicodes:
out.setdefault(code, glyph.name)
return out
def select_in_font_window(names):
import FL
font = FL.fl.font
wanted = set(names)
FL.fl.Unselect()
for i in range(len(font)):
if font[i].name in wanted:
FL.fl.Select(i)
fontlab.UpdateAll()
return [font[i].name for i in range(len(font)) if FL.fl.Selected(i)]
# ---------------------------------------------------------------- core
def assigned(start, end):
"""Number of assigned characters (not Cn) in a range."""
return sum(1 for code in range(start, end + 1) if unicodedata.category(chr(code)) != "Cn")
def coverage(codes, show_all=False):
"""[(start, end, block, covered, assigned)] for blocks the font touches (or all blocks)."""
rows, outside = [], set(codes)
for start, end, name in BLOCKS:
inside = [c for c in codes if start <= c <= end]
outside.difference_update(inside)
if inside or show_all:
rows.append((start, end, name, len(inside), assigned(start, end)))
return rows, sorted(outside)
# ---------------------------------------------------------------- GUI
def muted(widget):
color = QtGui.QColor(widget.palette.color(QtGui.QPalette.WindowText))
color.setAlpha(150)
return color.name(QtGui.QColor.HexArgb)
class Coverage(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setWindowTitle(TITLE)
palette = self.palette # FontLab can pair a light Base with a light Text (seen in dark mode): use WindowText
palette.setColor(QtGui.QPalette.Text, palette.color(QtGui.QPalette.WindowText))
self.setPalette(palette)
self.setMinimumWidth(600)
self.resize(640, 520)
self._connections = []
self.rows = []
self.codes = {}
root = QtGui.QVBoxLayout(self)
root.setContentsMargins(20, 18, 20, 16)
root.setSpacing(12)
heading = QtGui.QLabel(TITLE)
font = heading.font
font.setPointSizeF(font.pointSizeF() * 1.35)
font.setBold(True)
heading.setFont(font)
root.addWidget(heading)
self.subtitle = QtGui.QLabel()
self.subtitle.setWordWrap(True)
self.subtitle.setStyleSheet(f"color: {muted(self)};")
root.addWidget(self.subtitle)
self.table = QtGui.QTableWidget(0, 4)
self.table.setHorizontalHeaderLabels(["Block", "Range", "Coverage", "Glyphs"])
self.table.verticalHeader().setVisible(False)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.table.setAlternatingRowColors(True)
self.table.setShowGrid(False)
header = self.table.horizontalHeader()
header.setSectionResizeMode(QtGui.QHeaderView.ResizeToContents)
header.setSectionResizeMode(0, QtGui.QHeaderView.Stretch)
header.setSectionResizeMode(2, QtGui.QHeaderView.Stretch)
self.table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
root.addWidget(self.table, 1)
footer = QtGui.QHBoxLayout()
self.status = QtGui.QLabel()
self.status.setStyleSheet(f"color: {muted(self)};")
footer.addWidget(self.status, 1)
self.show_all = QtGui.QCheckBox("Show untouched blocks")
self.refresh_button = QtGui.QPushButton("Refresh")
self.close_button = QtGui.QPushButton("Close")
footer.addWidget(self.show_all)
footer.addWidget(self.refresh_button)
footer.addWidget(self.close_button)
root.addLayout(footer)
self._connect(self.show_all.toggled, lambda *a: self.refresh())
self._connect(self.refresh_button.clicked, lambda *a: self.refresh())
self._connect(self.table.cellDoubleClicked, lambda row, *a: self.select_block(row))
self._connect(self.close_button.clicked, lambda *a: self.close())
self.refresh()
def _connect(self, signal, slot):
def guarded(*args):
try:
slot(*args)
except Exception as error: # an exception escaping a PythonQt slot can take FontLab down
self.slot_error = repr(error)
label = getattr(self, "status", None)
if label is not None:
label.setText("Error: " + self.slot_error)
signal.connect(guarded)
self._connections.append((signal, guarded))
def refresh(self):
self.codes = font_codes()
self.rows, self.outside = coverage(list(self.codes), self.show_all.isChecked())
if not self.codes:
self.subtitle.setText("Open a font with encoded glyphs first.")
else:
full = sum(1 for r in self.rows if r[3] and r[3] >= r[4])
self.subtitle.setText(f"{len(self.codes)} code points in {sum(1 for r in self.rows if r[3])} blocks; "
f"{full} complete. Double-click a block to select its glyphs.")
self.table.setRowCount(len(self.rows))
for row, (start, end, name, covered, total) in enumerate(self.rows):
self.table.setItem(row, 0, QtGui.QTableWidgetItem(name))
span = QtGui.QTableWidgetItem(f"{start:04X}–{end:04X}")
span.setForeground(QtGui.QBrush(QtGui.QColor(muted(self))))
self.table.setItem(row, 1, span)
bar = QtGui.QProgressBar()
bar.setRange(0, max(1, total))
bar.setValue(min(covered, max(1, total)))
bar.setTextVisible(True)
bar.setFormat(f"{100.0 * covered / total:.0f}%" if total else "–")
bar.setMaximumHeight(16)
self.table.setCellWidget(row, 2, bar)
count = QtGui.QTableWidgetItem(f"{covered} / {total}")
count.setTextAlignment(int(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter))
self.table.setItem(row, 3, count)
extra = f" · {len(self.outside)} code points outside planes 0–1" if self.outside else ""
self.status.setText(f"{len(self.rows)} blocks listed{extra}")
def select_block(self, row):
if not 0 <= row < len(self.rows):
return
start, end, name = self.rows[row][:3]
names = [glyph for code, glyph in self.codes.items() if start <= code <= end]
selected = select_in_font_window(names)
self.last_result = {"block": name, "selected": selected}
self.status.setText(f"{name}: selected {len(selected)} glyph{'s' if len(selected) != 1 else ''}")
def closeEvent(self, event):
for signal, slot in self._connections:
try:
signal.disconnect(slot)
except Exception:
pass
self._connections = []
getattr(sys, "_fontlab_samples", {}).pop(KEY, None)
QtGui.QDialog.closeEvent(self, event)
def main():
windows = sys.__dict__.setdefault("_fontlab_samples", {})
old = windows.pop(KEY, None)
if old is not None:
old.close()
dialog = Coverage(fontlab.MainWindow())
windows[KEY] = dialog
dialog.show()
dialog.raise_()
return dialog
if __name__ == "__main__":
main()