2929from numbers import Number
3030import os
3131from pathlib import Path
32+ import re
3233import subprocess
3334import sys
3435try :
8687 'extra bold' : 800 ,
8788 'black' : 900 ,
8889}
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+ ]
89117font_family_aliases = {
90118 'serif' ,
91119 'sans-serif' ,
95123 'monospace' ,
96124 'sans' ,
97125}
126+
127+
98128# OS Font paths
99129MSFolders = \
100130 r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
@@ -356,14 +386,21 @@ def ttfFontProperty(font):
356386 # Styles are: italic, oblique, and normal (default)
357387
358388 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+
359396 # These tables are actually mac_roman-encoded, but mac_roman support may be
360397 # missing in some alternative Python implementations and we are only going
361398 # to look for ASCII substrings, where any ASCII-compatible encoding works
362399 # - 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 ())
367404
368405 if sfnt4 .find ('oblique' ) >= 0 :
369406 style = 'oblique'
@@ -384,10 +421,47 @@ def ttfFontProperty(font):
384421 else :
385422 variant = 'normal'
386423
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'' ).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 ())
391465
392466 # Stretch can be absolute and relative
393467 # Absolute stretches are: ultra-condensed, extra-condensed, condensed,
@@ -956,7 +1030,7 @@ class FontManager:
9561030 # Increment this version number whenever the font cache data
9571031 # format or behavior has changed and requires a existing font
9581032 # cache files to be rebuilt.
959- __version__ = 310
1033+ __version__ = 330
9601034
9611035 def __init__ (self , size = None , weight = 'normal' ):
9621036 self ._version = self .__version__
0 commit comments