TTFQuery 2.0.1a1__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.
- ttfquery/__init__.py +2 -0
- ttfquery/_scriptregistry.py +16 -0
- ttfquery/describe.py +332 -0
- ttfquery/findsystem.py +142 -0
- ttfquery/glyph.py +194 -0
- ttfquery/glyphquery.py +90 -0
- ttfquery/guessdescription.py +135 -0
- ttfquery/scriptregistry.py +37 -0
- ttfquery/ttffamily.py +37 -0
- ttfquery/ttffiles.py +417 -0
- ttfquery/ttfgroups.py +50 -0
- ttfquery/ttfmetadata.py +41 -0
- ttfquery-2.0.1a1.dist-info/METADATA +83 -0
- ttfquery-2.0.1a1.dist-info/RECORD +17 -0
- ttfquery-2.0.1a1.dist-info/WHEEL +5 -0
- ttfquery-2.0.1a1.dist-info/entry_points.txt +5 -0
- ttfquery-2.0.1a1.dist-info/top_level.txt +1 -0
ttfquery/glyphquery.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Glyph-specific queries on font-files"""
|
|
2
|
+
from ttfquery import describe
|
|
3
|
+
try:
|
|
4
|
+
from OpenGLContext.debug.logs import text_log
|
|
5
|
+
except ImportError:
|
|
6
|
+
text_log = None
|
|
7
|
+
|
|
8
|
+
def hasGlyph( font, char, encoding=None ):
|
|
9
|
+
"""Check to see if font appears to have explicit glyph for char"""
|
|
10
|
+
glyfName = explicitGlyph( font, char, encoding )
|
|
11
|
+
if glyfName is None:
|
|
12
|
+
return False
|
|
13
|
+
return True
|
|
14
|
+
def explicitGlyph( font, char, encoding=None ):
|
|
15
|
+
"""Return glyphName or None if there is not explicit glyph for char"""
|
|
16
|
+
cmap = font['cmap']
|
|
17
|
+
if encoding is None:
|
|
18
|
+
encoding = describe.guessEncoding( font )
|
|
19
|
+
table = cmap.getcmap( *encoding )
|
|
20
|
+
glyfName = table.cmap.get( ord(char))
|
|
21
|
+
return glyfName
|
|
22
|
+
|
|
23
|
+
def glyphName( font, char, encoding=None, warnOnFailure=1 ):
|
|
24
|
+
"""Retrieve the glyph name for the given character
|
|
25
|
+
|
|
26
|
+
XXX
|
|
27
|
+
Not sure what the effect of the Unicode mapping
|
|
28
|
+
will be given the use of ord...
|
|
29
|
+
"""
|
|
30
|
+
glyfName = explicitGlyph( font, char, encoding )
|
|
31
|
+
if glyfName is None:
|
|
32
|
+
encoding = describe.guessEncoding( font ) #KH
|
|
33
|
+
cmap = font['cmap'] #KH
|
|
34
|
+
table = cmap.getcmap( *encoding ) #KH
|
|
35
|
+
glyfName = table.cmap.get( -1)
|
|
36
|
+
if glyfName is None:
|
|
37
|
+
glyfName = font['glyf'].glyphOrder[0]
|
|
38
|
+
if text_log and warnOnFailure:
|
|
39
|
+
text_log.warn(
|
|
40
|
+
"""Unable to find glyph name for %r, in %r using first glyph in table (%r)""",
|
|
41
|
+
char,
|
|
42
|
+
describe.shortName(font),
|
|
43
|
+
glyfName
|
|
44
|
+
)
|
|
45
|
+
return glyfName
|
|
46
|
+
|
|
47
|
+
def width( font, glyphName ):
|
|
48
|
+
"""Retrieve the width of the giving character for given font
|
|
49
|
+
|
|
50
|
+
The horizontal metrics table provides both the
|
|
51
|
+
width and the left side bearing, we should really
|
|
52
|
+
be using the left side bearing to adjust the
|
|
53
|
+
character, but that's a later project.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
return font['hmtx'].metrics[ glyphName ][0]
|
|
57
|
+
except KeyError:
|
|
58
|
+
raise ValueError( """Couldn't find glyph for glyphName %r"""%(
|
|
59
|
+
glyphName,
|
|
60
|
+
))
|
|
61
|
+
|
|
62
|
+
def lineHeight( font ):
|
|
63
|
+
"""Get the base-line to base-line height for the font
|
|
64
|
+
|
|
65
|
+
XXX
|
|
66
|
+
There is some fudging going on here as I
|
|
67
|
+
workaround what appears to be a problem with the
|
|
68
|
+
specification for sTypoDescender, which states
|
|
69
|
+
that it should normally be a negative value, but
|
|
70
|
+
winds up being positive in at least one font that
|
|
71
|
+
defines points below the zero axis.
|
|
72
|
+
|
|
73
|
+
XXX The entire OS/2 table doesn't appear in a few
|
|
74
|
+
fonts (symbol fonts in particular), such as Corel's
|
|
75
|
+
BeeHive and BlackLight 686.
|
|
76
|
+
"""
|
|
77
|
+
return charHeight(font) + font['OS/2'].sTypoLineGap
|
|
78
|
+
|
|
79
|
+
def charHeight( font ):
|
|
80
|
+
"""Determine the general character height for the font (for scaling)"""
|
|
81
|
+
ascent = font['OS/2'].sTypoAscender
|
|
82
|
+
descent = font['OS/2'].sTypoDescender
|
|
83
|
+
if descent > 0:
|
|
84
|
+
descent = - descent
|
|
85
|
+
return ascent - descent
|
|
86
|
+
|
|
87
|
+
def charDescent( font ):
|
|
88
|
+
"""Determine the general descent for the font (for scaling)"""
|
|
89
|
+
return font['OS/2'].sTypoDescender
|
|
90
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Heuristic attempting to extract descriptions from font names
|
|
2
|
+
|
|
3
|
+
This should only be used as a backup in case there is
|
|
4
|
+
no proper available querying mechanism (i.e. fonttools),
|
|
5
|
+
and even then, we really would rather avoid using this
|
|
6
|
+
extremely fragile mechanism.
|
|
7
|
+
|
|
8
|
+
Basically what happens is that a name compression function
|
|
9
|
+
is run over the font name, as seen in the Windows Registry,
|
|
10
|
+
producing a base name and a set of modifiers, with the
|
|
11
|
+
modifiers being the weight (as a string) and a flag
|
|
12
|
+
indicating whether the font appears to be italicised.
|
|
13
|
+
|
|
14
|
+
XXX This is English-specific and entirely dependent on the
|
|
15
|
+
naming conventions commonly seen when naming fonts, so
|
|
16
|
+
there will be a considerable number of cases where the
|
|
17
|
+
resulting name and flags will be incorrect.
|
|
18
|
+
"""
|
|
19
|
+
WEIGHTS = [
|
|
20
|
+
'demibold', 'extrabold', 'semibold', 'ultrabold', 'bold',
|
|
21
|
+
'ultralight', 'extralight', 'light',
|
|
22
|
+
'heavy',
|
|
23
|
+
'medium',
|
|
24
|
+
'thin',
|
|
25
|
+
]
|
|
26
|
+
IGNORE_MODIFIERS = [
|
|
27
|
+
"unicode",
|
|
28
|
+
"regular",
|
|
29
|
+
"normal",
|
|
30
|
+
"bt",
|
|
31
|
+
"let",
|
|
32
|
+
## "mt",
|
|
33
|
+
'itc',
|
|
34
|
+
"(truetype)",
|
|
35
|
+
'ft',
|
|
36
|
+
'win95bt',
|
|
37
|
+
'plain:1.0',
|
|
38
|
+
'plain',
|
|
39
|
+
'plain:',
|
|
40
|
+
]
|
|
41
|
+
WEIGHT_SYNONYMS = [
|
|
42
|
+
('lt', 'light'),
|
|
43
|
+
('lite', 'light'),
|
|
44
|
+
('bolditalic','bold'),
|
|
45
|
+
('demi', 'demibold'),
|
|
46
|
+
('semi', 'semibold'),
|
|
47
|
+
]
|
|
48
|
+
WEIGHT_MODIFIERS = [
|
|
49
|
+
'extra', 'ultra'
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
ITALIC_INDICATORS = [
|
|
54
|
+
'italic', 'ital','itali', 'it', 'bolditalic',
|
|
55
|
+
]
|
|
56
|
+
SYSTEM_FONTS = {}
|
|
57
|
+
|
|
58
|
+
def interpretModifiers( name ):
|
|
59
|
+
"""Heuristic attempt to get weight and italic data from font-name
|
|
60
|
+
|
|
61
|
+
return base_font_name, (weight, italic)
|
|
62
|
+
"""
|
|
63
|
+
name = name.replace('_',' ').replace('-',' ').lower().split()
|
|
64
|
+
weight = "normal"
|
|
65
|
+
for w in WEIGHTS:
|
|
66
|
+
if w in name:
|
|
67
|
+
weight = w
|
|
68
|
+
while w in name:
|
|
69
|
+
name.remove( w )
|
|
70
|
+
break
|
|
71
|
+
for w,syn in WEIGHT_SYNONYMS:
|
|
72
|
+
if w in name:
|
|
73
|
+
weight = syn
|
|
74
|
+
while w in name:
|
|
75
|
+
name.remove( w )
|
|
76
|
+
if weight in ('bold','light'):
|
|
77
|
+
# "extra bold", "extra light", etceteras
|
|
78
|
+
for w in WEIGHT_MODIFIERS:
|
|
79
|
+
if w in name:
|
|
80
|
+
weight = w+weight
|
|
81
|
+
while w in name:
|
|
82
|
+
name.remove( w )
|
|
83
|
+
break
|
|
84
|
+
# XXX should look for "extra" and "ultra" w/out bold/light,
|
|
85
|
+
# but that that point we're basically just shooting blind
|
|
86
|
+
# as to which it is...
|
|
87
|
+
for i in ITALIC_INDICATORS:
|
|
88
|
+
if i in name:
|
|
89
|
+
italic = 1
|
|
90
|
+
while i in name:
|
|
91
|
+
name.remove( i )
|
|
92
|
+
break
|
|
93
|
+
else:
|
|
94
|
+
italic = 0
|
|
95
|
+
for i in IGNORE_MODIFIERS:
|
|
96
|
+
while i in name:
|
|
97
|
+
name.remove( i )
|
|
98
|
+
name = " ".join( name )
|
|
99
|
+
if not name.strip():
|
|
100
|
+
# MT Extra
|
|
101
|
+
raise ValueError( "name reduced to null by name compression" )
|
|
102
|
+
return None,(None,None)
|
|
103
|
+
return name, (weight, italic)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def add( name, file ):
|
|
107
|
+
"""Add a font with name and file to this module's registry"""
|
|
108
|
+
name, style = interpretModifiers( name )
|
|
109
|
+
if name is not None:
|
|
110
|
+
SYSTEM_FONTS.setdefault(name, {})[style] = file
|
|
111
|
+
return name, style
|
|
112
|
+
def get( name, style=None ):
|
|
113
|
+
"""Get a font by name and optional style
|
|
114
|
+
|
|
115
|
+
Style defaults to ("normal", 0)
|
|
116
|
+
"""
|
|
117
|
+
name, tstyle = interpretModifiers( name )
|
|
118
|
+
if tstyle != style and style is None:
|
|
119
|
+
style = tstyle
|
|
120
|
+
set = SYSTEM_FONTS.get( name )
|
|
121
|
+
if set:
|
|
122
|
+
if style in set:
|
|
123
|
+
return set[style]
|
|
124
|
+
# try match on italics
|
|
125
|
+
for key, value in set.items():
|
|
126
|
+
if key[1] == style[1]:
|
|
127
|
+
return value
|
|
128
|
+
# try match on weight
|
|
129
|
+
for key, value in set.items():
|
|
130
|
+
if key[0] == style[0]:
|
|
131
|
+
return value
|
|
132
|
+
# well, there's at least one font here, use it...
|
|
133
|
+
for key, value in set.items():
|
|
134
|
+
return value
|
|
135
|
+
return None
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Provides a single font metadata cache from which multiple programs can run
|
|
2
|
+
|
|
3
|
+
Note: on systems without an XDG_CACHE_HOME or a HOME environment variable
|
|
4
|
+
this will store the cache in the *code* directory for ttfquery, which is
|
|
5
|
+
obviously sub-optimal.
|
|
6
|
+
|
|
7
|
+
`registryFile` -- calculated at load time as the location for the cache
|
|
8
|
+
"""
|
|
9
|
+
from ttfquery import ttffiles
|
|
10
|
+
import os, logging
|
|
11
|
+
log = logging.getLogger( __name__ )
|
|
12
|
+
|
|
13
|
+
### more robust registry-file location by John Hunter...
|
|
14
|
+
if 'XDG_CACHE_HOME' in os.environ:
|
|
15
|
+
registryFile = os.path.join( os.environ['XDG_CACHE_HOME'], "ttfquery", "font.cache")
|
|
16
|
+
elif 'HOME' in os.environ:
|
|
17
|
+
registryFile = os.path.join( os.environ['HOME'], ".font.cache")
|
|
18
|
+
else:
|
|
19
|
+
# OpenGLContext uses the Application Data directory for win32,
|
|
20
|
+
# should consider porting that code here...
|
|
21
|
+
registryFile = os.path.join( os.path.split(__file__)[0], "font.cache")
|
|
22
|
+
|
|
23
|
+
registry = None
|
|
24
|
+
def get_registry():
|
|
25
|
+
"""Get a singleton registry instance in `registryFile` scanning if new"""
|
|
26
|
+
global registry
|
|
27
|
+
if registry is not None:
|
|
28
|
+
return registry
|
|
29
|
+
if os.path.isfile( registryFile ):
|
|
30
|
+
registry = ttffiles.load( registryFile )
|
|
31
|
+
else:
|
|
32
|
+
registry = ttffiles.Registry()
|
|
33
|
+
log.info( """Scanning for system fonts...""" )
|
|
34
|
+
new,failed = registry.scan( printErrors = 1, force = 0)
|
|
35
|
+
log.info( """Scan complete. Saving to %r\n""", registryFile,)
|
|
36
|
+
registry.save(registryFile)
|
|
37
|
+
return registry
|
ttfquery/ttffamily.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Query for font-members of a particular family"""
|
|
3
|
+
from __future__ import print_function
|
|
4
|
+
from ttfquery import ttffiles
|
|
5
|
+
import sys, logging
|
|
6
|
+
log = logging.getLogger( __name__ )
|
|
7
|
+
|
|
8
|
+
def get_options():
|
|
9
|
+
options = ttffiles.get_options()
|
|
10
|
+
options.description = 'Find all members of a particular font-family'
|
|
11
|
+
options.add_argument(
|
|
12
|
+
'major', help='The major family component, e.g. ANY or SANS, SCRIPT, SERIF, SERIF-OLD, SERIF-SLAB, etc',
|
|
13
|
+
default='ANY',
|
|
14
|
+
nargs='?',
|
|
15
|
+
)
|
|
16
|
+
options.add_argument(
|
|
17
|
+
'minor', help='The minor family component, or all if unspecified',
|
|
18
|
+
nargs='?',
|
|
19
|
+
default=None,
|
|
20
|
+
)
|
|
21
|
+
return options
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
logging.basicConfig(level=logging.INFO)
|
|
25
|
+
options = get_options().parse_args()
|
|
26
|
+
registry = ttffiles.registry_for_options(options)
|
|
27
|
+
search(registry,options.major,options.minor)
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
def search(registry,major,minor=None):
|
|
31
|
+
for fontName in registry.familyMembers(major,minor):
|
|
32
|
+
print('F', fontName)
|
|
33
|
+
for specific in registry.fontMembers( fontName ):
|
|
34
|
+
print(' ', registry.fontFile( specific ))
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
main()
|