Source code for ttfquery.describe

"""Extract meta-data from a font-file to describe the font"""
from fontTools import ttLib
import sys
try:
    from OpenGLContext.debug.logs import text_log
except ImportError:
    text_log = None

[docs]def openFont( filename ): """Get a new font object""" if isinstance( filename, (str,unicode)): filename = open( filename, 'rb') return ttLib.TTFont(filename)
FONT_SPECIFIER_NAME_ID = 4 FONT_SPECIFIER_FAMILY_ID = 1
[docs]def shortName( font ): """Get the short name from the font's names table""" name = "" family = "" for record in font['name'].names: if record.nameID == FONT_SPECIFIER_NAME_ID and not name: if '\000' in record.string: name = unicode(record.string, 'utf-16-be').encode('utf-8') else: name = record.string elif record.nameID == FONT_SPECIFIER_FAMILY_ID and not family: if '\000' in record.string: family = unicode(record.string, 'utf-16-be').encode('utf-8') else: family = record.string if name and family: break return name, family
FAMILY_NAMES = { 0: ("ANY",{}), 1: ("SERIF-OLD", { 0: "ANY", 1: "ROUNDED-LEGIBILITY", 2: "GARALDE", 3: "VENETIAN", 4: "VENETIAN-MODIFIED", 5: "DUTCH-MODERN", 6: "DUTCH-TRADITIONAL", 7: "CONTEMPORARY", 8: "CALLIGRAPHIC", 15: "MISCELLANEOUS", }), 2: ("SERIF-TRANSITIONAL", { 0: "ANY", 1: "DIRECT-LINE", 2: "SCRIPT", 15: "MISCELLANEOUS", }), 3: ("SERIF", { 0: "ANY", 1: "ITALIAN", 2: "SCRIPT", 15: "MISCELLANEOUS", }), 4: ("SERIF-CLARENDON",{ 0: "ANY", 1: "CLARENDON", 2: "MODERN", 3: "TRADITIONAL", 4: "NEWSPAPER", 5: "STUB-SERIF", 6: "MONOTYPE", 7: "TYPEWRITER", 15: "MISCELLANEOUS", }), 5: ("SERIF-SLAB",{ 0: 'ANY', 1: 'MONOTONE', 2: 'HUMANIST', 3: 'GEOMETRIC', 4: 'SWISS', 5: 'TYPEWRITER', 15: 'MISCELLANEOUS', }), 7: ("SERIF-FREEFORM",{ 0: 'ANY', 1: 'MODERN', 15: 'MISCELLANEOUS', }), 8: ("SANS",{ 0: 'ANY', 1: 'GOTHIC-NEO-GROTESQUE-IBM', 2: 'HUMANIST', 3: 'ROUND-GEOMETRIC-LOW-X', 4: 'ROUND-GEOMETRIC-HIGH-X', 5: 'GOTHIC-NEO-GROTESQUE', 6: 'GOTHIC-NEO-GROTESQUE-MODIFIED', 9: 'GOTHIC-TYPEWRITER', 10: 'MATRIX', 15: 'MISCELLANEOUS', }), 9: ("ORNAMENTAL",{ 0: 'ANY', 1: 'ENGRAVER', 2: 'BLACK-LETTER', 3: 'DECORATIVE', 4: 'THREE-DIMENSIONAL', 15: 'MISCELLANEOUS', }), 10:("SCRIPT",{ 0: 'ANY', 1: 'UNCIAL', 2: 'BRUSH-JOINED', 3: 'FORMAL-JOINED', 4: 'MONOTONE-JOINED', 5: 'CALLIGRAPHIC', 6: 'BRUSH-UNJOINED', 7: 'FORMAL-UNJOINED', 8: 'MONOTONE-UNJOINED', 15: 'MISCELLANEOUS', }), 12:("SYMBOL",{ 0: 'ANY', 3: 'MIXED-SERIF', 6: 'OLDSTYLE-SERIF', 7: 'NEO-GROTESQUE-SANS', 15: 'MISCELLANEOUS', }), } WEIGHT_NAMES = { 'thin':100, 'extralight':200, 'ultralight':200, 'light':300, 'normal':400, 'regular':400, 'plain':400, 'medium':500, 'semibold':600, 'demibold':600, 'bold':700, 'extrabold':800, 'ultrabold':800, 'black':900, 'heavy':900, } WEIGHT_NUMBERS = {} for key,value in WEIGHT_NAMES.items(): WEIGHT_NUMBERS.setdefault(value,[]).append(key)
[docs]def weightNumber( name ): """Convert a string-name to a weight number compatible with this module""" if isinstance( name, (str,unicode)): name = name.lower() name = name.replace( '-','').replace(' ','') if name and name[-1] == '+': name = name[:-1] adjust = 50 elif name and name[-1] == '-': name = name[:-1] adjust = -50 else: adjust = 0 return WEIGHT_NAMES[ name ]+ adjust else: return int(name) or 400 # for cases where number isn't properly specified
[docs]def weightName( number ): """Convert integer number to a human-readable weight-name""" number = int(number) or 400 if WEIGHT_NUMBERS.has_key( number ): return WEIGHT_NUMBERS[number] name = 'thin-' for x in range(100,1000, 100): if number >= x: name = WEIGHT_NUMBERS[x]+'+' return name
[docs]def family( font ): """Get the family (and sub-family) for a font""" HIBYTE = 65280 LOBYTE = 255 familyID = (font['OS/2'].sFamilyClass&HIBYTE)>>8 subFamilyID = font['OS/2'].sFamilyClass&LOBYTE return familyNames( familyID, subFamilyID )
[docs]def familyNames( familyID, subFamilyID=0 ): """Convert family integers to human-readable names""" familyName, subFamilies = FAMILY_NAMES.get( familyID, ('RESERVED',None)) if familyName == 'RESERVED': if text_log: text_log.warn( 'Font has invalid (reserved) familyID: %s', familyID ) if subFamilies: subFamily = subFamilies.get(subFamilyID, 'RESERVED') else: subFamily = 'ANY' return ( familyName, subFamily )
[docs]def modifiers( font ): """Get weight and italic modifiers for a font weight is taken from the OS/2 usWeightClass field italic is taken from either OS/2 fsSelection or head macStyle, if either indicates italics we report italics """ return ( # weight as an integer font['OS/2'].usWeightClass, ( # italic font['OS/2'].fsSelection &1 or font['head'].macStyle&2 ), )
[docs]def guessEncoding( font, given=None ): """Attempt to guess/retrieve an encoding from the font itself Basically this will try to get the given encoding (unless it is None). If given is a single integer or a single-item tuple, we will attempt scan looking for any table matching given as the platform ID and returning the first sub table. If given is a two-value tuple, we will require explicit matching, and raise errors if the encoding cannot be retrieved. if given is None, we will return the first encoding in the font. XXX This needs some work, particularly for non-win32 platforms, where there is no preference embodied for the native encoding. """ if isinstance( given, tuple) and given: if len(given) == 2: if __debug__: if text_log: text_log.info( """Checking for explicitly required encoding %r""", given ) if not font['cmap'].getcmap( *given ): raise ValueError( """The specified font encoding %r does not appear to be available within the font %r. Available encodings: %s"""% ( given, shortName(font), [ (table.platformID,table.platEncID) for table in font['cmap'].tables ], ) ) return given elif len(given) > 2: raise TypeError("""Encoding must be None, a two-tuple, or an integer, got %r"""%(given,)) else: # treat as a single integer, regardless of number of integer's given = given[0] if isinstance( given, (int, long)): for table in font['cmap'].tables: if table.platformID == given: return (table.platformID, table.platEncID) raise ValueError( """Could not find encoding with specified platformID==%s within the font %r. Available encodings: %s"""% ( given, shortName(font), [ (table.platformID,table.platEncID) for table in font['cmap'].tables ], ) ) if sys.platform == 'win32': prefered = (3,1) # should have prefered values for Linux and Mac as well... for table in font['cmap'].tables: if (table.platformID, table.platEncID) == prefered: return prefered # just retrieve the first table's values for table in font['cmap'].tables: return (table.platformID, table.platEncID) raise ValueError( """There are no encoding tables within the font %r, likely a corrupt font-file"""% ( shortName(font), ) )