29
29
from numbers import Number
30
30
import os
31
31
from pathlib import Path
32
+ import re
32
33
import subprocess
33
34
import sys
34
35
try :
86
87
'extra bold' : 800 ,
87
88
'black' : 900 ,
88
89
}
90
+ _weight_regexes = [
91
+ # From fontconfig's FcFreeTypeQueryFaceInternal; not the same as
92
+ # weight_dict!
93
+ ("thin" , 100 ),
94
+ ("extralight" , 200 ),
95
+ ("ultralight" , 200 ),
96
+ ("demilight" , 350 ),
97
+ ("semilight" , 350 ),
98
+ ("light" , 300 ), # Needs to come *after* demi/semilight!
99
+ ("book" , 380 ),
100
+ ("regular" , 400 ),
101
+ ("normal" , 400 ),
102
+ ("medium" , 500 ),
103
+ ("demibold" , 600 ),
104
+ ("demi" , 600 ),
105
+ ("semibold" , 600 ),
106
+ ("extrabold" , 800 ),
107
+ ("superbold" , 800 ),
108
+ ("ultrabold" , 800 ),
109
+ ("bold" , 700 ), # Needs to come *after* extra/super/ultrabold!
110
+ ("ultrablack" , 1000 ),
111
+ ("superblack" , 1000 ),
112
+ ("extrablack" , 1000 ),
113
+ (r"\bultra" , 1000 ),
114
+ ("black" , 900 ), # Needs to come *after* ultra/super/extrablack!
115
+ ("heavy" , 900 ),
116
+ ]
89
117
font_family_aliases = {
90
118
'serif' ,
91
119
'sans-serif' ,
95
123
'monospace' ,
96
124
'sans' ,
97
125
}
126
+
127
+
98
128
# OS Font paths
99
129
MSFolders = \
100
130
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
@@ -356,14 +386,21 @@ def ttfFontProperty(font):
356
386
# Styles are: italic, oblique, and normal (default)
357
387
358
388
sfnt = font .get_sfnt ()
389
+ mac_key = (1 , # platform: macintosh
390
+ 0 , # id: roman
391
+ 0 ) # langid: english
392
+ ms_key = (3 , # platform: microsoft
393
+ 1 , # id: unicode_cs
394
+ 0x0409 ) # langid: english_united_states
395
+
359
396
# These tables are actually mac_roman-encoded, but mac_roman support may be
360
397
# missing in some alternative Python implementations and we are only going
361
398
# to look for ASCII substrings, where any ASCII-compatible encoding works
362
399
# - or big-endian UTF-16, since important Microsoft fonts use that.
363
- sfnt2 = (sfnt .get ((1 , 0 , 0 , 2 ), b'' ).decode ('latin-1' ).lower () or
364
- sfnt .get ((3 , 1 , 0x0409 , 2 ), b'' ).decode ('utf_16_be' ).lower ())
365
- sfnt4 = (sfnt .get ((1 , 0 , 0 , 4 ), b'' ).decode ('latin-1' ).lower () or
366
- sfnt .get ((3 , 1 , 0x0409 , 4 ), b'' ).decode ('utf_16_be' ).lower ())
400
+ sfnt2 = (sfnt .get ((* mac_key , 2 ), b'' ).decode ('latin-1' ).lower () or
401
+ sfnt .get ((* ms_key , 2 ), b'' ).decode ('utf_16_be' ).lower ())
402
+ sfnt4 = (sfnt .get ((* mac_key , 4 ), b'' ).decode ('latin-1' ).lower () or
403
+ sfnt .get ((* ms_key , 4 ), b'' ).decode ('utf_16_be' ).lower ())
367
404
368
405
if sfnt4 .find ('oblique' ) >= 0 :
369
406
style = 'oblique'
@@ -384,10 +421,47 @@ def ttfFontProperty(font):
384
421
else :
385
422
variant = 'normal'
386
423
387
- if font .style_flags & ft2font .BOLD :
388
- weight = 700
389
- else :
390
- weight = next ((w for w in weight_dict if w in sfnt4 ), 400 )
424
+ # The weight-guessing algorithm is directly translated from fontconfig
425
+ # 2.13.1's FcFreeTypeQueryFaceInternal (fcfreetype.c).
426
+ wws_subfamily = 22
427
+ typographic_subfamily = 16
428
+ font_subfamily = 2
429
+ styles = [
430
+ sfnt .get ((* mac_key , wws_subfamily ), b'' ).decode ('latin-1' ),
431
+ sfnt .get ((* mac_key , typographic_subfamily ), b'' ).decode ('latin-1' ),
432
+ sfnt .get ((* mac_key , font_subfamily ), b'' ).decode ('latin-1' ),
433
+ sfnt .get ((* ms_key , wws_subfamily ), b'' ).decode ('utf-16-be' ),
434
+ sfnt .get ((* ms_key , typographic_subfamily ), b
9E81
39;' ).decode ('utf-16-be' ),
435
+ sfnt .get ((* ms_key , font_subfamily ), b'' ).decode ('utf-16-be' ),
436
+ ]
437
+ styles = [* filter (None , styles )] or [font .style_name ]
438
+
439
+ def get_weight (): # From fontconfig's FcFreeTypeQueryFaceInternal.
440
+ # OS/2 table weight.
441
+ os2 = font .get_sfnt_table ("OS/2" )
442
+ if os2 and os2 ["version" ] != 0xffff :
443
+ return os2 ["usWeightClass" ]
444
+ # PostScript font info weight.
445
+ try :
446
+ ps_font_info_weight = (
447
+ font .get_ps_font_info ()["weight" ].replace (" " , "" ) or "" )
448
+ except ValueError :
449
+ pass
450
+ else :
451
+ for regex , weight in _weight_regexes :
452
+ if re .fullmatch (regex , ps_font_info_weight , re .I ):
453
+ return weight
454
+ # Style name weight.
455
+ for style in styles :
456
+ style = style .replace (" " , "" )
457
+ for regex , weight in _weight_regexes :
458
+ if re .search (regex , style , re .I ):
459
+ return weight
460
+ if font .style_flags & ft2font .BOLD :
461
+ return 700 # "bold"
462
+ return 500 # "medium", not "regular"!
463
+
464
+ weight = int (get_weight ())
391
465
392
466
# Stretch can be absolute and relative
393
467
# Absolute stretches are: ultra-condensed, extra-condensed, condensed,
@@ -956,7 +1030,7 @@ class FontManager:
956
1030
# Increment this version number whenever the font cache data
957
1031
# format or behavior has changed and requires a existing font
958
1032
# cache files to be rebuilt.
959
- __version__ = 310
1033
+ __version__ = 330
960
1034
961
1035
def __init__ (self , size = None , weight = 'normal' ):
962
1036
self ._version = self .__version__
0 commit comments