src/reportlab/lib/fontfinder.py
changeset 4377 da36c2d6e2d2
parent 4330 617ffa6bbdc8
child 4379 01b9c20f5204
equal deleted inserted replaced
4376:5998ef4b95e2 4377:da36c2d6e2d2
     1 #Copyright ReportLab Europe Ltd. 2000-2017
     1 #Copyright ReportLab Europe Ltd. 2000-2017
     2 #see license.txt for license details
     2 #see license.txt for license details
     3 __version__='3.3.0'
     3 __version__='3.4.22'
     4 
     4 
     5 #modification of users/robin/ttflist.py.
     5 #modification of users/robin/ttflist.py.
     6 __doc__="""This provides some general-purpose tools for finding fonts.
     6 __doc__="""This provides some general-purpose tools for finding fonts.
     7 
     7 
     8 The FontFinder object can search for font files.  It aims to build
     8 The FontFinder object can search for font files.  It aims to build
    59 update itself smartly on repeated instantiation.
    59 update itself smartly on repeated instantiation.
    60 """
    60 """
    61 import sys, time, os, tempfile
    61 import sys, time, os, tempfile
    62 from reportlab.lib.utils import pickle
    62 from reportlab.lib.utils import pickle
    63 from xml.sax.saxutils import quoteattr
    63 from xml.sax.saxutils import quoteattr
       
    64 from reportlab.lib.utils import asBytes
    64 try:
    65 try:
    65     from hashlib import md5
    66     from hashlib import md5
    66 except ImportError:
    67 except ImportError:
    67     from md5 import md5
    68     from md5 import md5
    68 
    69 
   112                     attrs.append('%s=%s' % (k, quoteattr(str(v))))
   113                     attrs.append('%s=%s' % (k, quoteattr(str(v))))
   113         return '<font ' + ' '.join(attrs) + '/>'
   114         return '<font ' + ' '.join(attrs) + '/>'
   114 
   115 
   115 from reportlab.lib.utils import rl_isdir, rl_isfile, rl_listdir, rl_getmtime
   116 from reportlab.lib.utils import rl_isdir, rl_isfile, rl_listdir, rl_getmtime
   116 class FontFinder:
   117 class FontFinder:
   117     def __init__(self, dirs=[], useCache=True, validate=False):
   118     def __init__(self, dirs=[], useCache=True, validate=False, recur=False, fsEncoding=None):
   118         self.useCache = useCache
   119         self.useCache = useCache
   119         self.validate = validate
   120         self.validate = validate
   120 
   121         if fsEncoding is None:
   121         self._dirs = set(dirs)
   122             fsEncoding = sys.getfilesystemencoding()
       
   123         self._fsEncoding = fsEncoding or 'utf8'
       
   124 
       
   125         self._dirs = set()
       
   126         self._recur = recur
       
   127         self.addDirectories(dirs)
   122         self._fonts = []
   128         self._fonts = []
   123 
   129 
   124         self._skippedFiles = [] #list of filenames we did not handle
   130         self._skippedFiles = [] #list of filenames we did not handle
   125         self._badFiles = []  #list of filenames we rejected
   131         self._badFiles = []  #list of filenames we rejected
   126 
   132 
   127         self._fontsByName = {}
   133         self._fontsByName = {}
   128         self._fontsByFamily = {}
   134         self._fontsByFamily = {}
   129         self._fontsByFamilyBoldItalic = {}   #indexed by bold, italic
   135         self._fontsByFamilyBoldItalic = {}   #indexed by bold, italic
   130 
   136 
   131     def addDirectory(self, dirName):
   137     def addDirectory(self, dirName, recur=None):
   132         #aesthetics - if there are 2 copies of a font, should the first or last
   138         #aesthetics - if there are 2 copies of a font, should the first or last
   133         #be picked up?  might need reversing
   139         #be picked up?  might need reversing
   134         if rl_isdir(dirName):
   140         if rl_isdir(dirName):
   135             self._dirs.add(dirName)
   141             self._dirs.add(dirName)
   136 
   142             if recur if recur is not None else self._recur:
   137     def addDirectories(self, dirNames):
   143                 for r,D,F in os.walk(dirName):
       
   144                     for d in D:
       
   145                         self._dirs.add(os.path.join(r,d))
       
   146 
       
   147     def addDirectories(self, dirNames,recur=None):
   138         for dirName in dirNames:
   148         for dirName in dirNames:
   139             self.addDirectory(dirName)
   149             self.addDirectory(dirName,recur=recur)
   140 
   150 
   141     def getFamilyNames(self):
   151     def getFamilyNames(self):
   142         "Returns a list of the distinct font families found"
   152         "Returns a list of the distinct font families found"
   143 
       
   144         if not self._fontsByFamily:
   153         if not self._fontsByFamily:
   145             fonts = self._fonts
   154             fonts = self._fonts
   146             for font in fonts:
   155             for font in fonts:
   147                 fam = font.familyName
   156                 fam = font.familyName
       
   157                 if fam is None: continue
   148                 if fam in self._fontsByFamily:
   158                 if fam in self._fontsByFamily:
   149                     self._fontsByFamily[fam].append(font)
   159                     self._fontsByFamily[fam].append(font)
   150                 else:
   160                 else:
   151                     self._fontsByFamily[fam] = [font]
   161                     self._fontsByFamily[fam] = [font]
   152         names = list(self._fontsByFamily.keys())
   162         fsEncoding = self._fsEncoding
       
   163         names = list(asBytes(_,enc=fsEncoding) for _ in self._fontsByFamily.keys())
   153         names.sort()
   164         names.sort()
   154         return names
   165         return names
   155 
   166 
   156     def getFontsInFamily(self, familyName):
   167     def getFontsInFamily(self, familyName):
   157         "Return list of all font objects with this family name"
   168         "Return list of all font objects with this family name"
   198         raise KeyError("Cannot find font %s with bold=%s, italic=%s" % (familyName, bold, italic))
   209         raise KeyError("Cannot find font %s with bold=%s, italic=%s" % (familyName, bold, italic))
   199 
   210 
   200     def _getCacheFileName(self):
   211     def _getCacheFileName(self):
   201         """Base this on the directories...same set of directories
   212         """Base this on the directories...same set of directories
   202         should give same cache"""
   213         should give same cache"""
   203         hash = md5(''.join(self._dirs)).hexdigest()
   214         fsEncoding = self._fsEncoding
       
   215         hash = md5(b''.join(asBytes(_,enc=fsEncoding) for _ in sorted(self._dirs))).hexdigest()
   204         from reportlab.lib.utils import get_rl_tempfile
   216         from reportlab.lib.utils import get_rl_tempfile
   205         fn = get_rl_tempfile('fonts_%s.dat' % hash)
   217         fn = get_rl_tempfile('fonts_%s.dat' % hash)
   206         return fn
   218         return fn
   207 
   219 
   208     def save(self, fileName):
   220     def save(self, fileName):
   209         f = open(fileName, 'w')
   221         f = open(fileName, 'wb')
   210         pickle.dump(self, f)
   222         pickle.dump(self, f)
   211         f.close()
   223         f.close()
   212 
   224 
   213     def load(self, fileName):
   225     def load(self, fileName):
   214         f = open(fileName, 'r')
   226         f = open(fileName, 'rb')
   215         finder2 = pickle.load(f)
   227         finder2 = pickle.load(f)
   216         f.close()
   228         f.close()
   217         self.__dict__.update(finder2.__dict__)
   229         self.__dict__.update(finder2.__dict__)
   218 
   230 
   219     def search(self):
   231     def search(self):
   231                 except:
   243                 except:
   232                     pass  #pickle load failed.  Ho hum, maybe it's an old pickle.  Better rebuild it.
   244                     pass  #pickle load failed.  Ho hum, maybe it's an old pickle.  Better rebuild it.
   233 
   245 
   234         from stat import ST_MTIME
   246         from stat import ST_MTIME
   235         for dirName in self._dirs:
   247         for dirName in self._dirs:
   236             fileNames = rl_listdir(dirName)
   248             try:
       
   249                 fileNames = rl_listdir(dirName)
       
   250             except:
       
   251                 continue
   237             for fileName in fileNames:
   252             for fileName in fileNames:
   238                 root, ext = os.path.splitext(fileName)
   253                 root, ext = os.path.splitext(fileName)
   239                 if ext.lower() in EXTENSIONS:
   254                 if ext.lower() in EXTENSIONS:
   240                     #it's a font
   255                     #it's a font
   241                     f = FontDescriptor()
   256                     f = FontDescriptor()
   242                     f.fileName = os.path.normpath(os.path.join(dirName, fileName))
   257                     f.fileName = os.path.normpath(os.path.join(dirName, fileName))
   243                     f.timeModified = rl_getmtime(f.fileName)
   258                     try:
       
   259                         f.timeModified = rl_getmtime(f.fileName)
       
   260                     except:
       
   261                         self._skippedFiles.append(fileName)
       
   262                         continue
   244 
   263 
   245                     ext = ext.lower()
   264                     ext = ext.lower()
   246                     if ext[0] == '.':
   265                     if ext[0] == '.':
   247                         ext = ext[1:]
   266                         ext = ext[1:]
   248                     f.typeCode = ext  #strip the dot
   267                     f.typeCode = ext  #strip the dot