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/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()