From e3fabe843a1a15c312841ca68fde039e0d06adeb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 1 Jun 2009 18:12:57 -0700 Subject: [PATCH] Switch to using a C extension module to interface with fontconfig. This means that calibre now has a build time dependency on fontconfig as well. You can tell calibre where to find the font config headers and library via the environment variables: FC_INC_DIR and FC_LIB_DIR --- installer/osx/freeze.py | 2 - setup.py | 13 + src/calibre/constants.py | 3 +- src/calibre/ebooks/lrf/__init__.py | 3 +- src/calibre/gui2/convert/comic_input.py | 1 + src/calibre/gui2/convert/epub_output.py | 1 + src/calibre/gui2/convert/lrf_output.py | 1 + src/calibre/gui2/convert/mobi_output.py | 2 + src/calibre/gui2/convert/pdb_output.py | 1 + src/calibre/gui2/convert/pdf_output.py | 1 + src/calibre/gui2/convert/txt_output.py | 1 + src/calibre/gui2/convert/xpath_wizard.py | 2 +- src/calibre/gui2/convert/xpath_wizard.ui | 106 +++--- src/calibre/gui2/dialogs/config.py | 6 +- src/calibre/gui2/dialogs/scheduler.ui | 2 +- src/calibre/gui2/main.py | 18 +- src/calibre/gui2/widgets.py | 4 +- src/calibre/gui2/wizard/__init__.py | 10 + src/calibre/linux.py | 1 - src/calibre/utils/fontconfig.py | 400 ----------------------- src/calibre/utils/fonts/__init__.py | 150 +++++++++ src/calibre/utils/fonts/fontconfig.c | 331 +++++++++++++++++++ 22 files changed, 596 insertions(+), 463 deletions(-) delete mode 100644 src/calibre/utils/fontconfig.py create mode 100644 src/calibre/utils/fonts/__init__.py create mode 100644 src/calibre/utils/fonts/fontconfig.c diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index a16dd3aaee..8dee8feb7a 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -254,8 +254,6 @@ _check_symlinks_prescript() os.link(os.path.expanduser('~/pdftohtml/pdftohtml'), os.path.join(frameworks_dir, 'pdftohtml')) os.link(os.path.expanduser('~/pdftohtml/libpoppler.4.dylib'), os.path.join(frameworks_dir, 'libpoppler.4.dylib')) - print 'Adding plugins' - module_dir = os.path.join(resource_dir, 'lib', 'python2.6', 'lib-dynload') print 'Adding fontconfig' for f in glob.glob(os.path.expanduser('~/fontconfig-bundled/*')): dest = os.path.join(frameworks_dir, os.path.basename(f)) diff --git a/setup.py b/setup.py index ee2d54cc5a..14bbbf0a3f 100644 --- a/setup.py +++ b/setup.py @@ -76,8 +76,21 @@ if __name__ == '__main__': print 'WARNING: PoDoFo not found on your system. Various PDF related', print 'functionality will not work.' + fc_inc = '/usr/include/fontconfig' if islinux else \ + r'C:\cygwin\home\kovid\fontconfig\include\fontconfig' if iswindows else \ + '/Users/kovid/fontconfig/include/fontconfig' + fc_lib = '/usr/lib' if islinux else \ + r'C:\cygwin\home\kovid\fontconfig\lib' if iswindows else \ + '/Users/kovid/fontconfig/lib' + ext_modules = optional + [ + Extension('calibre.plugins.fontconfig', + sources = ['src/calibre/utils/fonts/fontconfig.c'], + include_dirs = [os.environ.get('FC_INC_DIR', fc_inc)], + libraries=['fontconfig'], + library_dirs=[os.environ.get('FC_LIB_DIR', fc_lib)]), + Extension('calibre.plugins.lzx', sources=['src/calibre/utils/lzx/lzxmodule.c', 'src/calibre/utils/lzx/compressor.c', diff --git a/src/calibre/constants.py b/src/calibre/constants.py index e7f7ffc344..bee906e01e 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -59,7 +59,8 @@ if plugins is None: plugin_path = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins') sys.path.insert(0, plugin_path) - for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc'] + \ + for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc', + 'fontconfig'] + \ (['winutil'] if iswindows else []) + \ (['usbobserver'] if isosx else []): try: diff --git a/src/calibre/ebooks/lrf/__init__.py b/src/calibre/ebooks/lrf/__init__.py index 9f6be65e3a..e4a18a1f91 100644 --- a/src/calibre/ebooks/lrf/__init__.py +++ b/src/calibre/ebooks/lrf/__init__.py @@ -35,7 +35,8 @@ class PRS500_PROFILE(object): name = 'prs500' def find_custom_fonts(options, logger): - from calibre.utils.fontconfig import files_for_family + from calibre.utils.fonts import fontconfig + files_for_family = fontconfig.files_for_family fonts = {'serif' : None, 'sans' : None, 'mono' : None} def family(cmd): return cmd.split(',')[-1].strip() diff --git a/src/calibre/gui2/convert/comic_input.py b/src/calibre/gui2/convert/comic_input.py index 00e095fb2a..e53d169e9a 100644 --- a/src/calibre/gui2/convert/comic_input.py +++ b/src/calibre/gui2/convert/comic_input.py @@ -13,6 +13,7 @@ from calibre.gui2.convert import Widget class PluginWidget(Widget, Ui_Form): TITLE = _('Comic Input') + HELP = _('Options specific to')+' comic '+_('input') def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'comic_input', diff --git a/src/calibre/gui2/convert/epub_output.py b/src/calibre/gui2/convert/epub_output.py index 74f0913398..98699f705f 100644 --- a/src/calibre/gui2/convert/epub_output.py +++ b/src/calibre/gui2/convert/epub_output.py @@ -13,6 +13,7 @@ from calibre.gui2.convert import Widget class PluginWidget(Widget, Ui_Form): TITLE = _('EPUB Output') + HELP = _('Options specific to')+' EPUB '+_('output') def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'epub_output', diff --git a/src/calibre/gui2/convert/lrf_output.py b/src/calibre/gui2/convert/lrf_output.py index 78a7bfa3fa..a9f1bfa6d1 100644 --- a/src/calibre/gui2/convert/lrf_output.py +++ b/src/calibre/gui2/convert/lrf_output.py @@ -16,6 +16,7 @@ font_family_model = None class PluginWidget(Widget, Ui_Form): TITLE = _('LRF Output') + HELP = _('Options specific to')+' LRF '+_('output') def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'lrf_output', diff --git a/src/calibre/gui2/convert/mobi_output.py b/src/calibre/gui2/convert/mobi_output.py index 32cb74ba47..797ab31493 100644 --- a/src/calibre/gui2/convert/mobi_output.py +++ b/src/calibre/gui2/convert/mobi_output.py @@ -13,6 +13,8 @@ from calibre.gui2.convert import Widget class PluginWidget(Widget, Ui_Form): TITLE = _('MOBI Output') + HELP = _('Options specific to')+' MOBI '+_('output') + def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'mobi_output', diff --git a/src/calibre/gui2/convert/pdb_output.py b/src/calibre/gui2/convert/pdb_output.py index 66563c3b24..57bf218d33 100644 --- a/src/calibre/gui2/convert/pdb_output.py +++ b/src/calibre/gui2/convert/pdb_output.py @@ -14,6 +14,7 @@ format_model = None class PluginWidget(Widget, Ui_Form): TITLE = _('PDB Output') + HELP = _('Options specific to')+' PDB '+_('output') def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'pdb_output', ['format']) diff --git a/src/calibre/gui2/convert/pdf_output.py b/src/calibre/gui2/convert/pdf_output.py index 2225b59436..99fb817cfc 100644 --- a/src/calibre/gui2/convert/pdf_output.py +++ b/src/calibre/gui2/convert/pdf_output.py @@ -15,6 +15,7 @@ orientation_model = None class PluginWidget(Widget, Ui_Form): TITLE = _('PDF Output') + HELP = _('Options specific to')+' PDF '+_('output') def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'pdf_output', ['paper_size', 'orientation']) diff --git a/src/calibre/gui2/convert/txt_output.py b/src/calibre/gui2/convert/txt_output.py index 6c084b18ff..dd36dc8cfc 100644 --- a/src/calibre/gui2/convert/txt_output.py +++ b/src/calibre/gui2/convert/txt_output.py @@ -14,6 +14,7 @@ newline_model = None class PluginWidget(Widget, Ui_Form): TITLE = _('TXT Output') + HELP = _('Options specific to')+' TXT '+_('output') def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'txt_output', ['newline']) diff --git a/src/calibre/gui2/convert/xpath_wizard.py b/src/calibre/gui2/convert/xpath_wizard.py index ebb43e7e79..d2a0d55a48 100644 --- a/src/calibre/gui2/convert/xpath_wizard.py +++ b/src/calibre/gui2/convert/xpath_wizard.py @@ -38,7 +38,7 @@ class Wizard(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) - self.resize(400, 300) + self.resize(440, 480) self.verticalLayout = QVBoxLayout(self) self.widget = WizardWidget(self) self.verticalLayout.addWidget(self.widget) diff --git a/src/calibre/gui2/convert/xpath_wizard.ui b/src/calibre/gui2/convert/xpath_wizard.ui index c23a2d1b7c..6f67312c47 100644 --- a/src/calibre/gui2/convert/xpath_wizard.ui +++ b/src/calibre/gui2/convert/xpath_wizard.ui @@ -1,7 +1,8 @@ - + + Form - - + + 0 0 @@ -9,133 +10,146 @@ 381 - + Form - - - - + + + + Match HTML &tags with tag name: - + tag - - - + + + true - + * - + a - + br - + div - + h1 - + h2 - + h3 - + h4 - + h5 - + h6 - + hr - + span - - - + + + Having the &attribute: - + attribute - - + + - - - + + + With &value: - + value - - - - - - + + + (A regular expression) - - - - <p>For example, to match all h2 tags that have class="chapter", set tag to <i>h2</i>, attribute to <i>class</i> and value to <i>chapter</i>.</p><p>Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag.</p><p>To learn more advanced usage of XPath see the <a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath Tutorial</a>. + + + + + + + <p>For example, to match all h2 tags that have class="chapter", set tag to <i>h2</i>, attribute to <i>class</i> and value to <i>chapter</i>.</p><p>Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag.</p><p>To learn more advanced usage of XPath see the <a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath Tutorial</a>. - + true - + true + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index c0030d7b3e..59e148046d 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -59,7 +59,6 @@ class ConfigTabs(QTabWidget): fromlist=[1]) pw = input_widget.PluginWidget pw.ICON = ':/images/forward.svg' - pw.HELP = _('Options specific to the input format.') self.widgets.append(widget_factory(pw)) except ImportError: continue @@ -71,14 +70,15 @@ class ConfigTabs(QTabWidget): fromlist=[1]) pw = output_widget.PluginWidget pw.ICON = ':/images/forward.svg' - pw.HELP = _('Options specific to the input format.') self.widgets.append(widget_factory(pw)) except ImportError: continue - for widget in self.widgets: + for i, widget in enumerate(self.widgets): self.addTab(widget, widget.TITLE.replace('\n', ' ').replace('&', '&&')) + self.setTabToolTip(i, widget.HELP if widget.HELP else widget.TITLE) + self.setUsesScrollButtons(True) def commit(self): for widget in self.widgets: diff --git a/src/calibre/gui2/dialogs/scheduler.ui b/src/calibre/gui2/dialogs/scheduler.ui index 84eb3daa6f..497b1215dc 100644 --- a/src/calibre/gui2/dialogs/scheduler.ui +++ b/src/calibre/gui2/dialogs/scheduler.ui @@ -6,7 +6,7 @@ 0 0 - 738 + 767 575 diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index bdc4568dce..ad472ed4db 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -107,7 +107,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): MainWindow.__init__(self, opts, parent) # Initialize fontconfig in a separate thread as this can be a lengthy # process if run for the first time on this machine - self.fc = __import__('calibre.utils.fontconfig', fromlist=1) + from calibre.utils.fonts import fontconfig + self.fc = fontconfig self.listener = Listener(listener) self.check_messages_timer = QTimer() self.connect(self.check_messages_timer, SIGNAL('timeout()'), @@ -1735,7 +1736,12 @@ def run_gui(opts, args, actions, listener, app): if getattr(main, 'restart_after_quit', False): e = sys.executable if getattr(sys, 'froze', False) else sys.argv[0] print 'Restarting with:', e, sys.argv - os.execvp(e, sys.argv) + if hasattr(sys, 'frameworks_dir'): + app = os.path.dirname(os.path.dirname(sys.frameworks_dir)) + import subprocess + subprocess.Popen('sleep 3s; open '+app, shell=True) + else: + os.execvp(e, sys.argv) else: if iswindows: try: @@ -1829,7 +1835,9 @@ if __name__ == '__main__': logfile = os.path.join(os.path.expanduser('~'), 'calibre.log') if os.path.exists(logfile): log = open(logfile).read().decode('utf-8', 'ignore') - d = QErrorMessage(('Error:%s
Traceback:
' - '%sLog:
%s')%(unicode(err), unicode(tb), log)) - d.exec_() + d = QErrorMessage() + d.showMessage(('Error:%s
Traceback:
' + '%sLog:
%s')%(unicode(err), + unicode(tb).replace('\n', '
'), + log.replace('\n', '
'))) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index d6df39b5d2..e918fa060e 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -16,7 +16,7 @@ from calibre.gui2 import human_readable, NONE, TableView, \ from calibre.gui2.dialogs.job_view_ui import Ui_Dialog from calibre.gui2.filename_pattern_ui import Ui_Form from calibre import fit_image -from calibre.utils.fontconfig import find_font_families +from calibre.utils.fonts import fontconfig from calibre.ebooks.metadata.meta import metadata_from_filename from calibre.utils.config import prefs @@ -293,7 +293,7 @@ class FontFamilyModel(QAbstractListModel): def __init__(self, *args): QAbstractListModel.__init__(self, *args) try: - self.families = find_font_families() + self.families = fontconfig.find_font_families() except: self.families = [] print 'WARNING: Could not load fonts' diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 1a0b626fbb..467aecd93f 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -23,6 +23,7 @@ from calibre.gui2.wizard.library_ui import Ui_WizardPage as LibraryUI from calibre.gui2.wizard.finish_ui import Ui_WizardPage as FinishUI from calibre.gui2.wizard.kindle_ui import Ui_WizardPage as KindleUI from calibre.gui2.wizard.stanza_ui import Ui_WizardPage as StanzaUI +from calibre.gui2 import min_available_height, available_width from calibre.utils.config import dynamic, prefs from calibre.gui2 import NONE, choose_dir, error_dialog @@ -483,6 +484,15 @@ class Wizard(QWizard): self.setPage(self.stanza_page.ID, self.stanza_page) self.device_extra_page = None + nh, nw = min_available_height()-75, available_width()-30 + if nh < 0: + nh = 580 + if nw < 0: + nw = 400 + nh = min(400, nh) + nw = min(580, nw) + self.resize(nw, nh) + def accept(self): self.device_page.commit() diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 1492374b8e..183ba73e04 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -27,7 +27,6 @@ entry_points = { 'librarything = calibre.ebooks.metadata.library_thing:main', 'calibre-debug = calibre.debug:main', 'calibredb = calibre.library.cli:main', - 'calibre-fontconfig = calibre.utils.fontconfig:main', 'calibre-parallel = calibre.utils.ipc.worker:main', 'calibre-customize = calibre.customize.ui:main', 'calibre-complete = calibre.utils.complete:main', diff --git a/src/calibre/utils/fontconfig.py b/src/calibre/utils/fontconfig.py deleted file mode 100644 index 2a5207c67c..0000000000 --- a/src/calibre/utils/fontconfig.py +++ /dev/null @@ -1,400 +0,0 @@ -#!/usr/bin/env python -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' -__docformat__ = 'restructuredtext en' - -''' -:mod:`fontconfig` -- Query system fonts -============================================= -.. module:: fontconfig - :platform: Unix, Windows, OS X - :synopsis: Query system fonts -.. moduleauthor:: Kovid Goyal - -A ctypes based wrapper around the `fontconfig `_ library. -It can be used to find all fonts available on the system as well as the closest -match to a given font specification. The main functions in this module are: - -.. autofunction:: find_font_families - -.. autofunction:: files_for_family - -.. autofunction:: match -''' - -import sys, os, locale, codecs, subprocess, re -from ctypes import cdll, c_void_p, Structure, c_int, POINTER, c_ubyte, c_char, util, \ - pointer, byref, create_string_buffer, Union, c_char_p, c_double - -try: - preferred_encoding = locale.getpreferredencoding() - codecs.lookup(preferred_encoding) -except: - preferred_encoding = 'utf-8' - -iswindows = 'win32' in sys.platform or 'win64' in sys.platform -isosx = 'darwin' in sys.platform -isbsd = 'bsd' in sys.platform -DISABLED = False -#if isosx: -# libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c')) -# size = ctypes.c_uint(0) -# ok = libc.sysctlbyname("hw.cpu64bit_capable", None, byref(size), None, 0) -# if ok != 0: -# is64bit = False -# else: -# buf = ctypes.c_char_p("\0" * size.value) -# ok = libc.sysctlbyname("hw.cpu64bit_capable", buf, byref(size), None, 0) -# if ok != 0: -# is64bit = False -# else: -# is64bit = '1' in buf.value -# DISABLED = is64bit - -def load_library(): - if isosx: - lib = os.path.join(getattr(sys, 'frameworks_dir'), 'libfontconfig.1.dylib') \ - if hasattr(sys, 'frameworks_dir') else util.find_library('fontconfig') - return cdll.LoadLibrary(lib) - elif iswindows: - return cdll.LoadLibrary('libfontconfig-1') - elif isbsd: - raw = subprocess.Popen('pkg-config --libs-only-L fontconfig'.split(), - stdout=subprocess.PIPE).stdout.read().strip() - match = re.search(r'-L([^\s,]+)', raw) - if not match: - return cdll.LoadLibrary('libfontconfig.so') - return cdll.LoadLibrary(match.group(1)+'/libfontconfig.so') - else: - try: - return cdll.LoadLibrary(util.find_library('fontconfig')) - except: - try: - return cdll.LoadLibrary('libfontconfig.so') - except: - return cdll.LoadLibrary('libfontconfig.so.1') - -class FcPattern(Structure): - _fields_ = [ - ('num', c_int), - ('size', c_int), - ('elts_offset', c_void_p), - ('ref', c_int) - ] -class FcFontSet(Structure): - _fields_ = [ - ('nfont', c_int), - ('sfont', c_int), - ('fonts', POINTER(POINTER(FcPattern))) - ] -( - FcTypeVoid, - FcTypeInteger, - FcTypeDouble, - FcTypeString, - FcTypeBool, - FcTypeMatrix, - FcTypeCharSet, - FcTypeFTFace, - FcTypeLangSet -) = map(c_int, range(9)) -(FcMatchPattern, FcMatchFont, FcMatchScan) = map(c_int, range(3)) -( -FcResultMatch, FcResultNoMatch, FcResultTypeMismatch, FcResultNoId, - FcResultOutOfMemory -) = map(c_int, range(5)) -FcFalse, FcTrue = c_int(0), c_int(1) - -class _FcValue(Union): - _fields_ = [ - ('s', c_char_p), - ('i', c_int), - ('b', c_int), - ('d', c_double), - ] - -class FcValue(Structure): - _fields_ = [ - ('type', c_int), - ('u', _FcValue) - ] - -class FcObjectSet(Structure): pass - -lib = load_library() -lib.FcPatternBuild.restype = POINTER(FcPattern) -lib.FcPatternCreate.restype = c_void_p -lib.FcObjectSetCreate.restype = POINTER(FcObjectSet) -lib.FcFontSetDestroy.argtypes = [POINTER(FcFontSet)] -lib.FcFontList.restype = POINTER(FcFontSet) -lib.FcNameUnparse.argtypes = [POINTER(FcPattern)] -lib.FcNameUnparse.restype = POINTER(c_ubyte) -lib.FcPatternGetString.argtypes = [POINTER(FcPattern), POINTER(c_char), c_int, c_void_p] -lib.FcPatternGetString.restype = c_int -lib.FcPatternAdd.argtypes = [c_void_p, POINTER(c_char), FcValue, c_int] -lib.FcPatternGetInteger.argtypes = [POINTER(FcPattern), POINTER(c_char), c_int, POINTER(c_int)] -lib.FcPatternGetInteger.restype = c_int -lib.FcNameParse.argtypes = [c_char_p] -lib.FcNameParse.restype = POINTER(FcPattern) -lib.FcDefaultSubstitute.argtypes = [POINTER(FcPattern)] -lib.FcConfigSubstitute.argtypes = [c_void_p, POINTER(FcPattern), c_int] -lib.FcFontSetCreate.restype = POINTER(FcFontSet) -lib.FcFontMatch.argtypes = [c_void_p, POINTER(FcPattern), POINTER(c_int)] -lib.FcFontMatch.restype = POINTER(FcPattern) -lib.FcFontSetAdd.argtypes = [POINTER(FcFontSet), POINTER(FcPattern)] -lib.FcFontSort.argtypes = [c_void_p, POINTER(FcPattern), c_int, c_void_p, POINTER(c_int)] -lib.FcFontSort.restype = POINTER(FcFontSet) -lib.FcFontRenderPrepare.argtypes = [c_void_p, POINTER(FcPattern), POINTER(FcPattern)] -lib.FcFontRenderPrepare.restype = POINTER(FcPattern) -lib.FcConfigCreate.restype = c_void_p -lib.FcConfigSetCurrent.argtypes = [c_void_p] -lib.FcConfigSetCurrent.restype = c_int -lib.FcConfigParseAndLoad.argtypes = [c_void_p, POINTER(c_char), c_int] -lib.FcConfigParseAndLoad.restype = c_int -lib.FcConfigBuildFonts.argtypes = [c_void_p] -lib.FcConfigBuildFonts.restype = c_int - -_init_error = None -_initialized = False -from threading import Thread - -class FontScanner(Thread): - def run(self): - # Initialize the fontconfig library. This has to be done manually - # for the OS X bundle as it may have its own private fontconfig. - if getattr(sys, 'frameworks_dir', False):# and not os.path.exists('/usr/X11/lib/libfontconfig.1.dylib'): - config_dir = os.path.join(os.path.dirname(getattr(sys, 'frameworks_dir')), 'Resources', 'fonts') - if isinstance(config_dir, unicode): - config_dir = config_dir.encode(sys.getfilesystemencoding()) - config = lib.FcConfigCreate() - if not lib.FcConfigParseAndLoad(config, os.path.join(config_dir, 'fonts.conf'), 1): - _init_error = 'Could not parse the fontconfig configuration' - return - if not lib.FcConfigBuildFonts(config): - _init_error = 'Could not build fonts' - return - if not lib.FcConfigSetCurrent(config): - _init_error = 'Could not set font config' - return - elif not lib.FcInit(): - _init_error = _('Could not initialize the fontconfig library') - return - global _initialized - _initialized = True - -if not DISABLED: - _scanner = FontScanner() - _scanner.start() - -def join(): - _scanner.join(120) - if _scanner.isAlive(): - raise RuntimeError('Scanning for system fonts seems to have hung. Try again in a little while.') - if _init_error is not None: - raise RuntimeError(_init_error) - -def find_font_families(allowed_extensions=['ttf', 'otf']): - ''' - Return an alphabetically sorted list of font families available on the system. - - `allowed_extensions`: A list of allowed extensions for font file types. Defaults to - `['ttf', 'otf']`. If it is empty, it is ignored. - ''' - if DISABLED: - return [] - join() - allowed_extensions = [i.lower() for i in allowed_extensions] - - empty_pattern = lib.FcPatternCreate() - oset = lib.FcObjectSetCreate() - if not lib.FcObjectSetAdd(oset, 'file'): - raise RuntimeError('Allocation failure') - if not lib.FcObjectSetAdd(oset, 'family'): - raise RuntimeError('Allocation failure') - fs = lib.FcFontList(0, empty_pattern, oset) - font_set = fs.contents - file = pointer(create_string_buffer(chr(0), 5000)) - family = pointer(create_string_buffer(chr(0), 200)) - font_families = [] - for i in range(font_set.nfont): - pat = font_set.fonts[i] - if lib.FcPatternGetString(pat, 'file', 0, byref(file)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - path = str(file.contents.value) - ext = os.path.splitext(path)[1] - if ext: - ext = ext[1:].lower() - if (not allowed_extensions) or (allowed_extensions and ext in allowed_extensions): - if lib.FcPatternGetString(pat, 'family', 0, byref(family)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - font_families.append(str(family.contents.value)) - - lib.FcObjectSetDestroy(oset) - lib.FcPatternDestroy(empty_pattern) - lib.FcFontSetDestroy(fs) - font_families = list(set(font_families)) - font_families.sort() - return font_families - -def files_for_family(family, normalize=True): - ''' - Find all the variants in the font family `family`. - Returns a dictionary of tuples. Each tuple is of the form (Full font name, path to font file). - The keys of the dictionary depend on `normalize`. If `normalize` is `False`, - they are a tuple (slant, weight) otherwise they are strings from the set - `('normal', 'bold', 'italic', 'bi', 'light', 'li')` - ''' - if DISABLED: - return {} - join() - if isinstance(family, unicode): - family = family.encode(preferred_encoding) - family_pattern = lib.FcPatternBuild(0, 'family', FcTypeString, family, None) - if not family_pattern: - raise RuntimeError('Allocation failure') - #lib.FcPatternPrint(family_pattern) - oset = lib.FcObjectSetCreate() - if not lib.FcObjectSetAdd(oset, 'file'): - raise RuntimeError('Allocation failure') - if not lib.FcObjectSetAdd(oset, 'weight'): - raise RuntimeError('Allocation failure') - if not lib.FcObjectSetAdd(oset, 'fullname'): - raise RuntimeError('Allocation failure') - if not lib.FcObjectSetAdd(oset, 'slant'): - raise RuntimeError('Allocation failure') - if not lib.FcObjectSetAdd(oset, 'style'): - raise RuntimeError('Allocation failure') - fonts = {} - fs = lib.FcFontList(0, family_pattern, oset) - font_set = fs.contents - file = pointer(create_string_buffer(5000)) - full_name = pointer(create_string_buffer(200)) - weight = c_int(0) - slant = c_int(0) - fname = '' - for i in range(font_set.nfont): - pat = font_set.fonts[i] - #lib.FcPatternPrint(pat) - pat = font_set.fonts[i] - if lib.FcPatternGetString(pat, 'file', 0, byref(file)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - if lib.FcPatternGetInteger(pat, 'weight', 0, byref(weight)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - if lib.FcPatternGetString(pat, 'fullname', 0, byref(full_name)) != FcResultMatch.value: - if lib.FcPatternGetString(pat, 'fullname', 0, byref(full_name)) == FcResultNoMatch.value: - if lib.FcPatternGetString(pat, 'style', 0, byref(full_name)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - fname = family + ' ' + full_name.contents.value - else: - raise RuntimeError('Error processing pattern') - else: - fname = full_name.contents.value - if lib.FcPatternGetInteger(pat, 'slant', 0, byref(slant)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - style = (slant.value, weight.value) - if normalize: - italic = slant.value > 0 - normal = weight.value == 80 - bold = weight.value > 80 - if italic: - style = 'italic' if normal else 'bi' if bold else 'li' - else: - style = 'normal' if normal else 'bold' if bold else 'light' - fonts[style] = (file.contents.value, fname) - lib.FcObjectSetDestroy(oset) - lib.FcPatternDestroy(family_pattern) - if not iswindows: - lib.FcFontSetDestroy(fs) - - return fonts - -def match(name, sort=False, verbose=False): - ''' - Find the system font that most closely matches `name`, where `name` is a specification - of the form:: - familyname-::... - - For example, `verdana:weight=bold:slant=italic` - - Returns a list of dictionaries. Each dictionary has the keys: 'weight', 'slant', 'family', 'file' - - `sort`: If `True` return a sorted list of matching fonts, where the sort id in order of - decreasing closeness of matching. - `verbose`: If `True` print debugging information to stdout - ''' - if DISABLED: - return [] - join() - if isinstance(name, unicode): - name = name.encode(preferred_encoding) - pat = lib.FcNameParse(name) - if not pat: - raise ValueError('Could not parse font name') - if verbose: - print 'Searching for pattern' - lib.FcPatternPrint(pat) - if not lib.FcConfigSubstitute(0, pat, FcMatchPattern): - raise RuntimeError('Allocation failure') - lib.FcDefaultSubstitute(pat) - fs = lib.FcFontSetCreate() - result = c_int(0) - matches = [] - if sort: - font_patterns = lib.FcFontSort(0, pat, FcFalse, 0, byref(result)) - if not font_patterns: - raise RuntimeError('Allocation failed') - fps = font_patterns.contents - for j in range(fps.nfont): - fpat = fps.fonts[j] - fp = lib.FcFontRenderPrepare(0, pat, fpat) - if fp: - lib.FcFontSetAdd(fs, fp) - lib.FcFontSetDestroy(font_patterns) - else: - match_pat = lib.FcFontMatch(0, pat, byref(result)) - if pat: - lib.FcFontSetAdd(fs, match_pat) - if result.value != FcResultMatch.value: - lib.FcPatternDestroy(pat) - return matches - font_set = fs.contents - - file = pointer(create_string_buffer(chr(0), 5000)) - family = pointer(create_string_buffer(chr(0), 200)) - weight = c_int(0) - slant = c_int(0) - for j in range(font_set.nfont): - fpat = font_set.fonts[j] - #lib.FcPatternPrint(fpat) - if lib.FcPatternGetString(fpat, 'file', 0, byref(file)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - if lib.FcPatternGetString(fpat, 'family', 0, byref(family)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - if lib.FcPatternGetInteger(fpat, 'weight', 0, byref(weight)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - if lib.FcPatternGetInteger(fpat, 'slant', 0, byref(slant)) != FcResultMatch.value: - raise RuntimeError('Error processing pattern') - - matches.append({ - 'file' : file.contents.value, - 'family' : family.contents.value, - 'weight' : weight.value, - 'slant' : slant.value, - } - ) - - lib.FcPatternDestroy(pat) - lib.FcFontSetDestroy(fs) - return matches - -def main(args=sys.argv): - print find_font_families() - if len(args) > 1: - print - print files_for_family(args[1]) - print - print match(args[1], verbose=True) - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/utils/fonts/__init__.py b/src/calibre/utils/fonts/__init__.py new file mode 100644 index 0000000000..f492132534 --- /dev/null +++ b/src/calibre/utils/fonts/__init__.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os, sys +from threading import Thread + +from calibre.constants import plugins + +_fc, _fc_err = plugins['fontconfig'] + +if _fc is None: + raise RuntimeError('Failed to load fontconfig with error:'+_fc_err) + +class FontConfig(Thread): + + def __init__(self): + Thread.__init__(self) + self.daemon = True + self.failed = False + + def run(self): + config = None + if getattr(sys, 'frameworks_dir', False): + config_dir = os.path.join(os.path.dirname( + getattr(sys, 'frameworks_dir')), 'Resources', 'fonts') + if isinstance(config_dir, unicode): + config_dir = config_dir.encode(sys.getfilesystemencoding()) + config = os.path.join(config_dir, 'fonts.conf') + try: + _fc.initialize(config) + except: + import traceback + traceback.print_exc() + self.failed = True + + def wait(self): + self.join() + if self.failed: + raise RuntimeError('Failed to initialize fontconfig') + + def find_font_families(self, allowed_extensions=['ttf', 'otf']): + ''' + Return an alphabetically sorted list of font families available on the system. + + `allowed_extensions`: A list of allowed extensions for font file types. Defaults to + `['ttf', 'otf']`. If it is empty, it is ignored. + ''' + self.wait() + ans = _fc.find_font_families([bytes('.'+x) for x in allowed_extensions]) + ans = sorted(set(ans), cmp=lambda x,y:cmp(x.lower(), y.lower())) + ans2 = [] + for x in ans: + try: + ans2.append(x.decode('utf-8')) + except UnicodeDecodeError: + continue + return ans2 + + def files_for_family(self, family, normalize=True): + ''' + Find all the variants in the font family `family`. + Returns a dictionary of tuples. Each tuple is of the form (Full font name, path to font file). + The keys of the dictionary depend on `normalize`. If `normalize` is `False`, + they are a tuple (slant, weight) otherwise they are strings from the set + `('normal', 'bold', 'italic', 'bi', 'light', 'li')` + ''' + self.wait() + if isinstance(family, unicode): + family = family.encode('utf-8') + fonts = {} + ofamily = str(family).decode('utf-8') + for fullname, path, style, nfamily, weight, slant in \ + _fc.files_for_family(str(family)): + style = (slant, weight) + if normalize: + italic = slant > 0 + normal = weight == 80 + bold = weight > 80 + if italic: + style = 'italic' if normal else 'bi' if bold else 'li' + else: + style = 'normal' if normal else 'bold' if bold else 'light' + try: + fullname, path = fullname.decode('utf-8'), path.decode('utf-8') + nfamily = nfamily.decode('utf-8') + except UnicodeDecodeError: + continue + if style in fonts: + if nfamily.lower().strip() == ofamily.lower().strip() \ + and 'Condensed' not in fullname and 'ExtraLight' not in fullname: + fonts[style] = (path, fullname) + else: + fonts[style] = (path, fullname) + + return fonts + + def match(self, name, all=False, verbose=False): + ''' + Find the system font that most closely matches `name`, where `name` is a specification + of the form:: + familyname-::... + + For example, `verdana:weight=bold:slant=italic` + + Returns a list of dictionaries, or a single dictionary. + Each dictionary has the keys: + 'weight', 'slant', 'family', 'file', 'fullname', 'style' + + `all`: If `True` return a sorted list of matching fonts, where the sort + is in order of decreasing closeness of matching. If `False` only the + best match is returned. ''' + self.wait() + if isinstance(name, unicode): + name = name.encode('utf-8') + fonts = [] + for fullname, path, style, family, weight, slant in \ + _fc.match(str(name), bool(all), bool(verbose)): + try: + fullname = fullname.decode('utf-8') + path = path.decode('utf-8') + style = style.decode('utf-8') + family = family.decode('utf-8') + fonts.append({ + 'fullname' : fullname, + 'path' : path, + 'style' : style, + 'family' : family, + 'weight' : weight, + 'slant' : slant + }) + except UnicodeDecodeError: + continue + return fonts if all else fonts[0] + +fontconfig = FontConfig() +fontconfig.start() + +def test(): + from pprint import pprint; + pprint(fontconfig.find_font_families()); + pprint(fontconfig.files_for_family('liberation serif')); + pprint(fontconfig.match('liberation serif:slant=italic:weight=bold', verbose=True)) + +if __name__ == '__main__': + test() diff --git a/src/calibre/utils/fonts/fontconfig.c b/src/calibre/utils/fonts/fontconfig.c new file mode 100644 index 0000000000..54ff22d915 --- /dev/null +++ b/src/calibre/utils/fonts/fontconfig.c @@ -0,0 +1,331 @@ +/* +:mod:`fontconfig` -- Pythonic interface to fontconfig +===================================================== + +.. module:: fontconfig + :platform: All + :synopsis: Pythonic interface to the fontconfig library + +.. moduleauthor:: Kovid Goyal Copyright 2009 + +*/ + +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#include + +static PyObject * +fontconfig_initialize(PyObject *self, PyObject *args) { + char *path; + FcBool ok; + FcConfig *config; + PyThreadState *_save; + + if (!PyArg_ParseTuple(args, "z", &path)) + return NULL; + if (path == NULL) { + _save = PyEval_SaveThread(); + ok = FcInit(); + PyEval_RestoreThread(_save); + } else { + config = FcConfigCreate(); + if (config == NULL) return PyErr_NoMemory(); + _save = PyEval_SaveThread(); + ok = FcConfigParseAndLoad(config, path, FcTrue); + if (ok) ok = FcConfigBuildFonts(config); + if (ok) ok = FcConfigSetCurrent(config); + PyEval_RestoreThread(_save); + if (!ok) return PyErr_NoMemory(); + ok = 1; + } + if (ok) Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +static +fontconfig_cleanup_find(FcPattern *p, FcObjectSet *oset, FcFontSet *fs) { + if (p != NULL) FcPatternDestroy(p); + if (oset != NULL) FcObjectSetDestroy(oset); + if (fs != NULL) FcFontSetDestroy(fs); +} + + +static PyObject * +fontconfig_find_font_families(PyObject *self, PyObject *args) { + int i; + size_t flen; + char *ext; + Py_ssize_t l, j, extlen; + FcBool ok; + FcPattern *pat, *temp; + FcObjectSet *oset; + FcFontSet *fs; + FcValue v, w; + PyObject *ans, *exts, *t; + + ans = PyList_New(0); + fs = NULL; oset = NULL; pat = NULL; + + if (ans == NULL) return PyErr_NoMemory(); + + if (!PyArg_ParseTuple(args, "O", &exts)) + return NULL; + + if (!PySequence_Check(exts)) { + PyErr_SetString(PyExc_ValueError, "Must pass sequence of extensions"); + return NULL; + } + l = PySequence_Size(exts); + + + pat = FcPatternCreate(); + if (pat == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + oset = FcObjectSetCreate(); + if (oset == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, FC_FILE)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, FC_FAMILY)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + fs = FcFontList(FcConfigGetCurrent(), pat, oset); + if (fs == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + for (i = 0; i < fs->nfont; i++) { + temp = fs->fonts[i]; + + if (temp == NULL) continue; + if (FcPatternGet(temp, FC_FILE, 0, &v) != FcResultMatch) continue; + + if (v.type == FcTypeString) { + flen = strlen((char *)v.u.s); + ok = FcFalse; + if (l == 0) ok = FcTrue; + for ( j = 0; j < l && !ok; j++) { + ext = PyBytes_AS_STRING(PySequence_ITEM(exts, j)); + extlen = PyBytes_GET_SIZE(PySequence_ITEM(exts, j)); + ok = flen > extlen && extlen > 0 && + PyOS_strnicmp(ext, v.u.s + (flen - extlen), extlen) == 0; + } + + if (ok) { + if (FcPatternGet(temp, FC_FAMILY, 0, &w) != FcResultMatch) continue; + if (w.type != FcTypeString) continue; + t = PyString_FromString(w.u.s); + if (t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (PyList_Append(ans, t) != 0) + { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + } + } + + } + fontconfig_cleanup_find(pat, oset, fs); + Py_INCREF(ans); + return ans; +} + +static PyObject * +fontconfig_files_for_family(PyObject *self, PyObject *args) { + char *family; int i; + FcPattern *pat, *tp; + FcObjectSet *oset; + FcFontSet *fs; + FcValue file, weight, fullname, style, slant, family2; + PyObject *ans, *temp, *t; + + if (!PyArg_ParseTuple(args, "s", &family)) + return NULL; + + ans = PyList_New(0); + if (ans == NULL) return PyErr_NoMemory(); + + fs = NULL; oset = NULL; pat = NULL; + + pat = FcPatternBuild(0, FC_FAMILY, FcTypeString, family, (char *) 0); + if (pat == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + oset = FcObjectSetCreate(); + if (oset == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, FC_FILE)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, FC_STYLE)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, FC_SLANT)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, FC_WEIGHT)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, FC_FAMILY)) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcObjectSetAdd(oset, "fullname")) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + fs = FcFontList(FcConfigGetCurrent(), pat, oset); + if (fs == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + for (i = 0; i < fs->nfont; i++) { + tp = fs->fonts[i]; + + if (tp == NULL) continue; + if (FcPatternGet(tp, FC_FILE, 0, &file) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_STYLE, 0, &style) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_WEIGHT, 0, &weight) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_SLANT, 0, &slant) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_FAMILY, 0, &family2) != FcResultMatch) continue; + if (FcPatternGet(tp, "fullname", 0, &fullname) != FcResultMatch) continue; + + temp = PyTuple_New(6); + if(temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + t = PyBytes_FromString(fullname.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 0, t); + t = PyBytes_FromString(file.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 1, t); + t = PyBytes_FromString(style.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 2, t); + t = PyBytes_FromString(family2.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 3, t); + t = PyInt_FromLong((long)weight.u.i); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 4, t); + t = PyInt_FromLong((long)slant.u.i); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 5, t); + if (PyList_Append(ans, temp) != 0) + { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + } + fontconfig_cleanup_find(pat, oset, fs); + Py_INCREF(ans); + return ans; +} + +static PyObject * +fontconfig_match(PyObject *self, PyObject *args) { + char *namespec; int i; + FcPattern *pat, *tp; + FcObjectSet *oset; + FcFontSet *fs, *fs2; + FcValue file, weight, fullname, style, slant, family; + FcResult res; + PyObject *ans, *temp, *t, *all, *verbose; + + if (!PyArg_ParseTuple(args, "sOO", &namespec, &all, &verbose)) + return NULL; + + ans = PyList_New(0); + if (ans == NULL) return PyErr_NoMemory(); + + fs = NULL; oset = NULL; pat = NULL; fs2 = NULL; + + pat = FcNameParse(namespec); + if (pat == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (PyObject_IsTrue(verbose)) FcPatternPrint(pat); + + if (!FcConfigSubstitute(FcConfigGetCurrent(), pat, FcMatchPattern)) + { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + FcDefaultSubstitute(pat); + + fs = FcFontSetCreate(); + if (fs == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (PyObject_IsTrue(all)) { + fs2 = FcFontSort(FcConfigGetCurrent(), pat, FcTrue, NULL, &res); + if (fs2 == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + for (i = 0; i < fs2->nfont; i++) { + tp = fs2->fonts[i]; + if (tp == NULL) continue; + tp = FcFontRenderPrepare(FcConfigGetCurrent(), pat, tp); + if (tp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcFontSetAdd(fs, tp)) + { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + } + if (fs2 != NULL) FcFontSetDestroy(fs2); + } else { + tp = FcFontMatch(FcConfigGetCurrent(), pat, &res); + if (tp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + if (!FcFontSetAdd(fs, tp)) + { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + } + + for (i = 0; i < fs->nfont; i++) { + tp = fs->fonts[i]; + if (tp == NULL) continue; + if (FcPatternGet(tp, FC_FILE, 0, &file) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_STYLE, 0, &style) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_WEIGHT, 0, &weight) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_SLANT, 0, &slant) != FcResultMatch) continue; + if (FcPatternGet(tp, FC_FAMILY, 0, &family) != FcResultMatch) continue; + if (FcPatternGet(tp, "fullname", 0, &fullname) != FcResultMatch) continue; + + temp = PyTuple_New(6); + if(temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + t = PyBytes_FromString(fullname.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 0, t); + t = PyBytes_FromString(file.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 1, t); + t = PyBytes_FromString(style.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 2, t); + t = PyBytes_FromString(family.u.s); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 3, t); + t = PyInt_FromLong((long)weight.u.i); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 4, t); + t = PyInt_FromLong((long)slant.u.i); + if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(temp, 5, t); + if (PyList_Append(ans, temp) != 0) + { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } + + } + fontconfig_cleanup_find(pat, oset, fs); + Py_INCREF(ans); + return ans; +} + + + +static +PyMethodDef fontconfig_methods[] = { + {"initialize", fontconfig_initialize, METH_VARARGS, + "initialize(path_to_config_file)\n\n" + "Initialize the library. If path to config file is specified it is used instead of the " + "default configuration. Returns True iff the initialization succeeded." + }, + + {"find_font_families", fontconfig_find_font_families, METH_VARARGS, + "find_font_families(allowed_extensions)\n\n" + "Find all font families on the system for fonts of the specified types. If no " + "types are specified all font families are returned." + }, + + {"files_for_family", fontconfig_files_for_family, METH_VARARGS, + "files_for_family(family, normalize)\n\n" + "Find all the variants in the font family `family`. " + "Returns a list of tuples. Each tuple is of the form " + "(fullname, path, style, family, weight, slant). " + }, + + {"match", fontconfig_match, METH_VARARGS, + "match(namespec,all,verbose)\n\n" + "Find all system fonts that match namespec, in decreasing order " + "of closeness. " + "Returns a list of tuples. Each tuple is of the form " + "(fullname, path, style, family, weight, slant). " + + }, + + {NULL, NULL, 0, NULL} +}; + + + +PyMODINIT_FUNC +initfontconfig(void) { + PyObject *m; + m = Py_InitModule3( + "fontconfig", fontconfig_methods, + "Find fonts." + ); + if (m == NULL) return; +} +